Merge lp:~openerp-dev/openerp-web/7.0-module-tutorial-xmo into lp:openerp-web/7.0

Proposed by Xavier (Open ERP)
Status: Rejected
Rejected by: Xavier (Open ERP)
Proposed branch: lp:~openerp-dev/openerp-web/7.0-module-tutorial-xmo
Merge into: lp:openerp-web/7.0
Diff against target: 1537 lines (+882/-283)
49 files modified
addons/web/doc/conf.py (+5/-1)
addons/web/doc/module.rst (+195/-36)
addons/web/doc/module/0 (+17/-0)
addons/web/doc/module/10 (+13/-0)
addons/web/doc/module/11 (+11/-0)
addons/web/doc/module/12 (+28/-0)
addons/web/doc/module/14 (+17/-0)
addons/web/doc/module/15 (+19/-0)
addons/web/doc/module/16 (+25/-0)
addons/web/doc/module/17 (+52/-0)
addons/web/doc/module/18 (+19/-0)
addons/web/doc/module/19 (+52/-0)
addons/web/doc/module/2 (+12/-0)
addons/web/doc/module/20 (+64/-0)
addons/web/doc/module/21 (+27/-0)
addons/web/doc/module/22 (+6/-0)
addons/web/doc/module/23 (+14/-0)
addons/web/doc/module/24 (+10/-0)
addons/web/doc/module/25 (+55/-0)
addons/web/doc/module/26 (+38/-0)
addons/web/doc/module/27 (+28/-0)
addons/web/doc/module/28 (+13/-0)
addons/web/doc/module/29 (+37/-0)
addons/web/doc/module/3 (+9/-0)
addons/web/doc/module/4 (+11/-0)
addons/web/doc/module/5 (+11/-0)
addons/web/doc/module/6 (+29/-0)
addons/web/doc/module/8 (+14/-0)
addons/web/doc/module/9 (+21/-0)
addons/web/doc/module/__openerp__.py (+0/-7)
addons/web/doc/module/__openerp__.py.1.diff (+0/-11)
addons/web/doc/module/__openerp__.py.2.diff (+0/-11)
addons/web/doc/module/__openerp__.py.3.diff (+0/-12)
addons/web/doc/module/__openerp__.py.4.diff (+0/-13)
addons/web/doc/module/__openerp__.py.5.diff (+0/-14)
addons/web/doc/module/series (+27/-0)
addons/web/doc/module/static/src/css/web_example.css (+0/-6)
addons/web/doc/module/static/src/css/web_example.css.1.diff (+0/-17)
addons/web/doc/module/static/src/js/first_module.js (+0/-2)
addons/web/doc/module/static/src/js/first_module.js.1.diff (+0/-8)
addons/web/doc/module/static/src/js/first_module.js.2.diff (+0/-11)
addons/web/doc/module/static/src/js/first_module.js.3.diff (+0/-18)
addons/web/doc/module/static/src/js/first_module.js.4.diff (+0/-15)
addons/web/doc/module/static/src/js/first_module.js.5.diff (+0/-23)
addons/web/doc/module/static/src/js/first_module.js.6.diff (+0/-55)
addons/web/doc/module/static/src/xml/web_example.xml (+0/-11)
addons/web/doc/module/web_example.xml (+0/-11)
addons/web/doc/testing.rst (+2/-0)
addons/web/doc/widget.rst (+1/-1)
To merge this branch: bzr merge lp:~openerp-dev/openerp-web/7.0-module-tutorial-xmo
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+148709@code.launchpad.net
To post a comment you must log in.
3739. By Xavier (Open ERP)

[WIP] testing in module guide

3740. By Xavier (Open ERP)

[IMP] final section of the module guide

Revision history for this message
Xavier (Open ERP) (xmo-deactivatedaccount) wrote :

was merged into trunk, so cancelling this proposal

Unmerged revisions

3740. By Xavier (Open ERP)

[IMP] final section of the module guide

3739. By Xavier (Open ERP)

[WIP] testing in module guide

3738. By Xavier (Open ERP)

[ADD] sync-on-start tutoring

3737. By Xavier (Open ERP)

[IMP] replace ad-hoc patch display by patchqueue

3736. By Xavier (Open ERP)

[IMP] web module tutorial: start adding RPC stuff

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'addons/web/doc/conf.py'
2--- addons/web/doc/conf.py 2013-01-17 09:27:22 +0000
3+++ addons/web/doc/conf.py 2013-02-18 15:08:21 +0000
4@@ -27,7 +27,11 @@
5
6 # Add any Sphinx extension module names here, as strings. They can be extensions
7 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
8-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode']
9+extensions = [
10+ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
11+ 'sphinx.ext.todo', 'sphinx.ext.viewcode',
12+ 'patchqueue'
13+]
14
15 # Add any paths that contain templates here, relative to this directory.
16 templates_path = ['_templates']
17
18=== modified file 'addons/web/doc/module.rst'
19--- addons/web/doc/module.rst 2013-01-17 09:27:22 +0000
20+++ addons/web/doc/module.rst 2013-02-18 15:08:21 +0000
21@@ -1,5 +1,7 @@
22 .. _module:
23
24+.. queue:: module/series
25+
26 Building an OpenERP Web module
27 ==============================
28
29@@ -19,8 +21,7 @@
30 ├── __init__.py
31 └── __openerp__.py
32
33-.. literalinclude:: module/__openerp__.py
34- :language: python
35+.. patch::
36
37 This is a sufficient minimal declaration of a valid OpenERP module.
38
39@@ -41,8 +42,7 @@
40 is the extent of it. You should also change the dependency to list
41 ``web``:
42
43-.. literalinclude:: module/__openerp__.py.1.diff
44- :language: diff
45+.. patch::
46
47 .. note::
48
49@@ -67,15 +67,13 @@
50 ``static/src/js``, to have room for e.g. other file types, or
51 third-party libraries.
52
53-.. literalinclude:: module/static/src/js/first_module.js
54- :language: javascript
55+.. patch::
56
57 The client won't load any file unless specified, thus the new file
58 should be listed in the module's manifest file, under a new key ``js``
59 (a list of file names, or glob patterns):
60
61-.. literalinclude:: module/__openerp__.py.2.diff
62- :language: diff
63+.. patch::
64
65 At this point, if the module is installed and the client reloaded the
66 message should appear in your browser's development console.
67@@ -100,8 +98,7 @@
68 client (such as making RPC requests to the server). This is done by
69 providing a `javascript module`_:
70
71-.. literalinclude:: module/static/src/js/first_module.js.1.diff
72- :language: diff
73+.. patch::
74
75 If you reload the client, you'll see a message in the console exactly
76 as previously. The differences, though invisible at this point, are:
77@@ -122,17 +119,12 @@
78
79 First, the action declaration:
80
81-.. literalinclude:: module/__openerp__.py.3.diff
82- :language: diff
83-
84-.. literalinclude:: module/web_example.xml
85- :language: xml
86+.. patch::
87
88 then set up the :doc:`client action hook <client_action>` to register
89 a function (for now):
90
91-.. literalinclude:: module/static/src/js/first_module.js.2.diff
92- :language: diff
93+.. patch::
94
95 Updating the module (in order to load the XML description) and
96 re-starting the server should display a new menu *Example Client
97@@ -148,8 +140,7 @@
98 its :js:func:`~openerp.web.Widget.start` to add some content to its
99 DOM:
100
101-.. literalinclude:: module/static/src/js/first_module.js.3.diff
102- :language: diff
103+.. patch::
104
105 after reloading the client (to update the javascript file), instead of
106 printing to the console the menu item clears the whole screen and
107@@ -159,15 +150,13 @@
108 <widget-dom_root>` we can now see how to add a stylesheet to a module:
109 first create the stylesheet file:
110
111-.. literalinclude:: module/static/src/css/web_example.css
112- :language: css
113+.. patch::
114
115 then add a reference to the stylesheet in the module's manifest (which
116 will require restarting the OpenERP Server to see the changes, as
117 usual):
118
119-.. literalinclude:: module/__openerp__.py.4.diff
120- :language: diff
121+.. patch::
122
123 the text displayed by the menu item should now be huge, and
124 white-on-black (instead of small and black-on-white). From there on,
125@@ -204,22 +193,16 @@
126
127 Adding a template file is similar to adding a style sheet:
128
129-.. literalinclude:: module/static/src/xml/web_example.xml
130- :language: xml
131-
132-.. literalinclude:: module/__openerp__.py.5.diff
133- :language: diff
134+.. patch::
135
136 The template can then easily be hooked in the widget:
137
138-.. literalinclude:: module/static/src/js/first_module.js.4.diff
139- :language: diff
140+.. patch::
141
142 And finally the CSS can be altered to style the new (and more complex)
143 template-generated DOM, rather than the code-generated one:
144
145-.. literalinclude:: module/static/src/css/web_example.css.1.diff
146- :language: diff
147+.. patch::
148
149 .. note::
150
151@@ -238,15 +221,13 @@
152 our stopwatch watch. First hook some events on the buttons to toggle
153 the widget's state:
154
155-.. literalinclude:: module/static/src/js/first_module.js.5.diff
156- :language: diff
157+.. patch::
158
159 This demonstrates the use of the "events hash" and event delegation to
160 declaratively handle events on the widget's DOM. And already changes
161 the button displayed in the UI. Then comes some actual logic:
162
163-.. literalinclude:: module/static/src/js/first_module.js.6.diff
164- :language: diff
165+.. patch::
166
167 * An initializer (the ``init`` method) is introduced to set-up a few
168 internal variables: ``_start`` will hold the start of the timer (as
169@@ -273,6 +254,184 @@
170 Starting and stopping the watch now works, and correctly tracks time
171 since having started the watch, neatly formatted.
172
173+Burning through the skies
174+-------------------------
175+
176+All work so far has been "local" outside of the original impetus
177+provided by the client action: the widget is self-contained and, once
178+started, does not communicate with anything outside itself. Not only
179+that, but it has no persistence: if the user leaves the stopwatch
180+screen (to go and see his inbox, or do some well-deserved accounting,
181+for instance) whatever was being timed will be lost.
182+
183+To prevent this irremediable loss, we can use OpenERP's support for
184+storing data as a model, allowing so that we don't lose our data and
185+can later retrieve, query and manipulate it. First let's create a
186+basic OpenERP model in which our data will be stored:
187+
188+.. patch::
189+
190+then let's add saving times to the database every time the stopwatch
191+is stopped, using :js:class:`the "high-level" Model API
192+<openerp.web.Model.call>`:
193+
194+.. patch::
195+
196+A look at the "Network" tab of your preferred browser's developer
197+tools while playing with the stopwatch will show that the save
198+(creation) request is indeed sent (and replied to, even though we're
199+ignoring the response at this point).
200+
201+These saved data should now be loaded and displayed when first opening
202+the action, so the user can see his previously recorded times. This is
203+done by overloading the model's ``start`` method: the purpose of
204+:js:func:`~openerp.base.Widget.start()` is to perform *asynchronous*
205+initialization steps, so the rest of the web client knows to "wait"
206+and gets a readiness signal. In this case, it will fetch the data
207+recorded previously using the :js:class:`~openerp.web.Query` interface
208+and add this data to an ordered list added to the widget's template:
209+
210+.. patch::
211+
212+And for consistency's sake (so that the display a user leaves is
213+pretty much the same as the one he comes back to), newly created
214+records should also automatically be added to the list:
215+
216+.. patch::
217+
218+Note that we're only displaying the record once we know it's been
219+saved from the database (the ``create`` call has returned without
220+error).
221+
222+Mic check, is this working?
223+---------------------------
224+
225+So far, features have been implemented, code has been worked and
226+tentatively tried. However, there is no guarantee they will *keep
227+working* as new changes are performed, new features added, …
228+
229+The original author (you, dear reader) could keep a notebook with a
230+list of workflows to check, to ensure everything keeps working. And
231+follow the notebook day after day, every time something is changed in
232+the module.
233+
234+That gets repetitive after a while. And computers are good at doing
235+repetitive stuff, as long as you tell them how to do it.
236+
237+So let's add test to the module, so that in the future the computer
238+can take care of ensuring what works today keeps working tomorrow.
239+
240+.. note::
241+
242+ Here we're writing tests after having implemented the widget. This
243+ may or may not work, we may need to alter bits and pieces of code
244+ to get them in a testable state. An other testing methodology is
245+ :abbr:`TDD (Test-Driven Development)` where the tests are written
246+ first, and the code necessary to make these tests pass is written
247+ afterwards.
248+
249+ Both methods have their opponents and detractors, advantages and
250+ inconvenients. Pick the one you prefer.
251+
252+The first step of :doc:`testing` is to set up the basic testing
253+structure:
254+
255+1. Creating a javascript file
256+
257+ .. patch::
258+
259+2. Containing a test section (and a few tests to make sure the tests
260+ are correctly run)
261+
262+ .. patch::
263+
264+3. Then declaring the test file in the module's manifest
265+
266+ .. patch::
267+
268+4. And finally — after restarting OpenERP — navigating to the test
269+ runner at ``/web/tests`` and selecting your soon-to-be-tested
270+ module:
271+
272+ .. image:: module/testing_0.png
273+ :align: center
274+
275+ the testing result do indeed match the test.
276+
277+The simplest tests to write are for synchronous pure
278+functions. Synchronous means no RPC call or any other such thing
279+(e.g. ``setTimeout``), only direct data processing, and pure means no
280+side-effect: the function takes some input, manipulates it and yields
281+an output.
282+
283+In our widget, only ``format_time`` fits the bill: it takes a duration
284+(in milliseconds) and returns an ``hours:minutes:second`` formatting
285+of it. Let's test it:
286+
287+.. patch::
288+
289+This series of simple tests passes with no issue. The next easy-ish
290+test type is to test basic DOM alterations from provided input, such
291+as (for our widget) updating the counter or displaying a record to the
292+records list: while it's not pure (it alters the DOM "in-place") it
293+has well-delimited side-effects and these side-effects come solely
294+from the provided input.
295+
296+Because these methods alter the widget's DOM, the widget needs a
297+DOM. Looking up :doc:`a widget's lifecycle <widget>`, the widget
298+really only gets its DOM when adding it to the document. However a
299+side-effect of this is to :js:func:`~openerp.web.Widget.start` it,
300+which for us means going to query the user's times.
301+
302+We don't have any records to get in our test, and we don't want to
303+test the initialization yet! So let's cheat a bit: we can manually
304+:js:func:`set a widget's DOM <openerp.web.Widget.setElement>`, let's
305+create a basic DOM matching what each method expects then call the
306+method:
307+
308+.. patch::
309+
310+The next group of patches (in terms of setup/complexity) is RPC tests:
311+testing components/methods which perform network calls (RPC
312+requests). In our module, ``start`` and ``watch_stop`` are in that
313+case: ``start`` fetches the user's recorded times and ``watch_stop``
314+creates a new record with the current watch.
315+
316+By default, tests don't allow RPC requests and will generate an error
317+when trying to perform one:
318+
319+.. image:: module/testing_1.png
320+ :align: center
321+
322+To allow them, the test case (or the test suite) has to explicitly opt
323+into :js:attr:`rpc support <TestOptions.rpc>` by adding the ``rpc:
324+'mock'`` option to the test case, and providing its own "rpc
325+responses":
326+
327+.. patch::
328+
329+.. note::
330+
331+ By defaut, tests cases don't load templates either. We had not
332+ needed to perform any template rendering before here, so we must
333+ now enable templates loading via :js:attr:`the corresponding
334+ option <TestOptions.templates>`.
335+
336+Our final test requires altering the module's code: asynchronous tests
337+use :doc:`deferred </async>` to know when a test ends and the other
338+one can start (otherwise test content will execute non-linearly and
339+the assertions of a test will be executed during the next test or
340+worse), but although ``watch_stop`` performs an asynchronous
341+``create`` operation it doesn't return a deferred we can synchronize
342+on. We simply need to return its result:
343+
344+.. patch::
345+
346+This makes no difference to the original code, but allows us to write
347+our test:
348+
349+.. patch::
350+
351 .. [#DOM-building] they are not alternative solutions: they work very
352 well together. Templates are used to build "just
353 DOM", sub-widgets are used to build DOM subsections
354
355=== added file 'addons/web/doc/module/0'
356--- addons/web/doc/module/0 1970-01-01 00:00:00 +0000
357+++ addons/web/doc/module/0 2013-02-18 15:08:21 +0000
358@@ -0,0 +1,17 @@
359+# HG changeset patch
360+# Parent 0000000000000000000000000000000000000000
361+
362+diff --git a/__init__.py b/__init__.py
363+new file mode 100644
364+diff --git a/__openerp__.py b/__openerp__.py
365+new file mode 100644
366+--- /dev/null
367++++ b/__openerp__.py
368+@@ -0,0 +1,7 @@
369++# __openerp__.py
370++{
371++ 'name': "Web Example",
372++ 'description': "Basic example of a (future) web module",
373++ 'category': 'Hidden',
374++ 'depends': ['base'],
375++}
376
377=== added file 'addons/web/doc/module/10'
378--- addons/web/doc/module/10 1970-01-01 00:00:00 +0000
379+++ addons/web/doc/module/10 2013-02-18 15:08:21 +0000
380@@ -0,0 +1,13 @@
381+# HG changeset patch
382+# Parent 72d9d59a93fcee06ba28cf0b98a1075331dcc8f4
383+diff --git a/static/src/css/web_example.css b/static/src/css/web_example.css
384+new file mode 100644
385+--- /dev/null
386++++ b/static/src/css/web_example.css
387+@@ -0,0 +1,6 @@
388++.openerp .oe_web_example {
389++ color: white;
390++ background-color: black;
391++ height: 100%;
392++ font-size: 400%;
393++}
394
395=== added file 'addons/web/doc/module/11'
396--- addons/web/doc/module/11 1970-01-01 00:00:00 +0000
397+++ addons/web/doc/module/11 2013-02-18 15:08:21 +0000
398@@ -0,0 +1,11 @@
399+# HG changeset patch
400+# Parent 3ed382d9a8fe64fbb8e2bf4045e3fcd5c74c92bc
401+diff --git a/__openerp__.py b/__openerp__.py
402+--- a/__openerp__.py
403++++ b/__openerp__.py
404+@@ -6,4 +6,5 @@
405+ 'depends': ['web'],
406+ 'data': ['web_example.xml'],
407+ 'js': ['static/src/js/first_module.js'],
408++ 'css': ['static/src/css/web_example.css'],
409+ }
410
411=== added file 'addons/web/doc/module/12'
412--- addons/web/doc/module/12 1970-01-01 00:00:00 +0000
413+++ addons/web/doc/module/12 2013-02-18 15:08:21 +0000
414@@ -0,0 +1,28 @@
415+# HG changeset patch
416+# Parent 43f21611dacb7c2b2f3810baeeef359ad7c329f0
417+
418+diff --git a/__openerp__.py b/__openerp__.py
419+--- a/__openerp__.py
420++++ b/__openerp__.py
421+@@ -7,4 +7,5 @@
422+ 'data': ['web_example.xml'],
423+ 'js': ['static/src/js/first_module.js'],
424+ 'css': ['static/src/css/web_example.css'],
425++ 'qweb': ['static/src/xml/web_example.xml'],
426+ }
427+diff --git a/static/src/xml/web_example.xml b/static/src/xml/web_example.xml
428+new file mode 100644
429+--- /dev/null
430++++ b/static/src/xml/web_example.xml
431+@@ -0,0 +1,11 @@
432++<templates>
433++<div t-name="web_example.action" class="oe_web_example oe_web_example_stopped">
434++ <h4 class="oe_web_example_timer">00:00:00</h4>
435++ <p class="oe_web_example_start">
436++ <button type="button">Start</button>
437++ </p>
438++ <p class="oe_web_example_stop">
439++ <button type="button">Stop</button>
440++ </p>
441++</div>
442++</templates>
443
444=== added file 'addons/web/doc/module/14'
445--- addons/web/doc/module/14 1970-01-01 00:00:00 +0000
446+++ addons/web/doc/module/14 2013-02-18 15:08:21 +0000
447@@ -0,0 +1,17 @@
448+# HG changeset patch
449+# Parent ae3b427c96b532794a65357b3f075129cc991276
450+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
451+--- a/static/src/js/first_module.js
452++++ b/static/src/js/first_module.js
453+@@ -2,10 +2,6 @@
454+ openerp.web_example = function (instance) {
455+ instance.web.client_actions.add('example.action', 'instance.web_example.Action');
456+ instance.web_example.Action = instance.web.Widget.extend({
457+- className: 'oe_web_example',
458+- start: function () {
459+- this.$el.text("Hello, world!");
460+- return this._super();
461+- }
462++ template: 'web_example.action'
463+ });
464+ };
465
466=== added file 'addons/web/doc/module/15'
467--- addons/web/doc/module/15 1970-01-01 00:00:00 +0000
468+++ addons/web/doc/module/15 2013-02-18 15:08:21 +0000
469@@ -0,0 +1,19 @@
470+# HG changeset patch
471+# Parent e2d2e1a4cc2d2496aebeb05d94768384427c9e8b
472+diff --git a/static/src/css/web_example.css b/static/src/css/web_example.css
473+--- a/static/src/css/web_example.css
474++++ b/static/src/css/web_example.css
475+@@ -2,5 +2,12 @@
476+ color: white;
477+ background-color: black;
478+ height: 100%;
479+- font-size: 400%;
480+ }
481++.openerp .oe_web_example h4 {
482++ margin: 0;
483++ font-size: 200%;
484++}
485++.openerp .oe_web_example.oe_web_example_started .oe_web_example_start button,
486++.openerp .oe_web_example.oe_web_example_stopped .oe_web_example_stop button {
487++ display: none
488++}
489
490=== added file 'addons/web/doc/module/16'
491--- addons/web/doc/module/16 1970-01-01 00:00:00 +0000
492+++ addons/web/doc/module/16 2013-02-18 15:08:21 +0000
493@@ -0,0 +1,25 @@
494+# HG changeset patch
495+# Parent 2645d7a09dcba7f6d6074a33252c16c03c56fdf3
496+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
497+--- a/static/src/js/first_module.js
498++++ b/static/src/js/first_module.js
499+@@ -2,6 +2,18 @@
500+ openerp.web_example = function (instance) {
501+ instance.web.client_actions.add('example.action', 'instance.web_example.Action');
502+ instance.web_example.Action = instance.web.Widget.extend({
503+- template: 'web_example.action'
504++ template: 'web_example.action',
505++ events: {
506++ 'click .oe_web_example_start button': 'watch_start',
507++ 'click .oe_web_example_stop button': 'watch_stop'
508++ },
509++ watch_start: function () {
510++ this.$el.addClass('oe_web_example_started')
511++ .removeClass('oe_web_example_stopped');
512++ },
513++ watch_stop: function () {
514++ this.$el.removeClass('oe_web_example_started')
515++ .addClass('oe_web_example_stopped');
516++ },
517+ });
518+ };
519
520=== added file 'addons/web/doc/module/17'
521--- addons/web/doc/module/17 1970-01-01 00:00:00 +0000
522+++ addons/web/doc/module/17 2013-02-18 15:08:21 +0000
523@@ -0,0 +1,52 @@
524+# HG changeset patch
525+# Parent 2921a545adc3406d3139be7951f3225e94493466
526+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
527+--- a/static/src/js/first_module.js
528++++ b/static/src/js/first_module.js
529+@@ -7,13 +7,46 @@ openerp.web_example = function (instance
530+ 'click .oe_web_example_start button': 'watch_start',
531+ 'click .oe_web_example_stop button': 'watch_stop'
532+ },
533++ init: function () {
534++ this._super.apply(this, arguments);
535++ this._start = null;
536++ this._watch = null;
537++ },
538++ update_counter: function () {
539++ var h, m, s;
540++ // Subtracting javascript dates returns the difference in milliseconds
541++ var diff = new Date() - this._start;
542++ s = diff / 1000;
543++ m = Math.floor(s / 60);
544++ s -= 60*m;
545++ h = Math.floor(m / 60);
546++ m -= 60*h;
547++ this.$('.oe_web_example_timer').text(
548++ _.str.sprintf("%02d:%02d:%02d", h, m, s));
549++ },
550+ watch_start: function () {
551+ this.$el.addClass('oe_web_example_started')
552+ .removeClass('oe_web_example_stopped');
553++ this._start = new Date();
554++ // Update the UI to the current time
555++ this.update_counter();
556++ // Update the counter at 30 FPS (33ms/frame)
557++ this._watch = setInterval(
558++ this.proxy('update_counter'),
559++ 33);
560+ },
561+ watch_stop: function () {
562++ clearInterval(this._watch);
563++ this.update_counter();
564++ this._start = this._watch = null;
565+ this.$el.removeClass('oe_web_example_started')
566+ .addClass('oe_web_example_stopped');
567+ },
568++ destroy: function () {
569++ if (this._watch) {
570++ clearInterval(this._watch);
571++ }
572++ this._super();
573++ }
574+ });
575+ };
576
577=== added file 'addons/web/doc/module/18'
578--- addons/web/doc/module/18 1970-01-01 00:00:00 +0000
579+++ addons/web/doc/module/18 2013-02-18 15:08:21 +0000
580@@ -0,0 +1,19 @@
581+# HG changeset patch
582+# Parent e0cc13c2b2ec4d6f6bfdb033b189a32e44106f2e
583+diff --git a/__init__.py b/__init__.py
584+--- a/__init__.py
585++++ b/__init__.py
586+@@ -0,0 +1,13 @@
587++# __init__.py
588++from openerp.osv import orm, fields
589++
590++
591++class Times(orm.Model):
592++ _name = 'web_example.stopwatch'
593++
594++ _columns = {
595++ 'time': fields.integer("Time", required=True,
596++ help="Measured time in milliseconds"),
597++ 'user_id': fields.many2one('res.users', "User", required=True,
598++ help="User who registered the measurement")
599++ }
600
601=== added file 'addons/web/doc/module/19'
602--- addons/web/doc/module/19 1970-01-01 00:00:00 +0000
603+++ addons/web/doc/module/19 2013-02-18 15:08:21 +0000
604@@ -0,0 +1,52 @@
605+# HG changeset patch
606+# Parent 05797cc75b49634e640f44b24347f2905b464022
607+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
608+--- a/static/src/js/first_module.js
609++++ b/static/src/js/first_module.js
610+@@ -12,11 +12,13 @@ openerp.web_example = function (instance
611+ this._start = null;
612+ this._watch = null;
613+ },
614+- update_counter: function () {
615++ current: function () {
616++ // Subtracting javascript dates returns the difference in milliseconds
617++ return new Date() - this._start;
618++ },
619++ update_counter: function (time) {
620+ var h, m, s;
621+- // Subtracting javascript dates returns the difference in milliseconds
622+- var diff = new Date() - this._start;
623+- s = diff / 1000;
624++ s = time / 1000;
625+ m = Math.floor(s / 60);
626+ s -= 60*m;
627+ h = Math.floor(m / 60);
628+@@ -29,18 +31,24 @@ openerp.web_example = function (instance
629+ .removeClass('oe_web_example_stopped');
630+ this._start = new Date();
631+ // Update the UI to the current time
632+- this.update_counter();
633++ this.update_counter(this.current());
634+ // Update the counter at 30 FPS (33ms/frame)
635+- this._watch = setInterval(
636+- this.proxy('update_counter'),
637++ this._watch = setInterval(function () {
638++ this.update_counter(this.current());
639++ }.bind(this),
640+ 33);
641+ },
642+ watch_stop: function () {
643+ clearInterval(this._watch);
644+- this.update_counter();
645++ var time = this.current();
646++ this.update_counter(time);
647+ this._start = this._watch = null;
648+ this.$el.removeClass('oe_web_example_started')
649+ .addClass('oe_web_example_stopped');
650++ new instance.web.Model('web_example.stopwatch').call('create', [{
651++ user_id: instance.session.uid,
652++ time: time,
653++ }]);
654+ },
655+ destroy: function () {
656+ if (this._watch) {
657
658=== added file 'addons/web/doc/module/2'
659--- addons/web/doc/module/2 1970-01-01 00:00:00 +0000
660+++ addons/web/doc/module/2 2013-02-18 15:08:21 +0000
661@@ -0,0 +1,12 @@
662+# HG changeset patch
663+# Parent 8a986919a3e22cd7cca51210820c09d4545dc60d
664+diff --git a/__openerp__.py b/__openerp__.py
665+--- a/__openerp__.py
666++++ b/__openerp__.py
667+@@ -3,5 +3,5 @@
668+ 'name': "Web Example",
669+ 'description': "Basic example of a (future) web module",
670+ 'category': 'Hidden',
671+- 'depends': ['base'],
672++ 'depends': ['web'],
673+ }
674
675=== added file 'addons/web/doc/module/20'
676--- addons/web/doc/module/20 1970-01-01 00:00:00 +0000
677+++ addons/web/doc/module/20 2013-02-18 15:08:21 +0000
678@@ -0,0 +1,64 @@
679+Index: web_example/static/src/js/first_module.js
680+===================================================================
681+--- web_example.orig/static/src/js/first_module.js
682++++ web_example/static/src/js/first_module.js
683+@@ -11,20 +11,36 @@ openerp.web_example = function (instance
684+ this._super.apply(this, arguments);
685+ this._start = null;
686+ this._watch = null;
687++ this.model = new instance.web.Model('web_example.stopwatch');
688++ },
689++ start: function () {
690++ var display = this.display_record.bind(this);
691++ return this.model.query()
692++ .filter([['user_id', '=', instance.session.uid]])
693++ .all().done(function (records) {
694++ _(records).each(display);
695++ });
696+ },
697+ current: function () {
698+ // Subtracting javascript dates returns the difference in milliseconds
699+ return new Date() - this._start;
700+ },
701+- update_counter: function (time) {
702++ display_record: function (record) {
703++ $('<li>')
704++ .text(this.format_time(record.time))
705++ .appendTo(this.$('.oe_web_example_saved'));
706++ },
707++ format_time: function (time) {
708+ var h, m, s;
709+ s = time / 1000;
710+ m = Math.floor(s / 60);
711+ s -= 60*m;
712+ h = Math.floor(m / 60);
713+ m -= 60*h;
714+- this.$('.oe_web_example_timer').text(
715+- _.str.sprintf("%02d:%02d:%02d", h, m, s));
716++ return _.str.sprintf("%02d:%02d:%02d", h, m, s);
717++ },
718++ update_counter: function (time) {
719++ this.$('.oe_web_example_timer').text(this.format_time(time));
720+ },
721+ watch_start: function () {
722+ this.$el.addClass('oe_web_example_started')
723+@@ -45,7 +61,7 @@ openerp.web_example = function (instance
724+ this._start = this._watch = null;
725+ this.$el.removeClass('oe_web_example_started')
726+ .addClass('oe_web_example_stopped');
727+- new instance.web.Model('web_example.stopwatch').call('create', [{
728++ this.model.call('create', [{
729+ user_id: instance.session.uid,
730+ time: time,
731+ }]);
732+Index: web_example/static/src/xml/web_example.xml
733+===================================================================
734+--- web_example.orig/static/src/xml/web_example.xml
735++++ web_example/static/src/xml/web_example.xml
736+@@ -7,5 +7,6 @@
737+ <p class="oe_web_example_stop">
738+ <button type="button">Stop</button>
739+ </p>
740++ <ol class="oe_web_example_saved"></ol>
741+ </div>
742+ </templates>
743
744=== added file 'addons/web/doc/module/21'
745--- addons/web/doc/module/21 1970-01-01 00:00:00 +0000
746+++ addons/web/doc/module/21 2013-02-18 15:08:21 +0000
747@@ -0,0 +1,27 @@
748+Index: web_example/static/src/js/first_module.js
749+===================================================================
750+--- web_example.orig/static/src/js/first_module.js
751++++ web_example/static/src/js/first_module.js
752+@@ -55,16 +55,20 @@ openerp.web_example = function (instance
753+ 33);
754+ },
755+ watch_stop: function () {
756++ var self = this;
757+ clearInterval(this._watch);
758+ var time = this.current();
759+ this.update_counter(time);
760+ this._start = this._watch = null;
761+ this.$el.removeClass('oe_web_example_started')
762+ .addClass('oe_web_example_stopped');
763+- this.model.call('create', [{
764++ var record = {
765+ user_id: instance.session.uid,
766+ time: time,
767+- }]);
768++ };
769++ this.model.call('create', [record]).done(function () {
770++ self.display_record(record);
771++ });
772+ },
773+ destroy: function () {
774+ if (this._watch) {
775
776=== added file 'addons/web/doc/module/22'
777--- addons/web/doc/module/22 1970-01-01 00:00:00 +0000
778+++ addons/web/doc/module/22 2013-02-18 15:08:21 +0000
779@@ -0,0 +1,6 @@
780+Index: web_example/static/src/tests/timer.js
781+===================================================================
782+--- /dev/null
783++++ web_example/static/src/tests/timer.js
784+@@ -0,0 +1 @@
785++
786
787=== added file 'addons/web/doc/module/23'
788--- addons/web/doc/module/23 1970-01-01 00:00:00 +0000
789+++ addons/web/doc/module/23 2013-02-18 15:08:21 +0000
790@@ -0,0 +1,14 @@
791+Index: web_example/static/src/tests/timer.js
792+===================================================================
793+--- web_example.orig/static/src/tests/timer.js
794++++ web_example/static/src/tests/timer.js
795+@@ -1 +1,8 @@
796+-
797++openerp.testing.section('timer', function (test) {
798++ test('successful test', function () {
799++ ok(true, "should work");
800++ });
801++ test('unsuccessful test', function () {
802++ ok(false, "shoud fail");
803++ });
804++});
805
806=== added file 'addons/web/doc/module/24'
807--- addons/web/doc/module/24 1970-01-01 00:00:00 +0000
808+++ addons/web/doc/module/24 2013-02-18 15:08:21 +0000
809@@ -0,0 +1,10 @@
810+Index: web_example/__openerp__.py
811+===================================================================
812+--- web_example.orig/__openerp__.py
813++++ web_example/__openerp__.py
814+@@ -8,4 +8,5 @@
815+ 'js': ['static/src/js/first_module.js'],
816+ 'css': ['static/src/css/web_example.css'],
817+ 'qweb': ['static/src/xml/web_example.xml'],
818++ 'test': ['static/src/tests/timer.js'],
819+ }
820
821=== added file 'addons/web/doc/module/25'
822--- addons/web/doc/module/25 1970-01-01 00:00:00 +0000
823+++ addons/web/doc/module/25 2013-02-18 15:08:21 +0000
824@@ -0,0 +1,55 @@
825+Index: web_example/static/src/tests/timer.js
826+===================================================================
827+--- web_example.orig/static/src/tests/timer.js
828++++ web_example/static/src/tests/timer.js
829+@@ -1,8 +1,45 @@
830+ openerp.testing.section('timer', function (test) {
831+- test('successful test', function () {
832+- ok(true, "should work");
833+- });
834+- test('unsuccessful test', function () {
835+- ok(false, "shoud fail");
836++ test('format_time', function (instance) {
837++ var w = new instance.web_example.Action();
838++
839++ strictEqual(
840++ w.format_time(0),
841++ '00:00:00');
842++ strictEqual(
843++ w.format_time(543),
844++ '00:00:00',
845++ "should round sub-second times down to zero");
846++ strictEqual(
847++ w.format_time(5340),
848++ '00:00:05',
849++ "should floor sub-second extents to the previous second");
850++ strictEqual(
851++ w.format_time(60000),
852++ '00:01:00');
853++ strictEqual(
854++ w.format_time(3600000),
855++ '01:00:00');
856++ strictEqual(
857++ w.format_time(86400000),
858++ '24:00:00');
859++ strictEqual(
860++ w.format_time(604800000),
861++ '168:00:00');
862++
863++ strictEqual(
864++ w.format_time(22733958),
865++ '06:18:53');
866++ strictEqual(
867++ w.format_time(41676639),
868++ '11:34:36');
869++ strictEqual(
870++ w.format_time(57802094),
871++ '16:03:22');
872++ strictEqual(
873++ w.format_time(73451828),
874++ '20:24:11');
875++ strictEqual(
876++ w.format_time(84092336),
877++ '23:21:32');
878+ });
879+ });
880
881=== added file 'addons/web/doc/module/26'
882--- addons/web/doc/module/26 1970-01-01 00:00:00 +0000
883+++ addons/web/doc/module/26 2013-02-18 15:08:21 +0000
884@@ -0,0 +1,38 @@
885+Index: web_example/static/src/tests/timer.js
886+===================================================================
887+--- web_example.orig/static/src/tests/timer.js
888++++ web_example/static/src/tests/timer.js
889+@@ -42,4 +42,33 @@ openerp.testing.section('timer', functio
890+ w.format_time(84092336),
891+ '23:21:32');
892+ });
893++ test('update_counter', function (instance, $fixture) {
894++ var w = new instance.web_example.Action();
895++ // $fixture is a DOM tree whose content gets cleaned up before
896++ // each test, so we can add whatever we need to it
897++ $fixture.append('<div class="oe_web_example_timer">');
898++ // Then set it on the widget
899++ w.setElement($fixture);
900++
901++ // Update the counter with a known value
902++ w.update_counter(22733958);
903++ // And check the DOM matches
904++ strictEqual($fixture.text(), '06:18:53');
905++
906++ w.update_counter(73451828)
907++ strictEqual($fixture.text(), '20:24:11');
908++ });
909++ test('display_record', function (instance, $fixture) {
910++ var w = new instance.web_example.Action();
911++ $fixture.append('<ol class="oe_web_example_saved">')
912++ w.setElement($fixture);
913++
914++ w.display_record({time: 41676639});
915++ w.display_record({time: 84092336});
916++
917++ var $lis = $fixture.find('li');
918++ strictEqual($lis.length, 2, "should have printed 2 records");
919++ strictEqual($lis[0].textContent, '11:34:36');
920++ strictEqual($lis[1].textContent, '23:21:32');
921++ });
922+ });
923
924=== added file 'addons/web/doc/module/27'
925--- addons/web/doc/module/27 1970-01-01 00:00:00 +0000
926+++ addons/web/doc/module/27 2013-02-18 15:08:21 +0000
927@@ -0,0 +1,28 @@
928+Index: web_example/static/src/tests/timer.js
929+===================================================================
930+--- web_example.orig/static/src/tests/timer.js
931++++ web_example/static/src/tests/timer.js
932+@@ -71,4 +71,23 @@ openerp.testing.section('timer', functio
933+ strictEqual($lis[0].textContent, '11:34:36');
934+ strictEqual($lis[1].textContent, '23:21:32');
935+ });
936++ test('start', {templates: true, rpc: 'mock', asserts: 3}, function (instance, $fixture, mock) {
937++ // Rather odd-looking shortcut for search+read in a single RPC call
938++ mock('/web/dataset/search_read', function () {
939++ // ignore parameters, just return a pair of records.
940++ return {records: [
941++ {time: 22733958},
942++ {time: 84092336}
943++ ]};
944++ });
945++
946++ var w = new instance.web_example.Action();
947++ return w.appendTo($fixture)
948++ .then(function () {
949++ var $lis = $fixture.find('li');
950++ strictEqual($lis.length, 2);
951++ strictEqual($lis[0].textContent, '06:18:53');
952++ strictEqual($lis[1].textContent, '23:21:32');
953++ });
954++ });
955+ });
956
957=== added file 'addons/web/doc/module/28'
958--- addons/web/doc/module/28 1970-01-01 00:00:00 +0000
959+++ addons/web/doc/module/28 2013-02-18 15:08:21 +0000
960@@ -0,0 +1,13 @@
961+Index: web_example/static/src/js/first_module.js
962+===================================================================
963+--- web_example.orig/static/src/js/first_module.js
964++++ web_example/static/src/js/first_module.js
965+@@ -66,7 +66,7 @@ openerp.web_example = function (instance
966+ user_id: instance.session.uid,
967+ time: time,
968+ };
969+- this.model.call('create', [record]).done(function () {
970++ return this.model.call('create', [record]).done(function () {
971+ self.display_record(record);
972+ });
973+ },
974
975=== added file 'addons/web/doc/module/29'
976--- addons/web/doc/module/29 1970-01-01 00:00:00 +0000
977+++ addons/web/doc/module/29 2013-02-18 15:08:21 +0000
978@@ -0,0 +1,37 @@
979+Index: web_example/static/src/tests/timer.js
980+===================================================================
981+--- web_example.orig/static/src/tests/timer.js
982++++ web_example/static/src/tests/timer.js
983+@@ -90,4 +90,32 @@ openerp.testing.section('timer', functio
984+ strictEqual($lis[1].textContent, '23:21:32');
985+ });
986+ });
987++ test('watch_stop', {templates: true, rpc: 'mock', asserts: 3}, function (instance, $fix, mock) {
988++ var created = false;
989++ mock('web_example.stopwatch:create', function (args, kwargs) {
990++ created = true;
991++ // return a fake id (unused)
992++ return 42;
993++ });
994++ mock('/web/dataset/search_read', function () {
995++ return {records: []};
996++ });
997++
998++ var w = new instance.web_example.Action();
999++ return w.appendTo($fix)
1000++ .then(function () {
1001++ // Virtual start point 5s before 'now'
1002++ w._start = new Date() - 5000;
1003++ return w.watch_stop();
1004++ })
1005++ .done(function () {
1006++ ok(created, "should have called create()");
1007++ strictEqual($fix.find('.oe_web_example_timer').text(),
1008++ '00:00:05',
1009++ "should have updated the timer");
1010++ strictEqual($fix.find('li')[0].textContent,
1011++ '00:00:05',
1012++ "should have added the new time to the list");
1013++ });
1014++ });
1015+ });
1016
1017=== added file 'addons/web/doc/module/3'
1018--- addons/web/doc/module/3 1970-01-01 00:00:00 +0000
1019+++ addons/web/doc/module/3 2013-02-18 15:08:21 +0000
1020@@ -0,0 +1,9 @@
1021+# HG changeset patch
1022+# Parent dcf661a5eef8f82503831bdb8e6c9d2f9beb285e
1023+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
1024+new file mode 100644
1025+--- /dev/null
1026++++ b/static/src/js/first_module.js
1027+@@ -0,0 +1,2 @@
1028++// static/src/js/first_module.js
1029++console.log("Debug statement: file loaded");
1030
1031=== added file 'addons/web/doc/module/4'
1032--- addons/web/doc/module/4 1970-01-01 00:00:00 +0000
1033+++ addons/web/doc/module/4 2013-02-18 15:08:21 +0000
1034@@ -0,0 +1,11 @@
1035+# HG changeset patch
1036+# Parent 139dae60de67efa0017f5032f71ab774685c5507
1037+diff --git a/__openerp__.py b/__openerp__.py
1038+--- a/__openerp__.py
1039++++ b/__openerp__.py
1040+@@ -4,4 +4,5 @@
1041+ 'description': "Basic example of a (future) web module",
1042+ 'category': 'Hidden',
1043+ 'depends': ['web'],
1044++ 'js': ['static/src/js/first_module.js'],
1045+ }
1046
1047=== added file 'addons/web/doc/module/5'
1048--- addons/web/doc/module/5 1970-01-01 00:00:00 +0000
1049+++ addons/web/doc/module/5 2013-02-18 15:08:21 +0000
1050@@ -0,0 +1,11 @@
1051+# HG changeset patch
1052+# Parent c8ae7646cce3f271698c844eb2d67f9a8719650d
1053+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
1054+--- a/static/src/js/first_module.js
1055++++ b/static/src/js/first_module.js
1056+@@ -1,2 +1,4 @@
1057+ // static/src/js/first_module.js
1058+-console.log("Debug statement: file loaded");
1059++openerp.web_example = function (instance) {
1060++ console.log("Module loaded");
1061++};
1062
1063=== added file 'addons/web/doc/module/6'
1064--- addons/web/doc/module/6 1970-01-01 00:00:00 +0000
1065+++ addons/web/doc/module/6 2013-02-18 15:08:21 +0000
1066@@ -0,0 +1,29 @@
1067+# HG changeset patch
1068+# Parent 0026cb80097a724db8d36371bc00da993a51a06f
1069+
1070+diff --git a/__openerp__.py b/__openerp__.py
1071+--- a/__openerp__.py
1072++++ b/__openerp__.py
1073+@@ -4,5 +4,6 @@
1074+ 'description': "Basic example of a (future) web module",
1075+ 'category': 'Hidden',
1076+ 'depends': ['web'],
1077++ 'data': ['web_example.xml'],
1078+ 'js': ['static/src/js/first_module.js'],
1079+ }
1080+diff --git a/web_example.xml b/web_example.xml
1081+new file mode 100644
1082+--- /dev/null
1083++++ b/web_example.xml
1084+@@ -0,0 +1,11 @@
1085++<!-- web_example/web_example.xml -->
1086++<openerp>
1087++ <data>
1088++ <record model="ir.actions.client" id="action_client_example">
1089++ <field name="name">Example Client Action</field>
1090++ <field name="tag">example.action</field>
1091++ </record>
1092++ <menuitem action="action_client_example"
1093++ id="menu_client_example"/>
1094++ </data>
1095++</openerp>
1096
1097=== added file 'addons/web/doc/module/8'
1098--- addons/web/doc/module/8 1970-01-01 00:00:00 +0000
1099+++ addons/web/doc/module/8 2013-02-18 15:08:21 +0000
1100@@ -0,0 +1,14 @@
1101+# HG changeset patch
1102+# Parent d987c9edd884de1de30f2ceb70d2e554474b8dd1
1103+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
1104+--- a/static/src/js/first_module.js
1105++++ b/static/src/js/first_module.js
1106+@@ -1,4 +1,7 @@
1107+ // static/src/js/first_module.js
1108+ openerp.web_example = function (instance) {
1109+- console.log("Module loaded");
1110++ instance.web.client_actions.add('example.action', 'instance.web_example.action');
1111++ instance.web_example.action = function (parent, action) {
1112++ console.log("Executed the action", action);
1113++ };
1114+ };
1115
1116=== added file 'addons/web/doc/module/9'
1117--- addons/web/doc/module/9 1970-01-01 00:00:00 +0000
1118+++ addons/web/doc/module/9 2013-02-18 15:08:21 +0000
1119@@ -0,0 +1,21 @@
1120+# HG changeset patch
1121+# Parent 6a1a7240ea0e63182f60abb1eb5c631089d56dbe
1122+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
1123+--- a/static/src/js/first_module.js
1124++++ b/static/src/js/first_module.js
1125+@@ -1,7 +1,11 @@
1126+ // static/src/js/first_module.js
1127+ openerp.web_example = function (instance) {
1128+- instance.web.client_actions.add('example.action', 'instance.web_example.action');
1129+- instance.web_example.action = function (parent, action) {
1130+- console.log("Executed the action", action);
1131+- };
1132++ instance.web.client_actions.add('example.action', 'instance.web_example.Action');
1133++ instance.web_example.Action = instance.web.Widget.extend({
1134++ className: 'oe_web_example',
1135++ start: function () {
1136++ this.$el.text("Hello, world!");
1137++ return this._super();
1138++ }
1139++ });
1140+ };
1141
1142=== removed file 'addons/web/doc/module/__init__.py'
1143=== removed file 'addons/web/doc/module/__openerp__.py'
1144--- addons/web/doc/module/__openerp__.py 2012-12-05 13:28:54 +0000
1145+++ addons/web/doc/module/__openerp__.py 1970-01-01 00:00:00 +0000
1146@@ -1,7 +0,0 @@
1147-# __openerp__.py
1148-{
1149- 'name': "Web Example",
1150- 'description': "Basic example of a (future) web module",
1151- 'category': 'Hidden',
1152- 'depends': ['base'],
1153-}
1154
1155=== removed file 'addons/web/doc/module/__openerp__.py.1.diff'
1156--- addons/web/doc/module/__openerp__.py.1.diff 2012-12-05 13:28:54 +0000
1157+++ addons/web/doc/module/__openerp__.py.1.diff 1970-01-01 00:00:00 +0000
1158@@ -1,11 +0,0 @@
1159---- web_example/__openerp__.py
1160-+++ web_example/__openerp__.py
1161-@@ -1,7 +1,7 @@
1162- # __openerp__.py
1163- {
1164- 'name': "Web Example",
1165- 'description': "Basic example of a (future) web module",
1166- 'category': 'Hidden',
1167-- 'depends': ['base'],
1168-+ 'depends': ['web'],
1169- }
1170
1171=== removed file 'addons/web/doc/module/__openerp__.py.2.diff'
1172--- addons/web/doc/module/__openerp__.py.2.diff 2012-12-05 13:28:54 +0000
1173+++ addons/web/doc/module/__openerp__.py.2.diff 1970-01-01 00:00:00 +0000
1174@@ -1,11 +0,0 @@
1175---- web_example/__openerp__.py
1176-+++ web_example/__openerp__.py
1177-@@ -1,7 +1,8 @@
1178- # __openerp__.py
1179- {
1180- 'name': "Web Example",
1181- 'description': "Basic example of a (future) web module",
1182- 'category': 'Hidden',
1183- 'depends': ['web'],
1184-+ 'js': ['static/src/js/first_module.js'],
1185- }
1186
1187=== removed file 'addons/web/doc/module/__openerp__.py.3.diff'
1188--- addons/web/doc/module/__openerp__.py.3.diff 2012-12-05 13:28:54 +0000
1189+++ addons/web/doc/module/__openerp__.py.3.diff 1970-01-01 00:00:00 +0000
1190@@ -1,12 +0,0 @@
1191---- web_example/__openerp__.py
1192-+++ web_example/__openerp__.py
1193-@@ -1,8 +1,9 @@
1194- # __openerp__.py
1195- {
1196- 'name': "Web Example",
1197- 'description': "Basic example of a (future) web module",
1198- 'category': 'Hidden',
1199- 'depends': ['web'],
1200-+ 'data': ['web_example.xml'],
1201- 'js': ['static/src/js/first_module.js'],
1202- }
1203
1204=== removed file 'addons/web/doc/module/__openerp__.py.4.diff'
1205--- addons/web/doc/module/__openerp__.py.4.diff 2012-12-05 13:28:54 +0000
1206+++ addons/web/doc/module/__openerp__.py.4.diff 1970-01-01 00:00:00 +0000
1207@@ -1,13 +0,0 @@
1208---- web_example/__openerp__.py
1209-+++ web_example/__openerp__.py
1210-@@ -1,9 +1,10 @@
1211- # __openerp__.py
1212- {
1213- 'name': "Web Example",
1214- 'description': "Basic example of a (future) web module",
1215- 'category': 'Hidden',
1216- 'depends': ['web'],
1217- 'data': ['web_example.xml'],
1218- 'js': ['static/src/js/first_module.js'],
1219-+ 'css': ['static/src/css/web_example.css'],
1220- }
1221
1222=== removed file 'addons/web/doc/module/__openerp__.py.5.diff'
1223--- addons/web/doc/module/__openerp__.py.5.diff 2012-12-05 13:28:54 +0000
1224+++ addons/web/doc/module/__openerp__.py.5.diff 1970-01-01 00:00:00 +0000
1225@@ -1,14 +0,0 @@
1226---- web_example/__openerp__.py
1227-+++ web_example/__openerp__.py
1228-@@ -1,10 +1,11 @@
1229- # __openerp__.py
1230- {
1231- 'name': "Web Example",
1232- 'description': "Basic example of a (future) web module",
1233- 'category': 'Hidden',
1234- 'depends': ['web'],
1235- 'data': ['web_example.xml'],
1236- 'js': ['static/src/js/first_module.js'],
1237- 'css': ['static/src/css/web_example.css'],
1238-+ 'qweb': ['static/src/xml/web_example.xml'],
1239- }
1240
1241=== added file 'addons/web/doc/module/series'
1242--- addons/web/doc/module/series 1970-01-01 00:00:00 +0000
1243+++ addons/web/doc/module/series 2013-02-18 15:08:21 +0000
1244@@ -0,0 +1,27 @@
1245+0
1246+2
1247+3
1248+4
1249+5
1250+6
1251+8
1252+9
1253+10
1254+11
1255+12
1256+14
1257+15
1258+16
1259+17
1260+18
1261+19
1262+20
1263+21
1264+22
1265+23
1266+24
1267+25
1268+26
1269+27
1270+28
1271+29
1272
1273=== removed directory 'addons/web/doc/module/static'
1274=== removed directory 'addons/web/doc/module/static/src'
1275=== removed directory 'addons/web/doc/module/static/src/css'
1276=== removed file 'addons/web/doc/module/static/src/css/web_example.css'
1277--- addons/web/doc/module/static/src/css/web_example.css 2012-12-05 13:28:54 +0000
1278+++ addons/web/doc/module/static/src/css/web_example.css 1970-01-01 00:00:00 +0000
1279@@ -1,6 +0,0 @@
1280-.openerp .oe_web_example {
1281- color: white;
1282- background-color: black;
1283- height: 100%;
1284- font-size: 400%;
1285-}
1286
1287=== removed file 'addons/web/doc/module/static/src/css/web_example.css.1.diff'
1288--- addons/web/doc/module/static/src/css/web_example.css.1.diff 2012-12-05 13:28:54 +0000
1289+++ addons/web/doc/module/static/src/css/web_example.css.1.diff 1970-01-01 00:00:00 +0000
1290@@ -1,17 +0,0 @@
1291---- web_example/static/src/css/web_example.css
1292-+++ web_example/static/src/css/web_example.css
1293-@@ -1,6 +1,13 @@
1294- .openerp .oe_web_example {
1295- color: white;
1296- background-color: black;
1297- height: 100%;
1298-- font-size: 400%;
1299- }
1300-+.openerp .oe_web_example h4 {
1301-+ margin: 0;
1302-+ font-size: 200%;
1303-+}
1304-+.openerp .oe_web_example.oe_web_example_started .oe_web_example_start button,
1305-+.openerp .oe_web_example.oe_web_example_stopped .oe_web_example_stop button {
1306-+ display: none
1307-+}
1308
1309=== removed directory 'addons/web/doc/module/static/src/js'
1310=== removed file 'addons/web/doc/module/static/src/js/first_module.js'
1311--- addons/web/doc/module/static/src/js/first_module.js 2012-12-05 13:28:54 +0000
1312+++ addons/web/doc/module/static/src/js/first_module.js 1970-01-01 00:00:00 +0000
1313@@ -1,2 +0,0 @@
1314-// static/src/js/first_module.js
1315-console.log("Debug statement: file loaded");
1316
1317=== removed file 'addons/web/doc/module/static/src/js/first_module.js.1.diff'
1318--- addons/web/doc/module/static/src/js/first_module.js.1.diff 2012-12-05 13:28:54 +0000
1319+++ addons/web/doc/module/static/src/js/first_module.js.1.diff 1970-01-01 00:00:00 +0000
1320@@ -1,8 +0,0 @@
1321---- web_example/static/src/js/first_module.js
1322-+++ web_example/static/src/js/first_module.js
1323-@@ -1,2 +1,4 @@
1324- // static/src/js/first_module.js
1325--console.log("Debug statement: file loaded");
1326-+openerp.web_example = function (instance) {
1327-+ console.log("Module loaded");
1328-+};
1329
1330=== removed file 'addons/web/doc/module/static/src/js/first_module.js.2.diff'
1331--- addons/web/doc/module/static/src/js/first_module.js.2.diff 2012-12-05 13:28:54 +0000
1332+++ addons/web/doc/module/static/src/js/first_module.js.2.diff 1970-01-01 00:00:00 +0000
1333@@ -1,11 +0,0 @@
1334---- web_example/static/src/js/first_module.js
1335-+++ web_example/static/src/js/first_module.js
1336-@@ -1,4 +1,7 @@
1337- // static/src/js/first_module.js
1338- openerp.web_example = function (instance) {
1339-- console.log("Module loaded");
1340-+ instance.web.client_actions.add('example.action', 'instance.web_example.action');
1341-+ instance.web_example.action = function (parent, action) {
1342-+ console.log("Executed the action", action);
1343-+ };
1344- };
1345
1346=== removed file 'addons/web/doc/module/static/src/js/first_module.js.3.diff'
1347--- addons/web/doc/module/static/src/js/first_module.js.3.diff 2012-12-05 13:28:54 +0000
1348+++ addons/web/doc/module/static/src/js/first_module.js.3.diff 1970-01-01 00:00:00 +0000
1349@@ -1,18 +0,0 @@
1350---- web_example/static/src/js/first_module.js
1351-+++ web_example/static/src/js/first_module.js
1352-@@ -1,7 +1,11 @@
1353- // static/src/js/first_module.js
1354- openerp.web_example = function (instance) {
1355-- instance.web.client_actions.add('example.action', 'instance.web_example.action');
1356-- instance.web_example.action = function (parent, action) {
1357-- console.log("Executed the action", action);
1358-- };
1359-+ instance.web.client_actions.add('example.action', 'instance.web_example.Action');
1360-+ instance.web_example.Action = instance.web.Widget.extend({
1361-+ className: 'oe_web_example',
1362-+ start: function () {
1363-+ this.$el.text("Hello, world!");
1364-+ return this._super();
1365-+ }
1366-+ });
1367- };
1368
1369=== removed file 'addons/web/doc/module/static/src/js/first_module.js.4.diff'
1370--- addons/web/doc/module/static/src/js/first_module.js.4.diff 2012-12-05 13:28:54 +0000
1371+++ addons/web/doc/module/static/src/js/first_module.js.4.diff 1970-01-01 00:00:00 +0000
1372@@ -1,15 +0,0 @@
1373---- web_example/static/src/js/first_module.js
1374-+++ web_example/static/src/js/first_module.js
1375-@@ -1,11 +1,7 @@
1376- // static/src/js/first_module.js
1377- openerp.web_example = function (instance) {
1378- instance.web.client_actions.add('example.action', 'instance.web_example.Action');
1379- instance.web_example.Action = instance.web.Widget.extend({
1380-+ template: 'web_example.action'
1381-- className: 'oe_web_example',
1382-- start: function () {
1383-- this.$el.text("Hello, world!");
1384-- return this._super();
1385-- }
1386- });
1387- };
1388
1389=== removed file 'addons/web/doc/module/static/src/js/first_module.js.5.diff'
1390--- addons/web/doc/module/static/src/js/first_module.js.5.diff 2012-12-05 13:28:54 +0000
1391+++ addons/web/doc/module/static/src/js/first_module.js.5.diff 1970-01-01 00:00:00 +0000
1392@@ -1,23 +0,0 @@
1393---- web_example/static/src/js/first_module.js
1394-+++ web_example/static/src/js/first_module.js
1395-@@ -1,7 +1,19 @@
1396- // static/src/js/first_module.js
1397- openerp.web_example = function (instance) {
1398- instance.web.client_actions.add('example.action', 'instance.web_example.Action');
1399- instance.web_example.Action = instance.web.Widget.extend({
1400-- template: 'web_example.action'
1401-+ template: 'web_example.action',
1402-+ events: {
1403-+ 'click .oe_web_example_start button': 'watch_start',
1404-+ 'click .oe_web_example_stop button': 'watch_stop'
1405-+ },
1406-+ watch_start: function () {
1407-+ this.$el.addClass('oe_web_example_started')
1408-+ .removeClass('oe_web_example_stopped');
1409-+ },
1410-+ watch_stop: function () {
1411-+ this.$el.removeClass('oe_web_example_started')
1412-+ .addClass('oe_web_example_stopped');
1413-+ },
1414- });
1415- };
1416
1417=== removed file 'addons/web/doc/module/static/src/js/first_module.js.6.diff'
1418--- addons/web/doc/module/static/src/js/first_module.js.6.diff 2012-12-05 13:28:54 +0000
1419+++ addons/web/doc/module/static/src/js/first_module.js.6.diff 1970-01-01 00:00:00 +0000
1420@@ -1,55 +0,0 @@
1421---- web_example/static/src/js/first_module.js
1422-+++ web_example/static/src/js/first_module.js
1423-@@ -1,19 +1,52 @@
1424- // static/src/js/first_module.js
1425- openerp.web_example = function (instance) {
1426- instance.web.client_actions.add('example.action', 'instance.web_example.Action');
1427- instance.web_example.Action = instance.web.Widget.extend({
1428- template: 'web_example.action',
1429- events: {
1430- 'click .oe_web_example_start button': 'watch_start',
1431- 'click .oe_web_example_stop button': 'watch_stop'
1432- },
1433-+ init: function () {
1434-+ this._super.apply(this, arguments);
1435-+ this._start = null;
1436-+ this._watch = null;
1437-+ },
1438-+ update_counter: function () {
1439-+ var h, m, s;
1440-+ // Subtracting javascript dates returns the difference in milliseconds
1441-+ var diff = new Date() - this._start;
1442-+ s = diff / 1000;
1443-+ m = Math.floor(s / 60);
1444-+ s -= 60*m;
1445-+ h = Math.floor(m / 60);
1446-+ m -= 60*h;
1447-+ this.$('.oe_web_example_timer').text(
1448-+ _.str.sprintf("%02d:%02d:%02d", h, m, s));
1449-+ },
1450- watch_start: function () {
1451- this.$el.addClass('oe_web_example_started')
1452- .removeClass('oe_web_example_stopped');
1453-+ this._start = new Date();
1454-+ // Update the UI to the current time
1455-+ this.update_counter();
1456-+ // Update the counter at 30 FPS (33ms/frame)
1457-+ this._watch = setInterval(
1458-+ this.proxy('update_counter'),
1459-+ 33);
1460- },
1461- watch_stop: function () {
1462-+ clearInterval(this._watch);
1463-+ this.update_counter();
1464-+ this._start = this._watch = null;
1465- this.$el.removeClass('oe_web_example_started')
1466- .addClass('oe_web_example_stopped');
1467- },
1468-+ destroy: function () {
1469-+ if (this._watch) {
1470-+ clearInterval(this._watch);
1471-+ }
1472-+ this._super();
1473-+ }
1474- });
1475- };
1476
1477=== removed directory 'addons/web/doc/module/static/src/xml'
1478=== removed file 'addons/web/doc/module/static/src/xml/web_example.xml'
1479--- addons/web/doc/module/static/src/xml/web_example.xml 2012-12-05 13:28:54 +0000
1480+++ addons/web/doc/module/static/src/xml/web_example.xml 1970-01-01 00:00:00 +0000
1481@@ -1,11 +0,0 @@
1482-<templates>
1483-<div t-name="web_example.action" class="oe_web_example oe_web_example_stopped">
1484- <h4 class="oe_web_example_timer">00:00:00</h4>
1485- <p class="oe_web_example_start">
1486- <button type="button">Start</button>
1487- </p>
1488- <p class="oe_web_example_stop">
1489- <button type="button">Stop</button>
1490- </p>
1491-</div>
1492-</templates>
1493
1494=== added file 'addons/web/doc/module/testing_0.png'
1495Binary files addons/web/doc/module/testing_0.png 1970-01-01 00:00:00 +0000 and addons/web/doc/module/testing_0.png 2013-02-18 15:08:21 +0000 differ
1496=== added file 'addons/web/doc/module/testing_1.png'
1497Binary files addons/web/doc/module/testing_1.png 1970-01-01 00:00:00 +0000 and addons/web/doc/module/testing_1.png 2013-02-18 15:08:21 +0000 differ
1498=== removed file 'addons/web/doc/module/web_example.xml'
1499--- addons/web/doc/module/web_example.xml 2012-12-05 13:28:54 +0000
1500+++ addons/web/doc/module/web_example.xml 1970-01-01 00:00:00 +0000
1501@@ -1,11 +0,0 @@
1502-<!-- web_example/web_example.xml -->
1503-<openerp>
1504- <data>
1505- <record model="ir.actions.client" id="action_client_example">
1506- <field name="name">Example Client Action</field>
1507- <field name="tag">example.action</field>
1508- </record>
1509- <menuitem action="action_client_example"
1510- id="menu_client_example"/>
1511- </data>
1512-</openerp>
1513
1514=== modified file 'addons/web/doc/testing.rst'
1515--- addons/web/doc/testing.rst 2012-11-16 11:10:43 +0000
1516+++ addons/web/doc/testing.rst 2013-02-18 15:08:21 +0000
1517@@ -327,6 +327,8 @@
1518 :js:attr:`~TestOptions.rpc`, and can be one of two modes: ``mock`` or
1519 ``rpc``.
1520
1521+.. _testing-rpc-mock:
1522+
1523 Mock RPC
1524 ++++++++
1525
1526
1527=== modified file 'addons/web/doc/widget.rst'
1528--- addons/web/doc/widget.rst 2012-12-12 16:57:39 +0000
1529+++ addons/web/doc/widget.rst 2013-02-18 15:08:21 +0000
1530@@ -93,7 +93,7 @@
1531 Any override to :js:func:`~openerp.web.Widget.renderElement` which
1532 does not call its ``_super`` **must** call
1533 :js:func:`~openerp.web.Widget.setElement` with whatever it
1534- generated or the widget's behavior is undefined.r
1535+ generated or the widget's behavior is undefined.
1536
1537 .. note::
1538