This is done (again) with a vanilla instance of the tracker cube. We will populate the database with a bunch of entities and see what kind of job the automatic entity form does.
We should start by setting up a bit of context: a project with two unpublished versions, and a ticket linked to the project and the first version.
>>> p = rql('INSERT Project P: P name "cubicweb"')
>>> for num in ('0.1.0', '0.2.0'):
... rql('INSERT Version V: V num "%s", V version_of P WHERE P eid %%(p)s' % num, {'p': p[0][0]})
...
<resultset 'INSERT Version V: V num "0.1.0", V version_of P WHERE P eid %(p)s' (1 rows): [765L] (('Version',))>
<resultset 'INSERT Version V: V num "0.2.0", V version_of P WHERE P eid %(p)s' (1 rows): [766L] (('Version',))>
>>> t = rql('INSERT Ticket T: T title "let us write more doc", T done_in V, '
'T concerns P WHERE V num "0.1.0"', P eid %(p)s', {'p': p[0][0]})
>>> commit()
Now let’s see what the edition form builds for us.
>>> cnx.use_web_compatible_requests('http://fakeurl.com')
>>> req = cnx.request()
>>> form = req.vreg['forms'].select('edition', req, rset=rql('Ticket T'))
>>> html = form.render()
Note
In order to play interactively with web side application objects, we have to cheat a bit to have request object that will looks like HTTP request object, by calling use_web_compatible_requests() on the connection.
This creates an automatic entity form. The .render() call yields an html (unicode) string. The html output is shown below (with internal fieldset omitted).
<div class="iformTitle"><span>main informations</span></div>
<div class="formBody">
<form action="http://crater:9999/validateform" method="post" enctype="application/x-www-form-urlencoded"
id="entityForm" onsubmit="return freezeFormButtons('entityForm');"
class="entityForm" cubicweb:target="eformframe">
<div id="progress">validating...</div>
<fieldset>
<input name="__form_id" type="hidden" value="edition" />
<input name="__errorurl" type="hidden" value="http://perdu.com#entityForm" />
<input name="__domid" type="hidden" value="entityForm" />
<input name="__type:763" type="hidden" value="Ticket" />
<input name="eid" type="hidden" value="763" />
<input name="__maineid" type="hidden" value="763" />
<input name="_cw_edited_fields:763" type="hidden"
value="concerns-subject,done_in-subject,priority-subject,type-subject,title-subject,description-subject,__type,_cw_generic_field" />
...
</fieldset>
</form>
</div>
The main fieldset encloses a set of hidden fields containing various metadata, that will be used by the edit controller to process it back correctly.
The freezeFormButtons(...) javascript callback defined on the onlick event of the form element prevents accidental multiple clicks in a row.
The action of the form is mapped to the validateform controller (situated in cubicweb.web.views.basecontrollers).
A full explanation of the validation loop is given in The form validation process.
We can have a look at some of the inner nodes of the form. Some fields are omitted as they are redundant for our purposes.
<fieldset class="default">
<table class="attributeForm">
<tr class="title_subject_row">
<th class="labelCol"><label class="required" for="title-subject:763">title</label></th>
<td>
<input id="title-subject:763" maxlength="128" name="title-subject:763" size="45"
tabindex="1" type="text" value="let us write more doc" />
</td>
</tr>
... (description field omitted) ...
<tr class="priority_subject_row">
<th class="labelCol"><label class="required" for="priority-subject:763">priority</label></th>
<td>
<select id="priority-subject:763" name="priority-subject:763" size="1" tabindex="4">
<option value="important">important</option>
<option selected="selected" value="normal">normal</option>
<option value="minor">minor</option>
</select>
<div class="helper">importance</div>
</td>
</tr>
... (type field omitted) ...
<tr class="concerns_subject_row">
<th class="labelCol"><label class="required" for="concerns-subject:763">concerns</label></th>
<td>
<select id="concerns-subject:763" name="concerns-subject:763" size="1" tabindex="6">
<option selected="selected" value="760">Foo</option>
</select>
</td>
</tr>
<tr class="done_in_subject_row">
<th class="labelCol"><label for="done_in-subject:763">done in</label></th>
<td>
<select id="done_in-subject:763" name="done_in-subject:763" size="1" tabindex="7">
<option value="__cubicweb_internal_field__"></option>
<option selected="selected" value="761">Foo 0.1.0</option>
<option value="762">Foo 0.2.0</option>
</select>
<div class="helper">version in which this ticket will be / has been done</div>
</td>
</tr>
</table>
</fieldset>
Note that the whole form layout has been computed by the form renderer. It is the renderer which produces the table structure. Otherwise, the fields html structure is emitted by their associated widget.
While it is called the attributes section of the form, it actually contains attributes and mandatory relations. For each field, we observe:
<fieldset class="This ticket :">
<legend>This ticket :</legend>
<table class="attributeForm">
<tr class="_cw_generic_field_None_row">
<td colspan="2">
<table id="relatedEntities">
<tr><th> </th><td> </td></tr>
<tr id="relationSelectorRow_763" class="separator">
<th class="labelCol">
<select id="relationSelector_763" tabindex="8"
onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,763);">
<option value="">select a relation</option>
<option value="appeared_in_subject">appeared in</option>
<option value="custom_workflow_subject">custom workflow</option>
<option value="depends_on_object">dependency of</option>
<option value="depends_on_subject">depends on</option>
<option value="identical_to_subject">identical to</option>
<option value="see_also_subject">see also</option>
</select>
</th>
<td id="unrelatedDivs_763"></td>
</tr>
</table>
</td>
</tr>
</table>
</fieldset>
The optional relations are grouped into a drop-down combo box. Selection of an item triggers a javascript function which will:
Finally comes the buttons zone.
<table width="100%">
<tbody>
<tr>
<td align="center">
<button class="validateButton" tabindex="9" type="submit" value="validate">
<img alt="OK_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/ok.png" />
validate
</button>
</td>
<td style="align: right; width: 50%;">
<button class="validateButton"
onclick="postForm('__action_apply', 'button_apply', 'entityForm')"
tabindex="10" type="button" value="apply">
<img alt="APPLY_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/plus.png" />
apply
</button>
<button class="validateButton"
onclick="postForm('__action_cancel', 'button_cancel', 'entityForm')"
tabindex="11" type="button" value="cancel">
<img alt="CANCEL_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/cancel.png" />
cancel
</button>
</td>
</tr>
</tbody>
</table>
The most notable artifacts here are the postForm(...) calls defined on click events on these buttons. This function basically submits the form.
After the (html) document is loaded, the setFormsTarget javascript function dynamically transforms the DOM as follows. For all forms of the DOM, it:
Let us have a look again at the form element. We have omitted some irrelevant attributes.
On form submission, the form.action is invoked. Basically, the validateform controller is called and its output lands in the specified target, the iframe that was previously prepared.
Hence, the main page is not replaced, only the iframe contents. The validateform controller only outputs a tiny javascript fragment which is then immediately executed.
<iframe width="0px" height="0px" name="eformframe" id="eformframe" src="javascript: void(0)">
<script type="text/javascript">
window.parent.handleFormValidationResponse('entityForm', null, null,
[false, [2164, {"name-subject": "required field"}], null],
null);
</script>
</iframe>
The window.parent part ensures the javascript function is called on the right context (that is: the form element). We will describe its parameters:
Given the array structure described above, it is quite simple to manipulate the DOM to show the errors at appropriate places.
This mecanism may seem a bit overcomplicated but we have to deal with two realities: