3 Building an OpenERP Web module
4 ==============================
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.
14 A very basic OpenERP module structure will be our starting point:
22 .. literalinclude:: module/__openerp__.py
25 This is a sufficient minimal declaration of a valid OpenERP module.
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:
41 is the extent of it. You should also change the dependency to list
44 .. literalinclude:: module/__openerp__.py.1.diff
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
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.
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.
70 .. literalinclude:: module/static/src/js/first_module.js
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):
77 .. literalinclude:: module/__openerp__.py.2.diff
80 At this point, if the module is installed and the client reloaded the
81 message should appear in your browser's development console.
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.
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.
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.
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`_:
103 .. literalinclude:: module/static/src/js/first_module.js.1.diff
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:
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
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.
120 To demonstrate, let's build a simple :doc:`client action
121 <client_action>`: a stopwatch
123 First, the action declaration:
125 .. literalinclude:: module/__openerp__.py.3.diff
128 .. literalinclude:: module/web_example.xml
131 then set up the :doc:`client action hook <client_action>` to register
132 a function (for now):
134 .. literalinclude:: module/static/src/js/first_module.js.2.diff
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.
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
151 .. literalinclude:: module/static/src/js/first_module.js.3.diff
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.
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:
162 .. literalinclude:: module/static/src/css/web_example.css
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
169 .. literalinclude:: module/__openerp__.py.4.diff
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.
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
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
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
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.
205 Adding a template file is similar to adding a style sheet:
207 .. literalinclude:: module/static/src/xml/web_example.xml
210 .. literalinclude:: module/__openerp__.py.5.diff
213 The template can then easily be hooked in the widget:
215 .. literalinclude:: module/static/src/js/first_module.js.4.diff
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:
221 .. literalinclude:: module/static/src/css/web_example.css.1.diff
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).
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).
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
241 .. literalinclude:: module/static/src/js/first_module.js.5.diff
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:
248 .. literalinclude:: module/static/src/js/first_module.js.6.diff
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".
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.
260 * ``watch_start`` is augmented to initialize ``_start`` with its value
261 and set-up the update of the counter display every 33ms.
263 * ``watch_stop`` disables the updater, does a final update of the
264 counter display and resets everything.
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).
273 Starting and stopping the watch now works, and correctly tracks time
274 since having started the watch, neatly formatted.
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
282 .. _javascript module:
283 http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript