doc/book/devweb/edition/dissection.rst
changeset 10491 c67bcee93248
parent 10434 8e04ab5582d9
child 11875 011730a4af73
equal deleted inserted replaced
10490:76ab3c71aff2 10491:c67bcee93248
       
     1 
       
     2 .. _form_dissection:
       
     3 
       
     4 Dissection of an entity form
       
     5 ----------------------------
       
     6 
       
     7 This is done (again) with a vanilla instance of the `tracker`_
       
     8 cube. We will populate the database with a bunch of entities and see
       
     9 what kind of job the automatic entity form does.
       
    10 
       
    11 .. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker
       
    12 
       
    13 Populating the database
       
    14 ~~~~~~~~~~~~~~~~~~~~~~~
       
    15 
       
    16 We should start by setting up a bit of context: a project with two
       
    17 unpublished versions, and a ticket linked to the project and the first
       
    18 version.
       
    19 
       
    20 .. sourcecode:: python
       
    21 
       
    22  >>> p = rql('INSERT Project P: P name "cubicweb"')
       
    23  >>> for num in ('0.1.0', '0.2.0'):
       
    24  ...  rql('INSERT Version V: V num "%s", V version_of P WHERE P eid %%(p)s' % num, {'p': p[0][0]})
       
    25  ...
       
    26  <resultset 'INSERT Version V: V num "0.1.0", V version_of P WHERE P eid %(p)s' (1 rows): [765L] (('Version',))>
       
    27  <resultset 'INSERT Version V: V num "0.2.0", V version_of P WHERE P eid %(p)s' (1 rows): [766L] (('Version',))>
       
    28  >>> t = rql('INSERT Ticket T: T title "let us write more doc", T done_in V, '
       
    29              'T concerns P WHERE V num "0.1.0"', P eid %(p)s', {'p': p[0][0]})
       
    30  >>> commit()
       
    31 
       
    32 Now let's see what the edition form builds for us.
       
    33 
       
    34 .. sourcecode:: python
       
    35 
       
    36  >>> cnx.use_web_compatible_requests('http://fakeurl.com')
       
    37  >>> req = cnx.request()
       
    38  >>> form = req.vreg['forms'].select('edition', req, rset=rql('Ticket T'))
       
    39  >>> html = form.render()
       
    40 
       
    41 .. note::
       
    42 
       
    43   In order to play interactively with web side application objects, we have to
       
    44   cheat a bit to have request object that will looks like HTTP request object, by
       
    45   calling :meth:`use_web_compatible_requests()` on the connection.
       
    46 
       
    47 This creates an automatic entity form. The ``.render()`` call yields
       
    48 an html (unicode) string. The html output is shown below (with
       
    49 internal fieldset omitted).
       
    50 
       
    51 Looking at the html output
       
    52 ~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    53 
       
    54 The form enveloppe
       
    55 ''''''''''''''''''
       
    56 
       
    57 .. sourcecode:: html
       
    58 
       
    59  <div class="iformTitle"><span>main informations</span></div>
       
    60  <div class="formBody">
       
    61   <form action="http://crater:9999/validateform" method="post" enctype="application/x-www-form-urlencoded"
       
    62         id="entityForm" onsubmit="return freezeFormButtons(&#39;entityForm&#39;);"
       
    63         class="entityForm" target="eformframe">
       
    64     <div id="progress">validating...</div>
       
    65     <fieldset>
       
    66       <input name="__form_id" type="hidden" value="edition" />
       
    67       <input name="__errorurl" type="hidden" value="http://perdu.com#entityForm" />
       
    68       <input name="__domid" type="hidden" value="entityForm" />
       
    69       <input name="__type:763" type="hidden" value="Ticket" />
       
    70       <input name="eid" type="hidden" value="763" />
       
    71       <input name="__maineid" type="hidden" value="763" />
       
    72       <input name="_cw_edited_fields:763" type="hidden"
       
    73              value="concerns-subject,done_in-subject,priority-subject,type-subject,title-subject,description-subject,__type,_cw_generic_field" />
       
    74       ...
       
    75     </fieldset>
       
    76     <iframe width="0px" height="0px" name="eformframe" id="eformframe" src="javascript: void(0);"></iframe>
       
    77    </form>
       
    78  </div>
       
    79 
       
    80 The main fieldset encloses a set of hidden fields containing various
       
    81 metadata, that will be used by the `edit controller` to process it
       
    82 back correctly.
       
    83 
       
    84 The `freezeFormButtons(...)` javascript callback defined on the
       
    85 ``onlick`` event of the form element prevents accidental multiple
       
    86 clicks in a row.
       
    87 
       
    88 The ``action`` of the form is mapped to the ``validateform`` controller
       
    89 (situated in :mod:`cubicweb.web.views.basecontrollers`).
       
    90 
       
    91 A full explanation of the validation loop is given in
       
    92 :ref:`validation_process`.
       
    93 
       
    94 .. _attributes_section:
       
    95 
       
    96 The attributes section
       
    97 ''''''''''''''''''''''
       
    98 
       
    99 We can have a look at some of the inner nodes of the form. Some fields
       
   100 are omitted as they are redundant for our purposes.
       
   101 
       
   102 .. sourcecode:: html
       
   103 
       
   104       <fieldset class="default">
       
   105         <table class="attributeForm">
       
   106           <tr class="title_subject_row">
       
   107             <th class="labelCol"><label class="required" for="title-subject:763">title</label></th>
       
   108             <td>
       
   109               <input id="title-subject:763" maxlength="128" name="title-subject:763" size="45"
       
   110                      tabindex="1" type="text" value="let us write more doc" />
       
   111             </td>
       
   112           </tr>
       
   113           ... (description field omitted) ...
       
   114           <tr class="priority_subject_row">
       
   115             <th class="labelCol"><label class="required" for="priority-subject:763">priority</label></th>
       
   116             <td>
       
   117               <select id="priority-subject:763" name="priority-subject:763" size="1" tabindex="4">
       
   118                 <option value="important">important</option>
       
   119                 <option selected="selected" value="normal">normal</option>
       
   120                 <option value="minor">minor</option>
       
   121               </select>
       
   122               <div class="helper">importance</div>
       
   123             </td>
       
   124           </tr>
       
   125           ... (type field omitted) ...
       
   126           <tr class="concerns_subject_row">
       
   127             <th class="labelCol"><label class="required" for="concerns-subject:763">concerns</label></th>
       
   128             <td>
       
   129               <select id="concerns-subject:763" name="concerns-subject:763" size="1" tabindex="6">
       
   130                 <option selected="selected" value="760">Foo</option>
       
   131               </select>
       
   132             </td>
       
   133           </tr>
       
   134           <tr class="done_in_subject_row">
       
   135             <th class="labelCol"><label for="done_in-subject:763">done in</label></th>
       
   136             <td>
       
   137               <select id="done_in-subject:763" name="done_in-subject:763" size="1" tabindex="7">
       
   138                 <option value="__cubicweb_internal_field__"></option>
       
   139                 <option selected="selected" value="761">Foo 0.1.0</option>
       
   140                 <option value="762">Foo 0.2.0</option>
       
   141               </select>
       
   142               <div class="helper">version in which this ticket will be / has been  done</div>
       
   143             </td>
       
   144           </tr>
       
   145         </table>
       
   146       </fieldset>
       
   147 
       
   148 
       
   149 Note that the whole form layout has been computed by the form
       
   150 renderer. It is the renderer which produces the table
       
   151 structure. Otherwise, the fields html structure is emitted by their
       
   152 associated widget.
       
   153 
       
   154 While it is called the `attributes` section of the form, it actually
       
   155 contains attributes and *mandatory relations*. For each field, we
       
   156 observe:
       
   157 
       
   158 * a dedicated row with a specific class, such as ``title_subject_row``
       
   159   (responsability of the form renderer)
       
   160 
       
   161 * an html widget (input, select, ...) with:
       
   162 
       
   163   * an id built from the ``rtype-role:eid`` pattern
       
   164 
       
   165   * a name built from the same pattern
       
   166 
       
   167   * possible values or preselected options
       
   168 
       
   169 The relations section
       
   170 '''''''''''''''''''''
       
   171 
       
   172 .. sourcecode:: html
       
   173 
       
   174       <fieldset class="This ticket :">
       
   175         <legend>This ticket :</legend>
       
   176         <table class="attributeForm">
       
   177           <tr class="_cw_generic_field_None_row">
       
   178             <td colspan="2">
       
   179               <table id="relatedEntities">
       
   180                 <tr><th>&#160;</th><td>&#160;</td></tr>
       
   181                 <tr id="relationSelectorRow_763" class="separator">
       
   182                   <th class="labelCol">
       
   183                     <select id="relationSelector_763" tabindex="8"
       
   184                             onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,763);">
       
   185                       <option value="">select a relation</option>
       
   186                       <option value="appeared_in_subject">appeared in</option>
       
   187                       <option value="custom_workflow_subject">custom workflow</option>
       
   188                       <option value="depends_on_object">dependency of</option>
       
   189                       <option value="depends_on_subject">depends on</option>
       
   190                       <option value="identical_to_subject">identical to</option>
       
   191                       <option value="see_also_subject">see also</option>
       
   192                     </select>
       
   193                   </th>
       
   194                   <td id="unrelatedDivs_763"></td>
       
   195                 </tr>
       
   196               </table>
       
   197             </td>
       
   198           </tr>
       
   199         </table>
       
   200       </fieldset>
       
   201 
       
   202 The optional relations are grouped into a drop-down combo
       
   203 box. Selection of an item triggers a javascript function which will:
       
   204 
       
   205 * show already related entities in the div of id `relatedentities`
       
   206   using a two-colown layout, with an action to allow deletion of
       
   207   individual relations (there are none in this example)
       
   208 
       
   209 * provide a relation selector in the div of id `relationSelector_EID`
       
   210   to allow the user to set up relations and trigger dynamic action on
       
   211   the last div
       
   212 
       
   213 * fill the div of id `unrelatedDivs_EID` with a dynamically computed
       
   214   selection widget allowing direct selection of an unrelated (but
       
   215   relatable) entity or a switch towards the `search mode` of
       
   216   |cubicweb| which allows full browsing and selection of an entity
       
   217   using a dedicated action situated in the left column boxes.
       
   218 
       
   219 
       
   220 The buttons zone
       
   221 ''''''''''''''''
       
   222 
       
   223 Finally comes the buttons zone.
       
   224 
       
   225 .. sourcecode:: html
       
   226 
       
   227       <table width="100%">
       
   228         <tbody>
       
   229           <tr>
       
   230             <td align="center">
       
   231               <button class="validateButton" tabindex="9" type="submit" value="validate">
       
   232                 <img alt="OK_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/ok.png" />
       
   233                 validate
       
   234               </button>
       
   235             </td>
       
   236             <td style="align: right; width: 50%;">
       
   237               <button class="validateButton"
       
   238                       onclick="postForm(&#39;__action_apply&#39;, &#39;button_apply&#39;, &#39;entityForm&#39;)"
       
   239                       tabindex="10" type="button" value="apply">
       
   240                 <img alt="APPLY_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/plus.png" />
       
   241                 apply
       
   242               </button>
       
   243               <button class="validateButton"
       
   244                       onclick="postForm(&#39;__action_cancel&#39;, &#39;button_cancel&#39;, &#39;entityForm&#39;)"
       
   245                       tabindex="11" type="button" value="cancel">
       
   246                 <img alt="CANCEL_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/cancel.png" />
       
   247                 cancel
       
   248               </button>
       
   249             </td>
       
   250           </tr>
       
   251         </tbody>
       
   252       </table>
       
   253 
       
   254 The most notable artifacts here are the ``postForm(...)`` calls
       
   255 defined on click events on these buttons. This function basically
       
   256 submits the form.
       
   257 
       
   258 .. _validation_process:
       
   259 
       
   260 The form validation process
       
   261 ---------------------------
       
   262 
       
   263 Validation loop
       
   264 ~~~~~~~~~~~~~~~
       
   265 
       
   266 On form submission, the form.action is invoked. Basically, the
       
   267 ``validateform`` controller is called and its output lands in the
       
   268 specified ``target``, an invisible ``<iframe>`` at the end of the
       
   269 form.
       
   270 
       
   271 Hence, the main page is not replaced, only the iframe contents. The
       
   272 ``validateform`` controller only outputs a tiny javascript fragment
       
   273 which is then immediately executed.
       
   274 
       
   275 .. sourcecode:: html
       
   276 
       
   277  <iframe width="0px" height="0px" name="eformframe" id="eformframe" src="javascript: void(0);">
       
   278    <script type="text/javascript">
       
   279      window.parent.handleFormValidationResponse('entityForm', null, null,
       
   280                                                 [false, [2164, {"name-subject": "required field"}], null],
       
   281                                                 null);
       
   282    </script>
       
   283  </iframe>
       
   284 
       
   285 The ``window.parent`` part ensures the javascript function is called
       
   286 on the right context (that is: the form element). We will describe its
       
   287 parameters:
       
   288 
       
   289 * first comes the form id (`entityForm`)
       
   290 
       
   291 * then two optional callbacks for the success and failure case
       
   292 
       
   293 * an array containing:
       
   294 
       
   295   * a boolean which indicates status (success or failure), and then, on error:
       
   296 
       
   297     * an array structured as ``[eid, {'rtype-role': 'error msg'}, ...]``
       
   298 
       
   299   * on success:
       
   300 
       
   301     * a url (string) representing the next thing to jump to
       
   302 
       
   303 Given the array structure described above, it is quite simple to
       
   304 manipulate the DOM to show the errors at appropriate places.
       
   305 
       
   306 Explanation
       
   307 ~~~~~~~~~~~
       
   308 
       
   309 This mecanism may seem a bit overcomplicated but we have to deal with
       
   310 two realities:
       
   311 
       
   312 * in the (strict) XHTML world, there are no iframes (hence the dynamic
       
   313   inclusion, tolerated by Firefox)
       
   314 
       
   315 * no (or not all) browser(s) support file input field handling through
       
   316   ajax.