|
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('entityForm');" |
|
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> </th><td> </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('__action_apply', 'button_apply', 'entityForm')" |
|
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('__action_cancel', 'button_cancel', 'entityForm')" |
|
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. |