[IMP] website_blog: add breadcrumb; [IMP] clean links
[odoo/odoo.git] / addons / web / doc / module.rst
1 .. _module:
2
3 .. queue:: module/series
4
5 Building an OpenERP Web module
6 ==============================
7
8 There is no significant distinction between an OpenERP Web module and
9 an OpenERP module, the web part is mostly additional data and code
10 inside a regular OpenERP module. This allows providing more seamless
11 features by integrating your module deeper into the web client.
12
13 A Basic Module
14 --------------
15
16 A very basic OpenERP module structure will be our starting point:
17
18 .. code-block:: text
19
20     web_example
21     ├── __init__.py
22     └── __openerp__.py
23
24 .. patch::
25
26 This is a sufficient minimal declaration of a valid OpenERP module.
27
28 Web Declaration
29 ---------------
30
31 There is no such thing as a "web module" declaration. An OpenERP
32 module is automatically recognized as "web-enabled" if it contains a
33 ``static`` directory at its root, so:
34
35 .. code-block:: text
36
37     web_example
38     ├── __init__.py
39     ├── __openerp__.py
40     └── static
41
42 is the extent of it. You should also change the dependency to list
43 ``web``:
44
45 .. patch::
46
47 .. note::
48
49     This does not matter in normal operation so you may not realize
50     it's wrong (the web module does the loading of everything else, so
51     it can only be loaded), but when e.g. testing the loading process
52     is slightly different than normal, and incorrect dependency may
53     lead to broken code.
54
55 This makes the "web" discovery system consider the module as having a
56 "web part", and check if it has web controllers to mount or javascript
57 files to load. The content of the ``static/`` folder is also
58 automatically made available to web browser at the URL
59 ``$module-name/static/$file-path``. This is sufficient to provide
60 pictures (of cats, usually) through your module. However there are
61 still a few more steps to running javascript code.
62
63 Getting Things Done
64 -------------------
65
66 The first one is to add javascript code. It's customary to put it in
67 ``static/src/js``, to have room for e.g. other file types, or
68 third-party libraries.
69
70 .. patch::
71
72 The client won't load any file unless specified, thus the new file
73 should be listed in the module's manifest file, under a new key ``js``
74 (a list of file names, or glob patterns):
75
76 .. patch::
77
78 At this point, if the module is installed and the client reloaded the
79 message should appear in your browser's development console.
80
81 .. note::
82
83     Because the manifest file has been edited, you will have to
84     restart the OpenERP server itself for it to be taken in account.
85
86     You may also want to open your browser's console *before*
87     reloading, depending on the browser messages printed while the
88     console is closed may not work or may not appear after opening it.
89
90 .. note::
91
92     If the message does not appear, try cleaning your browser's caches
93     and ensure the file is correctly loaded from the server logs or
94     the "resources" tab of your browser's developers tools.
95
96 At this point the code runs, but it runs only once when the module is
97 initialized, and it can't get access to the various APIs of the web
98 client (such as making RPC requests to the server). This is done by
99 providing a `javascript module`_:
100
101 .. patch::
102
103 If you reload the client, you'll see a message in the console exactly
104 as previously. The differences, though invisible at this point, are:
105
106 * All javascript files specified in the manifest (only this one so
107   far) have been fully loaded
108 * An instance of the web client and a namespace inside that instance
109   (with the same name as the module) have been created and are
110   available for use
111
112 The latter point is what the ``instance`` parameter to the function
113 provides: an instance of the OpenERP Web client, with the contents of
114 all the new module's dependencies loaded in and initialized. These are
115 the entry points to the web client's APIs.
116
117 To demonstrate, let's build a simple :doc:`client action
118 <client_action>`: a stopwatch
119
120 First, the action declaration:
121
122 .. patch::
123
124 then set up the :doc:`client action hook <client_action>` to register
125 a function (for now):
126
127 .. patch::
128
129 Updating the module (in order to load the XML description) and
130 re-starting the server should display a new menu *Example Client
131 Action* at the top-level. Opening said menu will make the message
132 appear, as usual, in the browser's console.
133
134 Paint it black
135 --------------
136
137 The next step is to take control of the page itself, rather than just
138 print little messages in the console. This we can do by replacing our
139 client action function by a :doc:`widget`. Our widget will simply use
140 its :js:func:`~openerp.web.Widget.start` to add some content to its
141 DOM:
142
143 .. patch::
144
145 after reloading the client (to update the javascript file), instead of
146 printing to the console the menu item clears the whole screen and
147 displays the specified message in the page.
148
149 Since we've added a class on the widget's :ref:`DOM root
150 <widget-dom_root>` we can now see how to add a stylesheet to a module:
151 first create the stylesheet file:
152
153 .. patch::
154
155 then add a reference to the stylesheet in the module's manifest (which
156 will require restarting the OpenERP Server to see the changes, as
157 usual):
158
159 .. patch::
160
161 the text displayed by the menu item should now be huge, and
162 white-on-black (instead of small and black-on-white). From there on,
163 the world's your canvas.
164
165 .. note::
166
167     Prefixing CSS rules with both ``.openerp`` (to ensure the rule
168     will apply only within the confines of the OpenERP Web client) and
169     a class at the root of your own hierarchy of widgets is strongly
170     recommended to avoid "leaking" styles in case the code is running
171     embedded in an other web page, and does not have the whole screen
172     to itself.
173
174 So far we haven't built much (any, really) DOM content. It could all
175 be done in :js:func:`~openerp.web.Widget.start` but that gets unwieldy
176 and hard to maintain fast. It is also very difficult to extend by
177 third parties (trying to add or change things in your widgets) unless
178 broken up into multiple methods which each perform a little bit of the
179 rendering.
180
181 The first way to handle this method is to delegate the content to
182 plenty of sub-widgets, which can be individually overridden. An other
183 method [#DOM-building]_ is to use `a template
184 <http://en.wikipedia.org/wiki/Web_template>`_ to render a widget's
185 DOM.
186
187 OpenERP Web's template language is :doc:`qweb`. Although any
188 templating engine can be used (e.g. `mustache
189 <http://mustache.github.com/>`_ or `_.template
190 <http://underscorejs.org/#template>`_) QWeb has important features
191 which other template engines may not provide, and has special
192 integration to OpenERP Web widgets.
193
194 Adding a template file is similar to adding a style sheet:
195
196 .. patch::
197
198 The template can then easily be hooked in the widget:
199
200 .. patch::
201
202 And finally the CSS can be altered to style the new (and more complex)
203 template-generated DOM, rather than the code-generated one:
204
205 .. patch::
206
207 .. note::
208
209     The last section of the CSS change is an example of "state
210     classes": a CSS class (or set of classes) on the root of the
211     widget, which is toggled when the state of the widget changes and
212     can perform drastic alterations in rendering (usually
213     showing/hiding various elements).
214
215     This pattern is both fairly simple (to read and understand) and
216     efficient (because most of the hard work is pushed to the
217     browser's CSS engine, which is usually highly optimized, and done
218     in a single repaint after toggling the class).
219
220 The last step (until the next one) is to add some behavior and make
221 our stopwatch watch. First hook some events on the buttons to toggle
222 the widget's state:
223
224 .. patch::
225
226 This demonstrates the use of the "events hash" and event delegation to
227 declaratively handle events on the widget's DOM. And already changes
228 the button displayed in the UI. Then comes some actual logic:
229
230 .. patch::
231
232 * An initializer (the ``init`` method) is introduced to set-up a few
233   internal variables: ``_start`` will hold the start of the timer (as
234   a javascript Date object), and ``_watch`` will hold a ticker to
235   update the interface regularly and display the "current time".
236
237 * ``update_counter`` is in charge of taking the time difference
238   between "now" and ``_start``, formatting as ``HH:MM:SS`` and
239   displaying the result on screen.
240
241 * ``watch_start`` is augmented to initialize ``_start`` with its value
242   and set-up the update of the counter display every 33ms.
243
244 * ``watch_stop`` disables the updater, does a final update of the
245   counter display and resets everything.
246
247 * Finally, because javascript Interval and Timeout objects execute
248   "outside" the widget, they will keep going even after the widget has
249   been destroyed (especially an issue with intervals as they repeat
250   indefinitely). So ``_watch`` *must* be cleared when the widget is
251   destroyed (then the ``_super`` must be called as well in order to
252   perform the "normal" widget cleanup).
253
254 Starting and stopping the watch now works, and correctly tracks time
255 since having started the watch, neatly formatted.
256
257 Burning through the skies
258 -------------------------
259
260 All work so far has been "local" outside of the original impetus
261 provided by the client action: the widget is self-contained and, once
262 started, does not communicate with anything outside itself. Not only
263 that, but it has no persistence: if the user leaves the stopwatch
264 screen (to go and see his inbox, or do some well-deserved accounting,
265 for instance) whatever was being timed will be lost.
266
267 To prevent this irremediable loss, we can use OpenERP's support for
268 storing data as a model, allowing so that we don't lose our data and
269 can later retrieve, query and manipulate it. First let's create a
270 basic OpenERP model in which our data will be stored:
271
272 .. patch::
273
274 then let's add saving times to the database every time the stopwatch
275 is stopped, using :js:class:`the "high-level" Model API
276 <openerp.web.Model.call>`:
277
278 .. patch::
279
280 A look at the "Network" tab of your preferred browser's developer
281 tools while playing with the stopwatch will show that the save
282 (creation) request is indeed sent (and replied to, even though we're
283 ignoring the response at this point).
284
285 These saved data should now be loaded and displayed when first opening
286 the action, so the user can see his previously recorded times. This is
287 done by overloading the model's ``start`` method: the purpose of
288 :js:func:`~openerp.base.Widget.start()` is to perform *asynchronous*
289 initialization steps, so the rest of the web client knows to "wait"
290 and gets a readiness signal. In this case, it will fetch the data
291 recorded previously using the :js:class:`~openerp.web.Query` interface
292 and add this data to an ordered list added to the widget's template:
293
294 .. patch::
295
296 And for consistency's sake (so that the display a user leaves is
297 pretty much the same as the one he comes back to), newly created
298 records should also automatically be added to the list:
299
300 .. patch::
301
302 Note that we're only displaying the record once we know it's been
303 saved from the database (the ``create`` call has returned without
304 error).
305
306 Mic check, is this working?
307 ---------------------------
308
309 So far, features have been implemented, code has been worked and
310 tentatively tried. However, there is no guarantee they will *keep
311 working* as new changes are performed, new features added, …
312
313 The original author (you, dear reader) could keep a notebook with a
314 list of workflows to check, to ensure everything keeps working. And
315 follow the notebook day after day, every time something is changed in
316 the module.
317
318 That gets repetitive after a while. And computers are good at doing
319 repetitive stuff, as long as you tell them how to do it.
320
321 So let's add test to the module, so that in the future the computer
322 can take care of ensuring what works today keeps working tomorrow.
323
324 .. note::
325
326     Here we're writing tests after having implemented the widget. This
327     may or may not work, we may need to alter bits and pieces of code
328     to get them in a testable state. An other testing methodology is
329     :abbr:`TDD (Test-Driven Development)` where the tests are written
330     first, and the code necessary to make these tests pass is written
331     afterwards.
332
333     Both methods have their opponents and detractors, advantages and
334     inconvenients. Pick the one you prefer.
335
336 The first step of :doc:`testing` is to set up the basic testing
337 structure:
338
339 1. Creating a javascript file
340
341    .. patch::
342
343 2. Containing a test section (and a few tests to make sure the tests
344    are correctly run)
345
346    .. patch::
347
348 3. Then declaring the test file in the module's manifest
349
350    .. patch::
351
352 4. And finally — after restarting OpenERP — navigating to the test
353    runner at ``/web/tests`` and selecting your soon-to-be-tested
354    module:
355
356    .. image:: module/testing_0.png
357        :align: center
358
359    the testing result do indeed match the test.
360
361 The simplest tests to write are for synchronous pure
362 functions. Synchronous means no RPC call or any other such thing
363 (e.g. ``setTimeout``), only direct data processing, and pure means no
364 side-effect: the function takes some input, manipulates it and yields
365 an output.
366
367 In our widget, only ``format_time`` fits the bill: it takes a duration
368 (in milliseconds) and returns an ``hours:minutes:second`` formatting
369 of it. Let's test it:
370
371 .. patch::
372
373 This series of simple tests passes with no issue. The next easy-ish
374 test type is to test basic DOM alterations from provided input, such
375 as (for our widget) updating the counter or displaying a record to the
376 records list: while it's not pure (it alters the DOM "in-place") it
377 has well-delimited side-effects and these side-effects come solely
378 from the provided input.
379
380 Because these methods alter the widget's DOM, the widget needs a
381 DOM. Looking up :doc:`a widget's lifecycle <widget>`, the widget
382 really only gets its DOM when adding it to the document. However a
383 side-effect of this is to :js:func:`~openerp.web.Widget.start` it,
384 which for us means going to query the user's times.
385
386 We don't have any records to get in our test, and we don't want to
387 test the initialization yet! So let's cheat a bit: we can manually
388 :js:func:`set a widget's DOM <openerp.web.Widget.setElement>`, let's
389 create a basic DOM matching what each method expects then call the
390 method:
391
392 .. patch::
393
394 The next group of patches (in terms of setup/complexity) is RPC tests:
395 testing components/methods which perform network calls (RPC
396 requests). In our module, ``start`` and ``watch_stop`` are in that
397 case: ``start`` fetches the user's recorded times and ``watch_stop``
398 creates a new record with the current watch.
399
400 By default, tests don't allow RPC requests and will generate an error
401 when trying to perform one:
402
403 .. image:: module/testing_1.png
404     :align: center
405
406 To allow them, the test case (or the test suite) has to explicitly opt
407 into :js:attr:`rpc support <TestOptions.rpc>` by adding the ``rpc:
408 'mock'`` option to the test case, and providing its own "rpc
409 responses":
410
411 .. patch::
412
413 .. note::
414
415     By defaut, tests cases don't load templates either. We had not
416     needed to perform any template rendering before here, so we must
417     now enable templates loading via :js:attr:`the corresponding
418     option <TestOptions.templates>`.
419
420 Our final test requires altering the module's code: asynchronous tests
421 use :doc:`deferred </async>` to know when a test ends and the other
422 one can start (otherwise test content will execute non-linearly and
423 the assertions of a test will be executed during the next test or
424 worse), but although ``watch_stop`` performs an asynchronous
425 ``create`` operation it doesn't return a deferred we can synchronize
426 on. We simply need to return its result:
427
428 .. patch::
429
430 This makes no difference to the original code, but allows us to write
431 our test:
432
433 .. patch::
434
435 .. [#DOM-building] they are not alternative solutions: they work very
436                    well together. Templates are used to build "just
437                    DOM", sub-widgets are used to build DOM subsections
438                    *and* delegate part of the behavior (e.g. events
439                    handling).
440
441 .. _javascript module:
442     http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript