Launchpad automatic translations update.
[odoo/odoo.git] / addons / web / doc / module.rst
1 .. _module:
2
3 Building an OpenERP Web module
4 ==============================
5
6 There is no significant distinction between an OpenERP Web module and
7 an OpenERP module, the web part is mostly additional data and code
8 inside a regular OpenERP module. This allows providing more seamless
9 features by integrating your module deeper into the web client.
10
11 A Basic Module
12 --------------
13
14 A very basic OpenERP module structure will be our starting point:
15
16 .. code-block:: text
17
18     web_example
19     ├── __init__.py
20     └── __openerp__.py
21
22 .. literalinclude:: module/__openerp__.py
23     :language: python
24
25 This is a sufficient minimal declaration of a valid OpenERP module.
26
27 Web Declaration
28 ---------------
29
30 There is no such thing as a "web module" declaration. An OpenERP
31 module is automatically recognized as "web-enabled" if it contains a
32 ``static`` directory at its root, so:
33
34 .. code-block:: text
35
36     web_example
37     ├── __init__.py
38     ├── __openerp__.py
39     └── static
40
41 is the extent of it. You should also change the dependency to list
42 ``web``:
43
44 .. literalinclude:: module/__openerp__.py.1.diff
45     :language: diff
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 .. literalinclude:: module/static/src/js/first_module.js
71     :language: javascript
72
73 The client won't load any file unless specified, thus the new file
74 should be listed in the module's manifest file, under a new key ``js``
75 (a list of file names, or glob patterns):
76
77 .. literalinclude:: module/__openerp__.py.2.diff
78     :language: diff
79
80 At this point, if the module is installed and the client reloaded the
81 message should appear in your browser's development console.
82
83 .. note::
84
85     Because the manifest file has been edited, you will have to
86     restart the OpenERP server itself for it to be taken in account.
87
88     You may also want to open your browser's console *before*
89     reloading, depending on the browser messages printed while the
90     console is closed may not work or may not appear after opening it.
91
92 .. note::
93
94     If the message does not appear, try cleaning your browser's caches
95     and ensure the file is correctly loaded from the server logs or
96     the "resources" tab of your browser's developers tools.
97
98 At this point the code runs, but it runs only once when the module is
99 initialized, and it can't get access to the various APIs of the web
100 client (such as making RPC requests to the server). This is done by
101 providing a `javascript module`_:
102
103 .. literalinclude:: module/static/src/js/first_module.js.1.diff
104     :language: diff
105
106 If you reload the client, you'll see a message in the console exactly
107 as previously. The differences, though invisible at this point, are:
108
109 * All javascript files specified in the manifest (only this one so
110   far) have been fully loaded
111 * An instance of the web client and a namespace inside that instance
112   (with the same name as the module) have been created and are
113   available for use
114
115 The latter point is what the ``instance`` parameter to the function
116 provides: an instance of the OpenERP Web client, with the contents of
117 all the new module's dependencies loaded in and initialized. These are
118 the entry points to the web client's APIs.
119
120 To demonstrate, let's build a simple :doc:`client action
121 <client_action>`: a stopwatch
122
123 First, the action declaration:
124
125 .. literalinclude:: module/__openerp__.py.3.diff
126     :language: diff
127
128 .. literalinclude:: module/web_example.xml
129     :language: xml
130
131 then set up the :doc:`client action hook <client_action>` to register
132 a function (for now):
133
134 .. literalinclude:: module/static/src/js/first_module.js.2.diff
135     :language: diff
136
137 Updating the module (in order to load the XML description) and
138 re-starting the server should display a new menu *Example Client
139 Action* at the top-level. Opening said menu will make the message
140 appear, as usual, in the browser's console.
141
142 Paint it black
143 --------------
144
145 The next step is to take control of the page itself, rather than just
146 print little messages in the console. This we can do by replacing our
147 client action function by a :doc:`widget`. Our widget will simply use
148 its :js:func:`~openerp.web.Widget.start` to add some content to its
149 DOM:
150
151 .. literalinclude:: module/static/src/js/first_module.js.3.diff
152     :language: diff
153
154 after reloading the client (to update the javascript file), instead of
155 printing to the console the menu item clears the whole screen and
156 displays the specified message in the page.
157
158 Since we've added a class on the widget's :ref:`DOM root
159 <widget-dom_root>` we can now see how to add a stylesheet to a module:
160 first create the stylesheet file:
161
162 .. literalinclude:: module/static/src/css/web_example.css
163     :language: css
164
165 then add a reference to the stylesheet in the module's manifest (which
166 will require restarting the OpenERP Server to see the changes, as
167 usual):
168
169 .. literalinclude:: module/__openerp__.py.4.diff
170     :language: diff
171
172 the text displayed by the menu item should now be huge, and
173 white-on-black (instead of small and black-on-white). From there on,
174 the world's your canvas.
175
176 .. note::
177
178     Prefixing CSS rules with both ``.openerp`` (to ensure the rule
179     will apply only within the confines of the OpenERP Web client) and
180     a class at the root of your own hierarchy of widgets is strongly
181     recommended to avoid "leaking" styles in case the code is running
182     embedded in an other web page, and does not have the whole screen
183     to itself.
184
185 So far we haven't built much (any, really) DOM content. It could all
186 be done in :js:func:`~openerp.web.Widget.start` but that gets unwieldy
187 and hard to maintain fast. It is also very difficult to extend by
188 third parties (trying to add or change things in your widgets) unless
189 broken up into multiple methods which each perform a little bit of the
190 rendering.
191
192 The first way to handle this method is to delegate the content to
193 plenty of sub-widgets, which can be individually overridden. An other
194 method [#DOM-building]_ is to use `a template
195 <http://en.wikipedia.org/wiki/Web_template>`_ to render a widget's
196 DOM.
197
198 OpenERP Web's template language is :doc:`qweb`. Although any
199 templating engine can be used (e.g. `mustache
200 <http://mustache.github.com/>`_ or `_.template
201 <http://underscorejs.org/#template>`_) QWeb has important features
202 which other template engines may not provide, and has special
203 integration to OpenERP Web widgets.
204
205 Adding a template file is similar to adding a style sheet:
206
207 .. literalinclude:: module/static/src/xml/web_example.xml
208     :language: xml
209
210 .. literalinclude:: module/__openerp__.py.5.diff
211     :language: diff
212
213 The template can then easily be hooked in the widget:
214
215 .. literalinclude:: module/static/src/js/first_module.js.4.diff
216     :language: diff
217
218 And finally the CSS can be altered to style the new (and more complex)
219 template-generated DOM, rather than the code-generated one:
220
221 .. literalinclude:: module/static/src/css/web_example.css.1.diff
222     :language: diff
223
224 .. note::
225
226     The last section of the CSS change is an example of "state
227     classes": a CSS class (or set of classes) on the root of the
228     widget, which is toggled when the state of the widget changes and
229     can perform drastic alterations in rendering (usually
230     showing/hiding various elements).
231
232     This pattern is both fairly simple (to read and understand) and
233     efficient (because most of the hard work is pushed to the
234     browser's CSS engine, which is usually highly optimized, and done
235     in a single repaint after toggling the class).
236
237 The last step (until the next one) is to add some behavior and make
238 our stopwatch watch. First hook some events on the buttons to toggle
239 the widget's state:
240
241 .. literalinclude:: module/static/src/js/first_module.js.5.diff
242     :language: diff
243
244 This demonstrates the use of the "events hash" and event delegation to
245 declaratively handle events on the widget's DOM. And already changes
246 the button displayed in the UI. Then comes some actual logic:
247
248 .. literalinclude:: module/static/src/js/first_module.js.6.diff
249     :language: diff
250
251 * An initializer (the ``init`` method) is introduced to set-up a few
252   internal variables: ``_start`` will hold the start of the timer (as
253   a javascript Date object), and ``_watch`` will hold a ticker to
254   update the interface regularly and display the "current time".
255
256 * ``update_counter`` is in charge of taking the time difference
257   between "now" and ``_start``, formatting as ``HH:MM:SS`` and
258   displaying the result on screen.
259
260 * ``watch_start`` is augmented to initialize ``_start`` with its value
261   and set-up the update of the counter display every 33ms.
262
263 * ``watch_stop`` disables the updater, does a final update of the
264   counter display and resets everything.
265
266 * Finally, because javascript Interval and Timeout objects execute
267   "outside" the widget, they will keep going even after the widget has
268   been destroyed (especially an issue with intervals as they repeat
269   indefinitely). So ``_watch`` *must* be cleared when the widget is
270   destroyed (then the ``_super`` must be called as well in order to
271   perform the "normal" widget cleanup).
272
273 Starting and stopping the watch now works, and correctly tracks time
274 since having started the watch, neatly formatted.
275
276 .. [#DOM-building] they are not alternative solutions: they work very
277                    well together. Templates are used to build "just
278                    DOM", sub-widgets are used to build DOM subsections
279                    *and* delegate part of the behavior (e.g. events
280                    handling).
281
282 .. _javascript module:
283     http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript