summaryrefslogtreecommitdiff
path: root/addons/web/doc
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/doc
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/doc')
-rw-r--r--addons/web/doc/Makefile154
-rw-r--r--addons/web/doc/_static/openerp.pngbin0 -> 3479 bytes
-rw-r--r--addons/web/doc/_templates/sidebarintro.html16
-rw-r--r--addons/web/doc/_templates/sidebarlogo.html3
-rw-r--r--addons/web/doc/_themes/LICENSE37
-rw-r--r--addons/web/doc/_themes/README31
-rw-r--r--addons/web/doc/_themes/flask/layout.html25
-rw-r--r--addons/web/doc/_themes/flask/relations.html19
-rw-r--r--addons/web/doc/_themes/flask/static/flasky.css_t396
-rw-r--r--addons/web/doc/_themes/flask/static/small_flask.css70
-rw-r--r--addons/web/doc/_themes/flask/theme.conf9
-rw-r--r--addons/web/doc/_themes/flask_small/layout.html22
-rw-r--r--addons/web/doc/_themes/flask_small/static/flasky.css_t287
-rw-r--r--addons/web/doc/_themes/flask_small/static/forkme_right_darkblue_121621.pngbin0 -> 7791 bytes
-rw-r--r--addons/web/doc/_themes/flask_small/theme.conf10
-rw-r--r--addons/web/doc/_themes/flask_theme_support.py86
-rw-r--r--addons/web/doc/addon-structure.txt12
-rw-r--r--addons/web/doc/client_action.rst114
-rw-r--r--addons/web/doc/conf.py260
-rw-r--r--addons/web/doc/form_view.rst55
-rw-r--r--addons/web/doc/guidelines.rst194
-rw-r--r--addons/web/doc/images/db-query.pngbin0 -> 7142 bytes
-rw-r--r--addons/web/doc/images/runner.pngbin0 -> 3963 bytes
-rw-r--r--addons/web/doc/images/runner2.pngbin0 -> 6807 bytes
-rw-r--r--addons/web/doc/images/tests.pngbin0 -> 65388 bytes
-rw-r--r--addons/web/doc/images/tests2.pngbin0 -> 20114 bytes
-rw-r--r--addons/web/doc/images/tests3.pngbin0 -> 20382 bytes
-rw-r--r--addons/web/doc/index.rst55
-rw-r--r--addons/web/doc/list_view.rst531
-rw-r--r--addons/web/doc/make.bat170
-rw-r--r--addons/web/doc/module.rst442
-rw-r--r--addons/web/doc/module/017
-rw-r--r--addons/web/doc/module/1013
-rw-r--r--addons/web/doc/module/1111
-rw-r--r--addons/web/doc/module/1228
-rw-r--r--addons/web/doc/module/1417
-rw-r--r--addons/web/doc/module/1519
-rw-r--r--addons/web/doc/module/1625
-rw-r--r--addons/web/doc/module/1752
-rw-r--r--addons/web/doc/module/1819
-rw-r--r--addons/web/doc/module/1952
-rw-r--r--addons/web/doc/module/212
-rw-r--r--addons/web/doc/module/2064
-rw-r--r--addons/web/doc/module/2127
-rw-r--r--addons/web/doc/module/226
-rw-r--r--addons/web/doc/module/2314
-rw-r--r--addons/web/doc/module/2410
-rw-r--r--addons/web/doc/module/2555
-rw-r--r--addons/web/doc/module/2638
-rw-r--r--addons/web/doc/module/2728
-rw-r--r--addons/web/doc/module/2813
-rw-r--r--addons/web/doc/module/2937
-rw-r--r--addons/web/doc/module/39
-rw-r--r--addons/web/doc/module/411
-rw-r--r--addons/web/doc/module/511
-rw-r--r--addons/web/doc/module/629
-rw-r--r--addons/web/doc/module/814
-rw-r--r--addons/web/doc/module/921
-rw-r--r--addons/web/doc/module/series27
-rw-r--r--addons/web/doc/module/testing_0.pngbin0 -> 23285 bytes
-rw-r--r--addons/web/doc/module/testing_1.pngbin0 -> 36183 bytes
-rw-r--r--addons/web/doc/search_view.rst560
62 files changed, 4237 insertions, 0 deletions
diff --git a/addons/web/doc/Makefile b/addons/web/doc/Makefile
new file mode 100644
index 00000000..c1eff186
--- /dev/null
+++ b/addons/web/doc/Makefile
@@ -0,0 +1,154 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS = -q
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ sed -i '/-99999/d' _build/dirhtml/_static/flasky.css
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenERPTechnicalDocumentation.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenERPTechnicalDocumentation.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/OpenERPTechnicalDocumentation"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenERPTechnicalDocumentation"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/addons/web/doc/_static/openerp.png b/addons/web/doc/_static/openerp.png
new file mode 100644
index 00000000..caa1b21d
--- /dev/null
+++ b/addons/web/doc/_static/openerp.png
Binary files differ
diff --git a/addons/web/doc/_templates/sidebarintro.html b/addons/web/doc/_templates/sidebarintro.html
new file mode 100644
index 00000000..bbe4c472
--- /dev/null
+++ b/addons/web/doc/_templates/sidebarintro.html
@@ -0,0 +1,16 @@
+<p class="logo"><a href="https://doc.odoo.com/">
+ <img class="logo" src="{{ pathto('_static/openerp.png', 1) }}" alt="Logo"/>
+</a></p>
+
+<h3>Other Docs</h3>
+<ul>
+ <li><a href="https://doc.odoo.com/trunk">Odoo Developers Documentation</a></li>
+ <li><a href="https://doc.odoo.com/trunk/server">Odoo Server Developers Documentation</a></li>
+ <li><a href="https://doc.odoo.com/book">Odoo Users Documentation</a></li>
+</ul>
+
+<h3>Useful Links</h3>
+<ul>
+ <li><a href="https://www.odoo.com/">The Odoo website</a></li>
+ <li><a href="http://python.org/">The Python programming language</a></li>
+</ul>
diff --git a/addons/web/doc/_templates/sidebarlogo.html b/addons/web/doc/_templates/sidebarlogo.html
new file mode 100644
index 00000000..de6e3e5c
--- /dev/null
+++ b/addons/web/doc/_templates/sidebarlogo.html
@@ -0,0 +1,3 @@
+<p class="logo"><a href="{{ pathto(master_doc) }}">
+ <img class="logo" src="{{ pathto('_static/openerp.png', 1) }}" alt="Logo"/>
+</a></p>
diff --git a/addons/web/doc/_themes/LICENSE b/addons/web/doc/_themes/LICENSE
new file mode 100644
index 00000000..8daab7ee
--- /dev/null
+++ b/addons/web/doc/_themes/LICENSE
@@ -0,0 +1,37 @@
+Copyright (c) 2010 by Armin Ronacher.
+
+Some rights reserved.
+
+Redistribution and use in source and binary forms of the theme, with or
+without modification, are permitted provided that the following conditions
+are met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+* The names of the contributors may not be used to endorse or
+ promote products derived from this software without specific
+ prior written permission.
+
+We kindly ask you to only use these themes in an unmodified manner just
+for Flask and Flask-related products, not for unrelated projects. If you
+like the visual style and want to use it for your own projects, please
+consider making some larger changes to the themes (such as changing
+font faces, sizes, colors or margins).
+
+THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/addons/web/doc/_themes/README b/addons/web/doc/_themes/README
new file mode 100644
index 00000000..b3292bdf
--- /dev/null
+++ b/addons/web/doc/_themes/README
@@ -0,0 +1,31 @@
+Flask Sphinx Styles
+===================
+
+This repository contains sphinx styles for Flask and Flask related
+projects. To use this style in your Sphinx documentation, follow
+this guide:
+
+1. put this folder as _themes into your docs folder. Alternatively
+ you can also use git submodules to check out the contents there.
+2. add this to your conf.py:
+
+ sys.path.append(os.path.abspath('_themes'))
+ html_theme_path = ['_themes']
+ html_theme = 'flask'
+
+The following themes exist:
+
+- 'flask' - the standard flask documentation theme for large
+ projects
+- 'flask_small' - small one-page theme. Intended to be used by
+ very small addon libraries for flask.
+
+The following options exist for the flask_small theme:
+
+ [options]
+ index_logo = '' filename of a picture in _static
+ to be used as replacement for the
+ h1 in the index.rst file.
+ index_logo_height = 120px height of the index logo
+ github_fork = '' repository name on github for the
+ "fork me" badge
diff --git a/addons/web/doc/_themes/flask/layout.html b/addons/web/doc/_themes/flask/layout.html
new file mode 100644
index 00000000..ad08eccd
--- /dev/null
+++ b/addons/web/doc/_themes/flask/layout.html
@@ -0,0 +1,25 @@
+{%- extends "basic/layout.html" %}
+{%- block extrahead %}
+ {{ super() }}
+ {% if theme_touch_icon %}
+ <link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
+ {% endif %}
+ <link media="only screen and (max-device-width: 480px)" href="{{
+ pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
+{% endblock %}
+{%- block relbar2 %}{% endblock %}
+{% block header %}
+ {{ super() }}
+ {% if pagename == 'index' %}
+ <div class=indexwrapper>
+ {% endif %}
+{% endblock %}
+{%- block footer %}
+ <div class="footer">
+ &copy; Copyright {{ copyright }}
+ Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> and a modified <a href="https://github.com/mitsuhiko/flask-sphinx-themes">Flask theme</a>.
+ </div>
+ {% if pagename == 'index' %}
+ </div>
+ {% endif %}
+{%- endblock %}
diff --git a/addons/web/doc/_themes/flask/relations.html b/addons/web/doc/_themes/flask/relations.html
new file mode 100644
index 00000000..3bbcde85
--- /dev/null
+++ b/addons/web/doc/_themes/flask/relations.html
@@ -0,0 +1,19 @@
+<h3>Related Topics</h3>
+<ul>
+ <li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
+ {%- for parent in parents %}
+ <li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
+ {%- endfor %}
+ {%- if prev %}
+ <li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
+ }}">{{ prev.title }}</a></li>
+ {%- endif %}
+ {%- if next %}
+ <li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
+ }}">{{ next.title }}</a></li>
+ {%- endif %}
+ {%- for parent in parents %}
+ </ul></li>
+ {%- endfor %}
+ </ul></li>
+</ul>
diff --git a/addons/web/doc/_themes/flask/static/flasky.css_t b/addons/web/doc/_themes/flask/static/flasky.css_t
new file mode 100644
index 00000000..9fec3190
--- /dev/null
+++ b/addons/web/doc/_themes/flask/static/flasky.css_t
@@ -0,0 +1,396 @@
+/*
+ * flasky.css_t
+ * ~~~~~~~~~~~~
+ *
+ * :copyright: Copyright 2010 by Armin Ronacher.
+ * :license: Flask Design License, see LICENSE for details.
+ */
+
+{% set page_width = '80em' %}
+{% set sidebar_width = '16em' %}
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+ font-family: 'Georgia', serif;
+ font-size: 15px;
+ background-color: white;
+ color: #000;
+ margin: 0;
+ padding: 0;
+}
+
+div.document {
+ width: {{ page_width }};
+ margin: 30px auto 0 auto;
+}
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+}
+
+div.bodywrapper {
+ margin: 0 0 0 {{ sidebar_width }};
+}
+
+div.sphinxsidebar {
+ width: {{ sidebar_width }};
+}
+
+hr {
+ border: 1px solid #B1B4B6;
+}
+
+div.body {
+ background-color: #ffffff;
+ color: #3E4349;
+ padding: 0 0px 0 0px;
+}
+
+img.floatingflask {
+ padding: 0 0 10px 10px;
+ float: right;
+}
+
+div.footer {
+ width: {{ page_width }};
+ margin: 20px auto 30px auto;
+ font-size: 12px;
+ color: #888;
+ text-align: right;
+}
+
+div.footer a {
+ color: #888;
+}
+
+div.related {
+ display: none;
+}
+
+div.sphinxsidebar a {
+ color: #444;
+ text-decoration: none;
+ border-bottom: 1px dotted #999;
+}
+
+div.sphinxsidebar a:hover {
+ border-bottom: 1px solid #999;
+}
+
+div.sphinxsidebar {
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+div.sphinxsidebarwrapper {
+ padding: 0px 10px;
+}
+
+div.sphinxsidebarwrapper p.logo {
+ padding: 0 0 20px 0;
+ margin: 0;
+ text-align: center;
+}
+
+div.sphinxsidebar h3,
+div.sphinxsidebar h4 {
+ font-family: 'Garamond', 'Georgia', serif;
+ color: #444;
+ font-size: 22px;
+ font-weight: normal;
+ margin: 0 0 5px 0;
+ padding: 0;
+}
+
+div.sphinxsidebar h4 {
+ font-size: 18px;
+}
+
+div.sphinxsidebar h3 a {
+ color: #444;
+}
+
+div.sphinxsidebar p.logo a,
+div.sphinxsidebar h3 a,
+div.sphinxsidebar p.logo a:hover,
+div.sphinxsidebar h3 a:hover {
+ border: none;
+}
+
+div.sphinxsidebar p {
+ color: #555;
+ margin: 10px 0;
+}
+
+div.sphinxsidebar ul {
+ margin: 10px 0;
+ padding: 0;
+ color: #000;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #ccc;
+ font-family: 'Georgia', serif;
+ font-size: 1em;
+}
+
+/* -- body styles ----------------------------------------------------------- */
+
+a {
+ color: #004B6B;
+ text-decoration: underline;
+}
+
+a:hover {
+ color: #6D4100;
+ text-decoration: underline;
+}
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+ font-family: 'Garamond', 'Georgia', serif;
+ font-weight: normal;
+ margin: 30px 0px 10px 0px;
+ padding: 0;
+}
+
+{% if theme_index_logo %}
+div.indexwrapper h1 {
+ /* We don't want it, I don't know why theme_index_logo is triggered. */
+ /* text-indent: -999999px; */
+ background: url({{ theme_index_logo }}) no-repeat center center;
+ height: {{ theme_index_logo_height }};
+}
+{% endif %}
+
+div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
+div.body h2 { font-size: 180%; }
+div.body h3 { font-size: 150%; }
+div.body h4 { font-size: 130%; }
+div.body h5 { font-size: 100%; }
+div.body h6 { font-size: 100%; }
+
+a.headerlink {
+ color: #ddd;
+ padding: 0 4px;
+ text-decoration: none;
+}
+
+a.headerlink:hover {
+ color: #444;
+ background: #eaeaea;
+}
+
+div.body p, div.body dd, div.body li {
+ line-height: 1.4em;
+}
+
+div.admonition {
+ background: #fafafa;
+ margin: 20px -30px;
+ padding: 10px 30px;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+}
+
+div.admonition tt.xref, div.admonition a tt {
+ border-bottom: 1px solid #fafafa;
+}
+
+dd div.admonition {
+ margin-left: -60px;
+ padding-left: 60px;
+}
+
+div.admonition p.admonition-title {
+ font-family: 'Garamond', 'Georgia', serif;
+ font-weight: normal;
+ font-size: 22px;
+ margin: 0 0 10px 0;
+ padding: 0;
+ line-height: 1;
+}
+
+div.admonition p.last {
+ margin-bottom: 0;
+}
+
+div.highlight {
+ background-color: white;
+}
+
+dt:target, .highlight {
+ background: #FAF3E8;
+}
+
+div.note {
+ background-color: #eee;
+ border: 1px solid #ccc;
+}
+
+div.seealso {
+ background-color: #ffc;
+ border: 1px solid #ff6;
+}
+
+div.topic {
+ background-color: #eee;
+}
+
+p.admonition-title {
+ display: inline;
+}
+
+p.admonition-title:after {
+ content: ":";
+}
+
+pre, tt {
+ font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+ font-size: 0.9em;
+}
+
+img.screenshot {
+}
+
+tt.descname, tt.descclassname {
+ font-size: 0.95em;
+}
+
+tt.descname {
+ padding-right: 0.08em;
+}
+
+img.screenshot {
+ -moz-box-shadow: 2px 2px 4px #eee;
+ -webkit-box-shadow: 2px 2px 4px #eee;
+ box-shadow: 2px 2px 4px #eee;
+}
+
+table.docutils {
+ border: 1px solid #888;
+ -moz-box-shadow: 2px 2px 4px #eee;
+ -webkit-box-shadow: 2px 2px 4px #eee;
+ box-shadow: 2px 2px 4px #eee;
+}
+
+table.docutils td, table.docutils th {
+ border: 1px solid #888;
+ padding: 0.25em 0.7em;
+}
+
+table.field-list, table.footnote {
+ border: none;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+table.footnote {
+ margin: 15px 0;
+ width: 100%;
+ border: 1px solid #eee;
+ background: #fdfdfd;
+ font-size: 0.9em;
+}
+
+table.footnote + table.footnote {
+ margin-top: -15px;
+ border-top: none;
+}
+
+table.field-list th {
+ padding: 0 0.8em 0 0;
+}
+
+table.field-list td {
+ padding: 0;
+}
+
+table.footnote td.label {
+ width: 0px;
+ padding: 0.3em 0 0.3em 0.5em;
+}
+
+table.footnote td {
+ padding: 0.3em 0.5em;
+}
+
+dl {
+ margin: 0;
+ padding: 0;
+}
+
+dl dd {
+ margin-left: 30px;
+}
+
+blockquote {
+ margin: 0 0 0 30px;
+ padding: 0;
+}
+
+ul, ol {
+ margin: 10px 0 10px 30px;
+ padding: 0;
+}
+
+pre {
+ background: #eee;
+ padding: 7px 30px;
+ margin: 15px -30px;
+ line-height: 1.3em;
+}
+
+dl pre, blockquote pre, li pre {
+ margin-left: -60px;
+ padding-left: 60px;
+}
+
+dl dl pre {
+ margin-left: -90px;
+ padding-left: 90px;
+}
+
+tt {
+ background-color: #ecf0f3;
+ color: #222;
+ /* padding: 1px 2px; */
+}
+
+tt.xref, a tt {
+ background-color: #FBFBFB;
+ border-bottom: 1px solid white;
+}
+
+a.reference {
+ text-decoration: none;
+ border-bottom: 1px dotted #004B6B;
+}
+
+a.reference:hover {
+ border-bottom: 1px solid #6D4100;
+}
+
+a.footnote-reference {
+ text-decoration: none;
+ font-size: 0.7em;
+ vertical-align: top;
+ border-bottom: 1px dotted #004B6B;
+}
+
+a.footnote-reference:hover {
+ border-bottom: 1px solid #6D4100;
+}
+
+a:hover tt {
+ background: #EEE;
+}
diff --git a/addons/web/doc/_themes/flask/static/small_flask.css b/addons/web/doc/_themes/flask/static/small_flask.css
new file mode 100644
index 00000000..1c6df309
--- /dev/null
+++ b/addons/web/doc/_themes/flask/static/small_flask.css
@@ -0,0 +1,70 @@
+/*
+ * small_flask.css_t
+ * ~~~~~~~~~~~~~~~~~
+ *
+ * :copyright: Copyright 2010 by Armin Ronacher.
+ * :license: Flask Design License, see LICENSE for details.
+ */
+
+body {
+ margin: 0;
+ padding: 20px 30px;
+}
+
+div.documentwrapper {
+ float: none;
+ background: white;
+}
+
+div.sphinxsidebar {
+ display: block;
+ float: none;
+ width: 102.5%;
+ margin: 50px -30px -20px -30px;
+ padding: 10px 20px;
+ background: #333;
+ color: white;
+}
+
+div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
+div.sphinxsidebar h3 a {
+ color: white;
+}
+
+div.sphinxsidebar a {
+ color: #aaa;
+}
+
+div.sphinxsidebar p.logo {
+ display: none;
+}
+
+div.document {
+ width: 100%;
+ margin: 0;
+}
+
+div.related {
+ display: block;
+ margin: 0;
+ padding: 10px 0 20px 0;
+}
+
+div.related ul,
+div.related ul li {
+ margin: 0;
+ padding: 0;
+}
+
+div.footer {
+ display: none;
+}
+
+div.bodywrapper {
+ margin: 0;
+}
+
+div.body {
+ min-height: 0;
+ padding: 0;
+}
diff --git a/addons/web/doc/_themes/flask/theme.conf b/addons/web/doc/_themes/flask/theme.conf
new file mode 100644
index 00000000..18c720f8
--- /dev/null
+++ b/addons/web/doc/_themes/flask/theme.conf
@@ -0,0 +1,9 @@
+[theme]
+inherit = basic
+stylesheet = flasky.css
+pygments_style = flask_theme_support.FlaskyStyle
+
+[options]
+index_logo = ''
+index_logo_height = 120px
+touch_icon =
diff --git a/addons/web/doc/_themes/flask_small/layout.html b/addons/web/doc/_themes/flask_small/layout.html
new file mode 100644
index 00000000..afd7a912
--- /dev/null
+++ b/addons/web/doc/_themes/flask_small/layout.html
@@ -0,0 +1,22 @@
+{% extends "basic/layout.html" %}
+{% block header %}
+ {{ super() }}
+ {% if pagename == 'index' %}
+ <div class=indexwrapper>
+ {% endif %}
+{% endblock %}
+{% block footer %}
+ {% if pagename == 'index' %}
+ </div>
+ {% endif %}
+{% endblock %}
+{# do not display relbars #}
+{% block relbar1 %}{% endblock %}
+{% block relbar2 %}
+ {% if theme_github_fork %}
+ <a target="_blank" href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
+ src="static/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
+ {% endif %}
+{% endblock %}
+{% block sidebar1 %}{% endblock %}
+{% block sidebar2 %}{% endblock %}
diff --git a/addons/web/doc/_themes/flask_small/static/flasky.css_t b/addons/web/doc/_themes/flask_small/static/flasky.css_t
new file mode 100644
index 00000000..fe2141c5
--- /dev/null
+++ b/addons/web/doc/_themes/flask_small/static/flasky.css_t
@@ -0,0 +1,287 @@
+/*
+ * flasky.css_t
+ * ~~~~~~~~~~~~
+ *
+ * Sphinx stylesheet -- flasky theme based on nature theme.
+ *
+ * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+ font-family: 'Georgia', serif;
+ font-size: 17px;
+ color: #000;
+ background: white;
+ margin: 0;
+ padding: 0;
+}
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+}
+
+div.bodywrapper {
+ margin: 40px auto 0 auto;
+ width: 700px;
+}
+
+hr {
+ border: 1px solid #B1B4B6;
+}
+
+div.body {
+ background-color: #ffffff;
+ color: #3E4349;
+ padding: 0 30px 30px 30px;
+}
+
+img.floatingflask {
+ padding: 0 0 10px 10px;
+ float: right;
+}
+
+div.footer {
+ text-align: right;
+ color: #888;
+ padding: 10px;
+ font-size: 14px;
+ width: 650px;
+ margin: 0 auto 40px auto;
+}
+
+div.footer a {
+ color: #888;
+ text-decoration: underline;
+}
+
+div.related {
+ line-height: 32px;
+ color: #888;
+}
+
+div.related ul {
+ padding: 0 0 0 10px;
+}
+
+div.related a {
+ color: #444;
+}
+
+/* -- body styles ----------------------------------------------------------- */
+
+a {
+ color: #004B6B;
+ text-decoration: underline;
+}
+
+a:hover {
+ color: #6D4100;
+ text-decoration: underline;
+}
+
+div.body {
+ padding-bottom: 40px; /* saved for footer */
+}
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+ font-family: 'Garamond', 'Georgia', serif;
+ font-weight: normal;
+ margin: 30px 0px 10px 0px;
+ padding: 0;
+}
+
+{% if theme_index_logo %}
+div.indexwrapper h1 {
+ text-indent: -999999px;
+ background: url({{ theme_index_logo }}) no-repeat center center;
+ height: {{ theme_index_logo_height }};
+}
+{% endif %}
+
+div.body h2 { font-size: 180%; }
+div.body h3 { font-size: 150%; }
+div.body h4 { font-size: 130%; }
+div.body h5 { font-size: 100%; }
+div.body h6 { font-size: 100%; }
+
+a.headerlink {
+ color: white;
+ padding: 0 4px;
+ text-decoration: none;
+}
+
+a.headerlink:hover {
+ color: #444;
+ background: #eaeaea;
+}
+
+div.body p, div.body dd, div.body li {
+ line-height: 1.4em;
+}
+
+div.admonition {
+ background: #fafafa;
+ margin: 20px -30px;
+ padding: 10px 30px;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+}
+
+div.admonition p.admonition-title {
+ font-family: 'Garamond', 'Georgia', serif;
+ font-weight: normal;
+ font-size: 24px;
+ margin: 0 0 10px 0;
+ padding: 0;
+ line-height: 1;
+}
+
+div.admonition p.last {
+ margin-bottom: 0;
+}
+
+div.highlight{
+ background-color: white;
+}
+
+dt:target, .highlight {
+ background: #FAF3E8;
+}
+
+div.note {
+ background-color: #eee;
+ border: 1px solid #ccc;
+}
+
+div.seealso {
+ background-color: #ffc;
+ border: 1px solid #ff6;
+}
+
+div.topic {
+ background-color: #eee;
+}
+
+div.warning {
+ background-color: #ffe4e4;
+ border: 1px solid #f66;
+}
+
+p.admonition-title {
+ display: inline;
+}
+
+p.admonition-title:after {
+ content: ":";
+}
+
+pre, tt {
+ font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+ font-size: 0.85em;
+}
+
+img.screenshot {
+}
+
+tt.descname, tt.descclassname {
+ font-size: 0.95em;
+}
+
+tt.descname {
+ padding-right: 0.08em;
+}
+
+img.screenshot {
+ -moz-box-shadow: 2px 2px 4px #eee;
+ -webkit-box-shadow: 2px 2px 4px #eee;
+ box-shadow: 2px 2px 4px #eee;
+}
+
+table.docutils {
+ border: 1px solid #888;
+ -moz-box-shadow: 2px 2px 4px #eee;
+ -webkit-box-shadow: 2px 2px 4px #eee;
+ box-shadow: 2px 2px 4px #eee;
+}
+
+table.docutils td, table.docutils th {
+ border: 1px solid #888;
+ padding: 0.25em 0.7em;
+}
+
+table.field-list, table.footnote {
+ border: none;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+table.footnote {
+ margin: 15px 0;
+ width: 100%;
+ border: 1px solid #eee;
+}
+
+table.field-list th {
+ padding: 0 0.8em 0 0;
+}
+
+table.field-list td {
+ padding: 0;
+}
+
+table.footnote td {
+ padding: 0.5em;
+}
+
+dl {
+ margin: 0;
+ padding: 0;
+}
+
+dl dd {
+ margin-left: 30px;
+}
+
+pre {
+ padding: 0;
+ margin: 15px -30px;
+ padding: 8px;
+ line-height: 1.3em;
+ padding: 7px 30px;
+ background: #eee;
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+}
+
+dl pre {
+ margin-left: -60px;
+ padding-left: 60px;
+}
+
+tt {
+ background-color: #ecf0f3;
+ color: #222;
+ /* padding: 1px 2px; */
+}
+
+tt.xref, a tt {
+ background-color: #FBFBFB;
+}
+
+a:hover tt {
+ background: #EEE;
+}
diff --git a/addons/web/doc/_themes/flask_small/static/forkme_right_darkblue_121621.png b/addons/web/doc/_themes/flask_small/static/forkme_right_darkblue_121621.png
new file mode 100644
index 00000000..146ef8a8
--- /dev/null
+++ b/addons/web/doc/_themes/flask_small/static/forkme_right_darkblue_121621.png
Binary files differ
diff --git a/addons/web/doc/_themes/flask_small/theme.conf b/addons/web/doc/_themes/flask_small/theme.conf
new file mode 100644
index 00000000..542b4625
--- /dev/null
+++ b/addons/web/doc/_themes/flask_small/theme.conf
@@ -0,0 +1,10 @@
+[theme]
+inherit = basic
+stylesheet = flasky.css
+nosidebar = true
+pygments_style = flask_theme_support.FlaskyStyle
+
+[options]
+index_logo = ''
+index_logo_height = 120px
+github_fork = ''
diff --git a/addons/web/doc/_themes/flask_theme_support.py b/addons/web/doc/_themes/flask_theme_support.py
new file mode 100644
index 00000000..33f47449
--- /dev/null
+++ b/addons/web/doc/_themes/flask_theme_support.py
@@ -0,0 +1,86 @@
+# flasky extensions. flasky pygments style based on tango style
+from pygments.style import Style
+from pygments.token import Keyword, Name, Comment, String, Error, \
+ Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
+
+
+class FlaskyStyle(Style):
+ background_color = "#f8f8f8"
+ default_style = ""
+
+ styles = {
+ # No corresponding class for the following:
+ #Text: "", # class: ''
+ Whitespace: "underline #f8f8f8", # class: 'w'
+ Error: "#a40000 border:#ef2929", # class: 'err'
+ Other: "#000000", # class 'x'
+
+ Comment: "italic #8f5902", # class: 'c'
+ Comment.Preproc: "noitalic", # class: 'cp'
+
+ Keyword: "bold #004461", # class: 'k'
+ Keyword.Constant: "bold #004461", # class: 'kc'
+ Keyword.Declaration: "bold #004461", # class: 'kd'
+ Keyword.Namespace: "bold #004461", # class: 'kn'
+ Keyword.Pseudo: "bold #004461", # class: 'kp'
+ Keyword.Reserved: "bold #004461", # class: 'kr'
+ Keyword.Type: "bold #004461", # class: 'kt'
+
+ Operator: "#582800", # class: 'o'
+ Operator.Word: "bold #004461", # class: 'ow' - like keywords
+
+ Punctuation: "bold #000000", # class: 'p'
+
+ # because special names such as Name.Class, Name.Function, etc.
+ # are not recognized as such later in the parsing, we choose them
+ # to look the same as ordinary variables.
+ Name: "#000000", # class: 'n'
+ Name.Attribute: "#c4a000", # class: 'na' - to be revised
+ Name.Builtin: "#004461", # class: 'nb'
+ Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
+ Name.Class: "#000000", # class: 'nc' - to be revised
+ Name.Constant: "#000000", # class: 'no' - to be revised
+ Name.Decorator: "#888", # class: 'nd' - to be revised
+ Name.Entity: "#ce5c00", # class: 'ni'
+ Name.Exception: "bold #cc0000", # class: 'ne'
+ Name.Function: "#000000", # class: 'nf'
+ Name.Property: "#000000", # class: 'py'
+ Name.Label: "#f57900", # class: 'nl'
+ Name.Namespace: "#000000", # class: 'nn' - to be revised
+ Name.Other: "#000000", # class: 'nx'
+ Name.Tag: "bold #004461", # class: 'nt' - like a keyword
+ Name.Variable: "#000000", # class: 'nv' - to be revised
+ Name.Variable.Class: "#000000", # class: 'vc' - to be revised
+ Name.Variable.Global: "#000000", # class: 'vg' - to be revised
+ Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
+
+ Number: "#990000", # class: 'm'
+
+ Literal: "#000000", # class: 'l'
+ Literal.Date: "#000000", # class: 'ld'
+
+ String: "#4e9a06", # class: 's'
+ String.Backtick: "#4e9a06", # class: 'sb'
+ String.Char: "#4e9a06", # class: 'sc'
+ String.Doc: "italic #8f5902", # class: 'sd' - like a comment
+ String.Double: "#4e9a06", # class: 's2'
+ String.Escape: "#4e9a06", # class: 'se'
+ String.Heredoc: "#4e9a06", # class: 'sh'
+ String.Interpol: "#4e9a06", # class: 'si'
+ String.Other: "#4e9a06", # class: 'sx'
+ String.Regex: "#4e9a06", # class: 'sr'
+ String.Single: "#4e9a06", # class: 's1'
+ String.Symbol: "#4e9a06", # class: 'ss'
+
+ Generic: "#000000", # class: 'g'
+ Generic.Deleted: "#a40000", # class: 'gd'
+ Generic.Emph: "italic #000000", # class: 'ge'
+ Generic.Error: "#ef2929", # class: 'gr'
+ Generic.Heading: "bold #000080", # class: 'gh'
+ Generic.Inserted: "#00A000", # class: 'gi'
+ Generic.Output: "#888", # class: 'go'
+ Generic.Prompt: "#745334", # class: 'gp'
+ Generic.Strong: "bold #000000", # class: 'gs'
+ Generic.Subheading: "bold #800080", # class: 'gu'
+ Generic.Traceback: "bold #a40000", # class: 'gt'
+ }
diff --git a/addons/web/doc/addon-structure.txt b/addons/web/doc/addon-structure.txt
new file mode 100644
index 00000000..19b38689
--- /dev/null
+++ b/addons/web/doc/addon-structure.txt
@@ -0,0 +1,12 @@
+<addon name>
+ +-- __manifest__.py
+ +-- controllers/
+ +-- static/
+ +-- lib/
+ +-- src/
+ +-- css/
+ +-- img/
+ +-- js/
+ +-- xml/
+ +-- test/
+ +-- test/
diff --git a/addons/web/doc/client_action.rst b/addons/web/doc/client_action.rst
new file mode 100644
index 00000000..4852400d
--- /dev/null
+++ b/addons/web/doc/client_action.rst
@@ -0,0 +1,114 @@
+.. highlight:: javascript
+
+Client actions
+==============
+
+Client actions are the client-side version of OpenERP's "Server
+Actions": instead of allowing for semi-arbitrary code to be executed
+in the server, they allow for execution of client-customized code.
+
+On the server side, a client action is an action of type
+``ir.actions.client``, which has (at most) two properties: a mandatory
+``tag``, which is an arbitrary string by which the client will
+identify the action, and an optional ``params`` which is simply a map
+of keys and values sent to the client as-is (this way, client actions
+can be made generic and reused in multiple contexts).
+
+General Structure
+-----------------
+
+In the OpenERP Web code, a client action only requires two pieces of
+information:
+
+* Mapping the action's ``tag`` to an object
+
+* Providing said object. Two different types of objects can be mapped
+ to a client action:
+
+ * An OpenERP Web widget, which must inherit from
+ :js:class:`openerp.web.Widget`
+
+ * A regular javascript function
+
+The major difference is in the lifecycle of these:
+
+* if the client action maps to a function, the function will be called
+ when executing the action. The function can have no further
+ interaction with the Web Client itself, although it can return an
+ action which will be executed after it.
+
+ The function takes 2 parameters: the ActionManager calling it and
+ the descriptor for the current action (the ``ir.actions.client``
+ dictionary).
+
+* if, on the other hand, the client action maps to a
+ :js:class:`~openerp.web.Widget`, that
+ :js:class:`~openerp.web.Widget` will be instantiated and added to
+ the web client's canvas, with the usual
+ :js:class:`~openerp.web.Widget` lifecycle (essentially, it will
+ either take over the content area of the client or it will be
+ integrated within a dialog).
+
+For example, to create a client action displaying a ``res.widget``
+object::
+
+ // Registers the object 'openerp.web_dashboard.Widget' to the client
+ // action tag 'board.home.widgets'
+ instance.web.client_actions.add(
+ 'board.home.widgets', 'instance.web_dashboard.Widget');
+ instance.web_dashboard.Widget = instance.web.Widget.extend({
+ template: 'HomeWidget'
+ });
+
+At this point, the generic :js:class:`~openerp.web.Widget` lifecycle
+takes over, the template is rendered, inserted in the client DOM,
+bound on the object's ``$el`` property and the object is started.
+
+The second parameter to the constructor is the descriptor for the
+action itself, which contains any parameter provided::
+
+ init: function (parent, action) {
+ // execute the Widget's init
+ this._super(parent);
+ // board.home.widgets only takes a single param, the identifier of the
+ // res.widget object it should display. Store it for later
+ this.widget_id = action.params.widget_id;
+ }
+
+More complex initialization (DOM manipulations, RPC requests, ...)
+should be performed in the :js:func:`~openerp.web.Widget.start()`
+method.
+
+.. note::
+
+ As required by :js:class:`~openerp.web.Widget`'s contract, if
+ :js:func:`~openerp.web.Widget.start()` executes any asynchronous
+ code it should return a ``Promise`` so callers know when it's
+ ready for interaction.
+
+.. code-block:: javascript
+
+ start: function () {
+ return Promise.all([
+ this._super(),
+ // Simply read the res.widget object this action should display
+ new instance.web.Model('res.widget').call(
+ 'read', [[this.widget_id], ['title']])
+ .then(this.proxy('on_widget_loaded')
+ ]);
+ }
+
+The client action can then behave exactly as it wishes to within its
+root (``this.$el``). In this case, it performs further renderings once
+its widget's content is retrieved::
+
+ on_widget_loaded: function (widgets) {
+ var widget = widgets[0];
+ var url = _.sprintf(
+ '/web_dashboard/widgets/content?session_id=%s&widget_id=%d',
+ this.session.session_id, widget.id);
+ this.$el.html(QWeb.render('HomeWidget.content', {
+ widget: widget,
+ url: url
+ }));
+ }
diff --git a/addons/web/doc/conf.py b/addons/web/doc/conf.py
new file mode 100644
index 00000000..111018ad
--- /dev/null
+++ b/addons/web/doc/conf.py
@@ -0,0 +1,260 @@
+# -*- coding: utf-8 -*-
+#
+# OpenERP Technical Documentation configuration file, created by
+# sphinx-quickstart on Fri Feb 17 16:14:06 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.append(os.path.abspath('_themes'))
+sys.path.insert(0, os.path.abspath('../addons'))
+sys.path.insert(0, os.path.abspath('..'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = [
+ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
+ 'sphinx.ext.todo', 'sphinx.ext.viewcode',
+ 'patchqueue'
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'OpenERP Web Developers Documentation'
+copyright = u'2012, OpenERP s.a.'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '7.0'
+# The full version, including alpha/beta/rc tags.
+release = '7.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'flask'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['_themes']
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+html_sidebars = {
+ 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
+ '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
+ 'sourcelink.html', 'searchbox.html']
+}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'openerp-web-doc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'openerp-web-doc.tex', u'OpenERP Web Developers Documentation',
+ u'OpenERP s.a.', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'openerp-web-doc', u'OpenERP Web Developers Documentation',
+ [u'OpenERP s.a.'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'OpenERPWebDocumentation', u'OpenERP Web Developers Documentation',
+ u'OpenERP s.a.', 'OpenERPWebDocumentation', 'Developers documentation for the openerp-web project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+todo_include_todos = True
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {
+ 'python': ('http://docs.python.org/', None),
+ 'openerpserver': ('http://doc.openerp.com/trunk/developers/server', None),
+}
diff --git a/addons/web/doc/form_view.rst b/addons/web/doc/form_view.rst
new file mode 100644
index 00000000..98d4a4de
--- /dev/null
+++ b/addons/web/doc/form_view.rst
@@ -0,0 +1,55 @@
+Notes on the usage of the Form View as a sub-widget
+===================================================
+
+Undocumented stuff
+------------------
+
+* ``initial_mode`` *option* defines the starting mode of the form
+ view, one of ``view`` and ``edit`` (?). Default value is ``view``
+ (non-editable form).
+
+* ``embedded_view`` *attribute* has to be set separately when
+ providing a view directly, no option available for that usage.
+
+ * View arch **must** contain node with
+ ``@class="oe_form_container"``, otherwise everything will break
+ without any info
+
+ * Root element of view arch not being ``form`` may or may not work
+ correctly, no idea.
+
+ * Freeform views => ``@version="7.0"``
+
+* Form is not entirely loaded (some widgets may not appear) unless
+ ``on_record_loaded`` is called (or ``do_show``, which itself calls
+ ``on_record_loaded``).
+
+* "Empty" form => ``on_button_new`` (...), or manually call
+ ``default_get`` + ``on_record_loaded``
+
+* Form fields default to width: 100%, padding, !important margin, can
+ be reached via ``.oe_form_field``
+
+* Form *will* render buttons and a pager, offers options to locate
+ both outside of form itself (``$buttons`` and ``$pager``), providing
+ empty jquery objects (``$()``) seems to stop displaying both but not
+ sure if there are deleterious side-effects.
+
+ Other options:
+
+ * Pass in ``$(document.createDocumentFragment)`` to ensure it's a
+ DOM-compatible tree completely outside of the actual DOM.
+
+ * ???
+
+* readonly fields probably don't have a background, beware if need of
+ overlay
+
+ * What is the difference between ``readonly`` and
+ ``effective_readonly``?
+
+* No facilities for DOM events handling/delegations e.g. handling
+ keyup/keydown/keypress from a form fields into the form's user.
+
+ * Also no way to reverse from a DOM node (e.g. DOMEvent#target) back to a
+ form view field easily
diff --git a/addons/web/doc/guidelines.rst b/addons/web/doc/guidelines.rst
new file mode 100644
index 00000000..8721d372
--- /dev/null
+++ b/addons/web/doc/guidelines.rst
@@ -0,0 +1,194 @@
+Guidelines and Recommendations
+==============================
+
+Web Module Recommendations
+--------------------------
+
+Identifiers (``id`` attribute) should be avoided
+''''''''''''''''''''''''''''''''''''''''''''''''
+
+In generic applications and modules, ``@id`` limits the reusabily of
+components and tends to make code more brittle.
+
+Just about all the time, they can be replaced with nothing, with
+classes or with keeping a reference to a DOM node or a jQuery element
+around.
+
+.. note::
+
+ If it is *absolutely necessary* to have an ``@id`` (because a
+ third-party library requires one and can't take a DOM element), it
+ should be generated with `_.uniqueId
+ <http://underscorejs.org/#uniqueId>`_ or some other similar
+ method.
+
+Avoid predictable/common CSS class names
+''''''''''''''''''''''''''''''''''''''''
+
+Class names such as "content" or "navigation" might match the desired
+meaning/semantics, but it is likely an other developer will have the
+same need, creating a naming conflict and unintended behavior. Generic
+class names should be prefixed with e.g. the name of the component
+they belong to (creating "informal" namespaces, much as in C or
+Objective-C)
+
+Global selectors should be avoided
+''''''''''''''''''''''''''''''''''
+
+Because a component may be used several times in a single page (an
+example in OpenERP is dashboards), queries should be restricted to a
+given component's scope. Unfiltered selections such as ``$(selector)``
+or ``document.querySelectorAll(selector)`` will generally lead to
+unintended or incorrect behavior.
+
+OpenERP Web's :js:class:`~openerp.web.Widget` has an attribute
+providing its DOM root :js:attr:`Widget.$el <openerp.web.Widget.$el>`,
+and a shortcut to select nodes directly :js:attr:`Widget.$
+<openerp.web.Widget.$>`.
+
+More generally, never assume your components own or controls anything
+beyond its own personal DOM.
+
+Understand deferreds
+''''''''''''''''''''
+
+Deferreds, promises, futures, …
+
+Known under many names, these objects are essential to and (in OpenERP
+Web) widely used for making :doc:`asynchronous javascript operations
+<async>` palatable and understandable.
+
+OpenERP Web guidelines
+----------------------
+
+* HTML templating/rendering should use :doc:`qweb` unless absolutely
+ trivial.
+
+* All interactive components (components displaying information to the
+ screen or intercepting DOM events) must inherit from
+ :class:`~openerp.web.Widget` and correctly implement and use its API
+ and lifecycle.
+
+* All css classes must be prefixed with *oe_* .
+
+* Asynchronous functions (functions which call :ref:`session.rpc
+ <rpc_rpc>` directly or indirectly at the very least) *must* return
+ deferreds, so that callers of overriders can correctly synchronize
+ with them.
+
+New Javascript guidelines
+-------------------------
+
+From v11, we introduce a new coding standard for Odoo Javascript code. Here it
+is:
+
+* add "use strict"; on top of every odoo JS module
+
+* name all entities exported by a JS module. So, instead of
+
+ .. code-block:: javascript
+
+ return Widget.extend({
+ ...
+ });
+
+you should use:
+
+ .. code-block:: javascript
+
+ var MyWidget = Widget.extend({
+ ...
+ });
+
+ return MyWidget
+
+* there should be one space between function and the left parenthesis:
+
+ .. code-block:: javascript
+
+ function (a, b) {}
+
+* JS files should have a (soft) limit of 80 chars width, and a hard limit of 100
+
+* document every functions and every files, with the style JSDoc.
+
+* for function overriding other functions, consider adding the tag @override in
+ the JS Doc. Also, you can mention which method is overridden:
+
+ .. code-block:: javascript
+
+ /**
+ * When a save operation has been confirmed from the model, this method is
+ * called.
+ *
+ * @override method from field manager mixin
+ * @param {string} id
+ * @returns {Deferred}
+ */
+ _confirmSave: function (id) {
+
+* there should be an empty line between the main function comments and the tags,
+ or parameter descriptions
+
+* avoid introspection: don't build dynamically a method name and call it. It is
+ more fragile and more difficult to refactor
+
+* methods should be private if possible
+
+* never read an attribute of an attribute on somethig that you have a reference.
+ So, this is not good:
+
+ .. code-block:: javascript
+
+ this.myObject.propA.propB
+
+* never use a reference to the parent widget
+
+* avoid using the 'include' functionality: extending a class is fine and does
+ not cause issue, including a class is much more fragile, and may not work.
+
+* For the widgets, here is how the various attributes/functions should be
+ ordered:
+
+ 1. all static attributes, such as template, events, custom_events, ...
+
+ 2. all methods from the lifecycle of a widget, in this order: init, willStart,
+ start, destroy
+
+ 3. If there are public methods, a section titled "Public", with an empty line
+ before and after
+
+ 4. all public methods, camelcased, in alphabetic order
+
+ 5. If there are private methods, a section titled "Private", with an empty line
+ before and after
+
+ 6. all private methods, camelcased and prefixed with _, in alphabetic order
+
+ 7. If there are event handlers, a section titled "Handlers", with an empty line
+ before and after
+
+ 8. all handlers, camelcased and prefixed with _on, in alphabetic order
+
+ 9. If there are static methods, they should be in a section titled "Static".
+ All static methods are considered public, camelcased with no _.
+
+* write unit tests
+
+* for the event handlers defined by the key 'event' or 'custom_events', don't
+ inline the function. Always add a string name, and add the definition in the
+ handler section
+
+* one space after if and for
+
+* never call private methods on another object
+
+* object definition on more than one line: each element should have a trailing
+ comma.
+
+* strings: double quotes for all textual strings (such as "Hello"), and single
+ quotes for all other strings, such as a css selector '.o_form_view'
+
+* always use this._super.apply(this, arguments);
+
+* keys in an object: ordered by alphabetic order \ No newline at end of file
diff --git a/addons/web/doc/images/db-query.png b/addons/web/doc/images/db-query.png
new file mode 100644
index 00000000..e063b724
--- /dev/null
+++ b/addons/web/doc/images/db-query.png
Binary files differ
diff --git a/addons/web/doc/images/runner.png b/addons/web/doc/images/runner.png
new file mode 100644
index 00000000..bd48e9d2
--- /dev/null
+++ b/addons/web/doc/images/runner.png
Binary files differ
diff --git a/addons/web/doc/images/runner2.png b/addons/web/doc/images/runner2.png
new file mode 100644
index 00000000..38ea2949
--- /dev/null
+++ b/addons/web/doc/images/runner2.png
Binary files differ
diff --git a/addons/web/doc/images/tests.png b/addons/web/doc/images/tests.png
new file mode 100644
index 00000000..84083d9e
--- /dev/null
+++ b/addons/web/doc/images/tests.png
Binary files differ
diff --git a/addons/web/doc/images/tests2.png b/addons/web/doc/images/tests2.png
new file mode 100644
index 00000000..c8a6f8ae
--- /dev/null
+++ b/addons/web/doc/images/tests2.png
Binary files differ
diff --git a/addons/web/doc/images/tests3.png b/addons/web/doc/images/tests3.png
new file mode 100644
index 00000000..247f7071
--- /dev/null
+++ b/addons/web/doc/images/tests3.png
Binary files differ
diff --git a/addons/web/doc/index.rst b/addons/web/doc/index.rst
new file mode 100644
index 00000000..5a862d29
--- /dev/null
+++ b/addons/web/doc/index.rst
@@ -0,0 +1,55 @@
+.. OpenERP Web documentation master file, created by
+ sphinx-quickstart on Fri Mar 18 16:31:55 2011.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+OpenERP Web Reference Documentation
+===================================
+
+See also the `OpenERP Web Training`_.
+
+.. _OpenERP Web Training: https://doc.openerp.com/trunk/training/
+
+Basics
+------
+
+.. toctree::
+ :maxdepth: 1
+
+ module
+ changelog-7.0
+
+Server-Side Web Framework
+-------------------------
+
+.. toctree::
+ :maxdepth: 1
+
+ web_controllers
+
+Javascript
+----------
+
+.. toctree::
+ :maxdepth: 1
+
+ guidelines
+ client_action
+ testing
+
+Views
+-----
+
+.. toctree::
+ :maxdepth: 1
+
+ search_view
+ list_view
+ form_view
+
+Indices and tables
+------------------
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/addons/web/doc/list_view.rst b/addons/web/doc/list_view.rst
new file mode 100644
index 00000000..f73abafe
--- /dev/null
+++ b/addons/web/doc/list_view.rst
@@ -0,0 +1,531 @@
+List View
+=========
+
+Style Hooks
+-----------
+
+The list view provides a few style hook classes for re-styling of list views in
+various situations:
+
+``.oe_list``
+
+ The root element of the list view, styling rules should be rooted
+ on that class.
+
+``table.oe_list_content``
+
+ The root table for the listview, accessory components may be
+ generated or added outside this section, this is the list view
+ "proper".
+
+``.oe_list_buttons``
+
+ The action buttons array for the list view, with its sub-elements
+
+ ``.oe_list_add``
+
+ The default "Create"/"Add" button of the list view
+
+ ``.oe_alternative``
+
+ The "alternative choice" for the list view, by default text
+ along the lines of "or import" with a link.
+
+``.oe_list_field_cell``
+
+ The cell (``td``) for a given field of the list view, cells which
+ are *not* fields (e.g. name of a group, or number of items in a
+ group) will not have this class. The field cell can be further
+ specified:
+
+ ``.oe_number``
+
+ Numeric cell types (integer and float)
+
+ ``.oe_button``
+
+ Action button (``button`` tag in the view) inside the cell
+
+ ``.o_readonly_modifier``
+
+ Readonly field cell
+
+ ``.oe_list_field_$type``
+
+ Additional class for the precise type of the cell, ``$type``
+ is the field's @widget if there is one, otherwise it's the
+ field's type.
+
+``.oe_list_record_selector``
+
+ Selector cells
+
+Editable list view
+++++++++++++++++++
+
+The editable list view module adds a few supplementary style hook
+classes, for edition situations:
+
+``.o_list_editable``
+
+ Added to the ``.oe_list`` when the list is editable (however that
+ was done). The class may be removed on-the-fly if the list becomes
+ non-editable.
+
+``.oe_editing``
+
+ Added to both ``.oe_list`` and ``.oe_list_button`` (as the
+ buttons may be outside of the list view) when a row of the list is
+ currently being edited.
+
+``tr.oe_edition``
+
+ Class set on the row being edited itself. Note that the edition
+ form is *not* contained within the row, this allows for styling or
+ modifying the row while it's being edited separately. Mostly for
+ fields which can not be edited (e.g. read-only fields).
+
+Columns display customization
+-----------------------------
+
+The list view provides a registry to
+:js:class:`openerp.web.list.Column` objects allowing for the
+customization of a column's display (e.g. so that a binary field is
+rendered as a link to the binary file directly in the list view).
+
+The registry is ``instance.web.list.columns``, the keys are of the
+form ``tag.type`` where ``tag`` can be ``field`` or ``button``, and
+``type`` can be either the field's type or the field's ``@widget`` (in
+the view).
+
+Most of the time, you'll want to define a ``tag.widget`` key
+(e.g. ``field.progressbar``).
+
+.. js:class:: openerp.web.list.Column(id, tag, attrs)
+
+ .. js:function:: openerp.web.list.Column.format(record_data, options)
+
+ Top-level formatting method, returns an empty string if the
+ column is invisible (unless the ``process_modifiers=false``
+ option is provided); returns ``options.value_if_empty`` or an
+ empty string if there is no value in the record for the
+ column.
+
+ Otherwise calls :js:func:`~openerp.web.list.Column._format`
+ and returns its result.
+
+ This method only needs to be overridden if the column has no
+ concept of values (and needs to bypass that check), for a
+ button for instance.
+
+ Otherwise, custom columns should generally override
+ :js:func:`~openerp.web.list.Column._format` instead.
+
+ :returns: String
+
+ .. js:function:: openerp.web.list.Column._format(record_data, options)
+
+ Never called directly, called if the column is visible and has
+ a value.
+
+ The default implementation calls
+ :js:func:`~openerp.web.format_value` and htmlescapes the
+ result (via ``_.escape``).
+
+ Note that the implementation of
+ :js:func:`~openerp.web.list.Column._format` *must* escape the
+ data provided to it, its output will *not* be escaped by
+ :js:func:`~openerp.web.list.Column.format`.
+
+ :returns: String
+
+Editable list view
+------------------
+
+List view edition is an extension to the base listview providing the
+capability of inline record edition by delegating to an embedded form
+view.
+
+Editability status
+++++++++++++++++++
+
+The editability status of a list view can be queried through the
+:js:func:`~openerp.web.ListView.editable` method, will return a falsy
+value if the listview is not currently editable.
+
+The editability status is based on three flags:
+
+``tree/@editable``
+
+ If present, can be either ``"top"`` or ``"bottom"``. Either will
+ make the list view editable, with new records being respectively
+ created at the top or at the bottom of the view.
+
+``context.set_editable``
+
+ Boolean flag extracted from a search context (during the
+ :js:func:`~openerp.web.ListView.do_search`` handler), ``true``
+ will make the view editable (from the top), ``false`` or the
+ absence of the flag is a noop.
+
+``defaults.editable``
+
+ Like ``tree/@editable``, one of absent (``null``)), ``"top"`` or
+ ``"bottom"``, fallback for the list view if none of the previous
+ two flags are set.
+
+These three flags can only *make* a listview editable, they can *not*
+override a previously set flag. To do that, a listview user should
+instead cancel :ref:`the edit:before event <listview-edit-before>`.
+
+The editable list view module adds a number of methods to the list
+view, on top of implementing the :js:class:`EditorDelegate` protocol:
+
+Interaction Methods
++++++++++++++++++++
+
+.. js:function:: openerp.web.ListView.ensure_saved
+
+ Attempts to resolve the pending edition, if any, by saving the
+ edited row's current state.
+
+ :returns: delegate resolving to all editions having been saved, or
+ rejected if a pending edition could not be saved
+ (e.g. validation failure)
+
+.. js:function:: openerp.web.ListView.start_edition([record][, options])
+
+ Starts editing the provided record inline, through an overlay form
+ view of editable fields in the record.
+
+ If no record is provided, creates a new one according to the
+ editability configuration of the list view.
+
+ This method resolves any pending edition when invoked, before
+ starting a new edition.
+
+ :param record: record to edit, or null to create a new record
+ :type record: :js:class:`~openerp.web.list.Record`
+ :param EditOptions options:
+ :returns: delegate to the form used for the edition
+
+.. js:function:: openerp.web.ListView.save_edition
+
+ Resolves the pending edition.
+
+ :returns: delegate to the save being completed, resolves to an
+ object with two attributes ``created`` (flag indicating
+ whether the saved record was just created or was
+ updated) and ``record`` the reloaded record having been
+ edited.
+
+.. js:function:: openerp.web.ListView.cancel_edition([force=false])
+
+ Cancels pending edition, cleans up the list view in case of
+ creation (removes the empty record being created).
+
+ :param Boolean force: doesn't check if the user has added any
+ data, discards the edition unconditionally
+
+Utility Methods
++++++++++++++++
+
+.. js:function:: openerp.web.ListView.get_cells_for(row)
+
+ Extracts the cells from a listview row, and puts them in a
+ {fieldname: cell} mapping for analysis and manipulation.
+
+ :param jQuery row:
+ :rtype: Object
+
+.. js:function:: openerp.web.ListView.with_event(event_name, event, action[, args][, trigger_params])
+
+ Executes ``action`` in the context of the view's editor,
+ bracketing it with cancellable event signals.
+
+ :param String event_name: base name for the bracketing event, will
+ be postfixed by ``:before`` and
+ ``:after`` before being called
+ (respectively before and after
+ ``action`` is executed)
+ :param Object event: object passed to the ``:before`` event
+ handlers.
+ :param Function action: function called with the view's editor as
+ its ``this``. May return a deferred.
+ :param Array args: arguments passed to ``action``
+ :param Array trigger_params: arguments passed to the ``:after``
+ event handler alongside the results
+ of ``action``
+
+Behavioral Customizations
++++++++++++++++++++++++++
+
+.. js:function:: openerp.web.ListView.handle_onwrite(record)
+
+ Implements the handling of the ``onwrite`` listview attribute:
+ calls the RPC methods specified by ``@onwrite``, and if that
+ method returns an array of ids loads or reloads the records
+ corresponding to those ids.
+
+ :param record: record being written having triggered the
+ ``onwrite`` callback
+ :type record: openerp.web.list.Record
+ :returns: deferred to all reloadings being done
+
+Events
+++++++
+
+For simpler interactions by/with external users of the listview, the
+view provides a number of dedicated events to its lifecycle.
+
+.. note:: if an event is defined as *cancellable*, it means its first
+ parameter is an object on which the ``cancel`` attribute can
+ be set. If the ``cancel`` attribute is set, the view will
+ abort its current behavior as soon as possible, and rollback
+ any state modification.
+
+ Generally speaking, an event should only be cancelled (by
+ setting the ``cancel`` flag to ``true``), uncancelling an
+ event is undefined as event handlers are executed on a
+ first-come-first-serve basis and later handlers may
+ re-cancel an uncancelled event.
+
+.. _listview-edit-before:
+
+``edit:before`` *cancellable*
+
+ Invoked before the list view starts editing a record.
+
+ Provided with an event object with a single property ``record``,
+ holding the attributes of the record being edited (``record`` is
+ empty *but not null* for a new record)
+
+``edit:after``
+
+ Invoked after the list view has gone into an edition state,
+ provided with the attributes of the record being edited (see
+ ``edit:before``) as first parameter and the form used for the
+ edition as second parameter.
+
+``save:before`` *cancellable*
+
+ Invoked right before saving a pending edition, provided with an
+ event object holding the listview's editor (``editor``) and the
+ edition form (``form``)
+
+``save:after``
+
+ Invoked after a save has been completed
+
+``cancel:before`` *cancellable*
+
+ Invoked before cancelling a pending edition, provided with the
+ same information as ``save:before``.
+
+``cancel:after``
+
+ Invoked after a pending edition has been cancelled.
+
+DOM events
+++++++++++
+
+The list view has grown hooks for the ``keyup`` event on its edition
+form (during edition): any such event bubbling out of the edition form
+will be forwarded to a method ``keyup_EVENTNAME``, where ``EVENTNAME``
+is the name of the key in ``$.ui.keyCode``.
+
+The method will also get the event object (originally passed to the
+``keyup`` handler) as its sole parameter.
+
+The base editable list view has handlers for the ``ENTER`` and
+``ESCAPE`` keys.
+
+Editor
+------
+
+The list-edition modules does not generally interact with the embedded
+formview, delegating instead to its
+:js:class:`~openerp.web.list.Editor`.
+
+.. js:class:: openerp.web.list.Editor(parent[, options])
+
+ The editor object provides a more convenient interface to form
+ views, and simplifies the usage of form views for semi-arbitrary
+ edition of stuff.
+
+ However, the editor does *not* task itself with being internally
+ consistent at this point: calling
+ e.g. :js:func:`~openerp.web.list.Editor.edit` multiple times in a
+ row without saving or cancelling each edit is undefined.
+
+ :param parent:
+ :type parent: :js:class:`~openerp.web.Widget`
+ :param EditorOptions options:
+
+ .. js:function:: openerp.web.list.Editor.is_editing([record_state])
+
+ Indicates whether the editor is currently in the process of
+ providing edition for a record.
+
+ Can be filtered by the state of the record being edited
+ (whether it's a record being *created* or a record being
+ *altered*), in which case it asserts both that an edition is
+ underway and that the record being edited respectively does
+ not yet exist in the database or already exists there.
+
+ :param record_state: state of the record being edited.
+ Either ``"new"`` or ``"edit"``.
+ :type record_state: String
+ :rtype: Boolean
+
+ .. js:function:: openerp.web.list.Editor.edit(record, configureField[, options])
+
+ Loads the provided record into the internal form view and
+ displays the form view.
+
+ Will also attempt to focus the first visible field of the form
+ view.
+
+ :param Object record: record to load into the form view
+ (key:value mapping similar to the result
+ of a ``read``)
+ :param configureField: function called with each field of the
+ form view right after the form is
+ displayed, lets whoever called this
+ method do some last-minute
+ configuration of form fields.
+ :type configureField: Function<String, openerp.web.form.Field>
+ :param EditOptions options:
+ :returns: jQuery delegate to the form object
+
+ .. js:function:: openerp.web.list.Editor.save
+
+ Attempts to save the internal form, then hide it
+
+ :returns: delegate to the record under edition (with ``id``
+ added for a creation). The record is not updated
+ from when it was passed in, aside from the ``id``
+ attribute.
+
+ .. js:function:: openerp.web.list.Editor.cancel([force=false])
+
+ Attemps to cancel the edition of the internal form, then hide
+ the form
+
+ :param Boolean force: unconditionally cancels the edition of
+ the internal form, even if the user has
+ already entered data in it.
+ :returns: delegate to the record under edition
+
+.. js:class:: EditorOptions
+
+ .. js:attribute:: EditorOptions.formView
+
+ Form view (sub)-class to instantiate and delegate edition to.
+
+ By default, :js:class:`~openerp.web.FormView`
+
+ .. js:attribute:: EditorOptions.delegate
+
+ Object used to get various bits of information about how to
+ display stuff.
+
+ By default, uses the editor's parent widget. See
+ :js:class:`EditorDelegate` for the methods and attributes to
+ provide.
+
+.. js:class:: EditorDelegate
+
+ Informal protocol defining the methods and attributes expected of
+ the :js:class:`~openerp.web.list.Editor`'s delegate.
+
+ .. js:attribute:: EditorDelegate.dataset
+
+ The dataset passed to the form view to synchronize the form
+ view and the outer widget.
+
+ .. js:function:: EditorDelegate.edition_view(editor)
+
+ Called by the :js:class:`~openerp.web.list.Editor` object to
+ get a form view (JSON) to pass along to the form view it
+ created.
+
+ The result should be a valid form view, see :doc:`Form Notes
+ <form_view>` for various peculiarities of the form view
+ format.
+
+ :param editor: editor object asking for the view
+ :type editor: :js:class:`~openerp.web.list.Editor`
+ :returns: form view
+ :rtype: Object
+
+ .. js:function:: EditorDelegate.prepends_on_create
+
+ By default, the :js:class:`~openerp.web.list.Editor` will
+ append the ids of newly created records to the
+ :js:attr:`EditorDelegate.dataset`. If this method returns
+ ``true``, it will prepend these ids instead.
+
+ :returns: whether new records should be prepended to the
+ dataset (instead of appended)
+ :rtype: Boolean
+
+
+.. js:class:: EditOptions
+
+ Options object optionally passed into a method starting an edition
+ to configure its setup and behavior
+
+ .. js:attribute:: focus_field
+
+ Name of the field to set focus on after setting up the edition
+ of the record.
+
+ If this option is not provided, or the requested field can not
+ be focused (invisible, readonly or not in the view), the first
+ visible non-readonly field is focused.
+
+Changes from 6.1
+----------------
+
+* The editable listview behavior has been rewritten pretty much from
+ scratch, any code touching on editability will have to be modified
+
+ * The overloading of :js:class:`~openerp.web.ListView.Groups` and
+ :js:class:`~openerp.web.ListView.List` for editability has been
+ drastically simplified, and most of the behavior has been moved to
+ the list view itself. Only
+ :js:func:`~openerp.web.ListView.List.row_clicked` is still
+ overridden.
+
+ * A new method ``get_row_for(record) -> jQuery(tr) | null`` has been
+ added to both ListView.List and ListView.Group, it can be called
+ from the list view to get the table row matching a record (if such
+ a row exists).
+
+* :js:func:`~openerp.web.ListView.do_button_action`'s core behavior
+ has been split away to
+ :js:func:`~openerp.web.ListView.handle_button`. This allows bypassing
+ overrides of :js:func:`~openerp.web.ListView.do_button_action` in a
+ parent class.
+
+ Ideally, :js:func:`~openerp.web.ListView.handle_button` should not be
+ overridden.
+
+* Modifiers handling has been improved (all modifiers information
+ should now be available through :js:func:`~Column.modifiers_for`,
+ not just ``invisible``)
+
+* Changed some handling of the list view's record: a record may now
+ have no id, and the listview will handle that correctly (for new
+ records being created) as well as correctly handle the ``id`` being
+ set.
+
+* Extended the internal collections structure of the list view with
+ `#find`_, `#succ`_ and `#pred`_.
+
+.. _#find: http://underscorejs.org/#find
+
+.. _#succ: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:succ
+
+.. _#pred: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:pred
diff --git a/addons/web/doc/make.bat b/addons/web/doc/make.bat
new file mode 100644
index 00000000..3e72cadb
--- /dev/null
+++ b/addons/web/doc/make.bat
@@ -0,0 +1,170 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OpenERPWeb.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OpenERPWeb.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/addons/web/doc/module.rst b/addons/web/doc/module.rst
new file mode 100644
index 00000000..ca808254
--- /dev/null
+++ b/addons/web/doc/module.rst
@@ -0,0 +1,442 @@
+.. _module:
+
+.. queue:: module/series
+
+Building a Web module
+=====================
+
+There is no significant distinction between a Web module and
+a regular module, the web part is mostly additional data and code
+inside a regular module. This allows providing more seamless
+features by integrating your module deeper into the web client.
+
+A Basic Module
+--------------
+
+A very basic OpenERP module structure will be our starting point:
+
+.. code-block:: text
+
+ web_example
+ ├── __init__.py
+ └── __manifest__.py
+
+.. patch::
+
+This is a sufficient minimal declaration of a valid module.
+
+Web Declaration
+---------------
+
+There is no such thing as a "web module" declaration. An OpenERP
+module is automatically recognized as "web-enabled" if it contains a
+``static`` directory at its root, so:
+
+.. code-block:: text
+
+ web_example
+ ├── __init__.py
+ ├── __manifest__.py
+ └── static
+
+is the extent of it. You should also change the dependency to list
+``web``:
+
+.. patch::
+
+.. note::
+
+ This does not matter in normal operation so you may not realize
+ it's wrong (the web module does the loading of everything else, so
+ it can only be loaded), but when e.g. testing the loading process
+ is slightly different than normal, and incorrect dependency may
+ lead to broken code.
+
+This makes the "web" discovery system consider the module as having a
+"web part", and check if it has web controllers to mount or javascript
+files to load. The content of the ``static/`` folder is also
+automatically made available to web browser at the URL
+``$module-name/static/$file-path``. This is sufficient to provide
+pictures (of cats, usually) through your module. However there are
+still a few more steps to running javascript code.
+
+Getting Things Done
+-------------------
+
+The first one is to add javascript code. It's customary to put it in
+``static/src/js``, to have room for e.g. other file types, or
+third-party libraries.
+
+.. patch::
+
+The client won't load any file unless specified, thus the new file
+should be listed in the module's manifest file, under a new key ``js``
+(a list of file names, or glob patterns):
+
+.. patch::
+
+At this point, if the module is installed and the client reloaded the
+message should appear in your browser's development console.
+
+.. note::
+
+ Because the manifest file has been edited, you will have to
+ restart the OpenERP server itself for it to be taken in account.
+
+ You may also want to open your browser's console *before*
+ reloading, depending on the browser messages printed while the
+ console is closed may not work or may not appear after opening it.
+
+.. note::
+
+ If the message does not appear, try cleaning your browser's caches
+ and ensure the file is correctly loaded from the server logs or
+ the "resources" tab of your browser's developers tools.
+
+At this point the code runs, but it runs only once when the module is
+initialized, and it can't get access to the various APIs of the web
+client (such as making RPC requests to the server). This is done by
+providing a `javascript module`_:
+
+.. patch::
+
+If you reload the client, you'll see a message in the console exactly
+as previously. The differences, though invisible at this point, are:
+
+* All javascript files specified in the manifest (only this one so
+ far) have been fully loaded
+* An instance of the web client and a namespace inside that instance
+ (with the same name as the module) have been created and are
+ available for use
+
+The latter point is what the ``instance`` parameter to the function
+provides: an instance of the OpenERP Web client, with the contents of
+all the new module's dependencies loaded in and initialized. These are
+the entry points to the web client's APIs.
+
+To demonstrate, let's build a simple :doc:`client action
+<client_action>`: a stopwatch
+
+First, the action declaration:
+
+.. patch::
+
+then set up the :doc:`client action hook <client_action>` to register
+a function (for now):
+
+.. patch::
+
+Updating the module (in order to load the XML description) and
+re-starting the server should display a new menu *Example Client
+Action* at the top-level. Opening said menu will make the message
+appear, as usual, in the browser's console.
+
+Paint it black
+--------------
+
+The next step is to take control of the page itself, rather than just
+print little messages in the console. This we can do by replacing our
+client action function by a :doc:`widget`. Our widget will simply use
+its :js:func:`~openerp.web.Widget.start` to add some content to its
+DOM:
+
+.. patch::
+
+after reloading the client (to update the javascript file), instead of
+printing to the console the menu item clears the whole screen and
+displays the specified message in the page.
+
+Since we've added a class on the widget's :ref:`DOM root
+<widget-dom_root>` we can now see how to add a stylesheet to a module:
+first create the stylesheet file:
+
+.. patch::
+
+then add a reference to the stylesheet in the module's manifest (which
+will require restarting the OpenERP Server to see the changes, as
+usual):
+
+.. patch::
+
+the text displayed by the menu item should now be huge, and
+white-on-black (instead of small and black-on-white). From there on,
+the world's your canvas.
+
+.. note::
+
+ Prefixing CSS rules with both ``.openerp`` (to ensure the rule
+ will apply only within the confines of the OpenERP Web client) and
+ a class at the root of your own hierarchy of widgets is strongly
+ recommended to avoid "leaking" styles in case the code is running
+ embedded in an other web page, and does not have the whole screen
+ to itself.
+
+So far we haven't built much (any, really) DOM content. It could all
+be done in :js:func:`~openerp.web.Widget.start` but that gets unwieldy
+and hard to maintain fast. It is also very difficult to extend by
+third parties (trying to add or change things in your widgets) unless
+broken up into multiple methods which each perform a little bit of the
+rendering.
+
+The first way to handle this method is to delegate the content to
+plenty of sub-widgets, which can be individually overridden. An other
+method [#DOM-building]_ is to use `a template
+<http://en.wikipedia.org/wiki/Web_template>`_ to render a widget's
+DOM.
+
+OpenERP Web's template language is :doc:`qweb`. Although any
+templating engine can be used (e.g. `mustache
+<http://mustache.github.com/>`_ or `_.template
+<http://underscorejs.org/#template>`_) QWeb has important features
+which other template engines may not provide, and has special
+integration to OpenERP Web widgets.
+
+Adding a template file is similar to adding a style sheet:
+
+.. patch::
+
+The template can then easily be hooked in the widget:
+
+.. patch::
+
+And finally the CSS can be altered to style the new (and more complex)
+template-generated DOM, rather than the code-generated one:
+
+.. patch::
+
+.. note::
+
+ The last section of the CSS change is an example of "state
+ classes": a CSS class (or set of classes) on the root of the
+ widget, which is toggled when the state of the widget changes and
+ can perform drastic alterations in rendering (usually
+ showing/hiding various elements).
+
+ This pattern is both fairly simple (to read and understand) and
+ efficient (because most of the hard work is pushed to the
+ browser's CSS engine, which is usually highly optimized, and done
+ in a single repaint after toggling the class).
+
+The last step (until the next one) is to add some behavior and make
+our stopwatch watch. First hook some events on the buttons to toggle
+the widget's state:
+
+.. patch::
+
+This demonstrates the use of the "events hash" and event delegation to
+declaratively handle events on the widget's DOM. And already changes
+the button displayed in the UI. Then comes some actual logic:
+
+.. patch::
+
+* An initializer (the ``init`` method) is introduced to set-up a few
+ internal variables: ``_start`` will hold the start of the timer (as
+ a javascript Date object), and ``_watch`` will hold a ticker to
+ update the interface regularly and display the "current time".
+
+* ``update_counter`` is in charge of taking the time difference
+ between "now" and ``_start``, formatting as ``HH:MM:SS`` and
+ displaying the result on screen.
+
+* ``watch_start`` is augmented to initialize ``_start`` with its value
+ and set-up the update of the counter display every 33ms.
+
+* ``watch_stop`` disables the updater, does a final update of the
+ counter display and resets everything.
+
+* Finally, because javascript Interval and Timeout objects execute
+ "outside" the widget, they will keep going even after the widget has
+ been destroyed (especially an issue with intervals as they repeat
+ indefinitely). So ``_watch`` *must* be cleared when the widget is
+ destroyed (then the ``_super`` must be called as well in order to
+ perform the "normal" widget cleanup).
+
+Starting and stopping the watch now works, and correctly tracks time
+since having started the watch, neatly formatted.
+
+Burning through the skies
+-------------------------
+
+All work so far has been "local" outside of the original impetus
+provided by the client action: the widget is self-contained and, once
+started, does not communicate with anything outside itself. Not only
+that, but it has no persistence: if the user leaves the stopwatch
+screen (to go and see his inbox, or do some well-deserved accounting,
+for instance) whatever was being timed will be lost.
+
+To prevent this irremediable loss, we can use OpenERP's support for
+storing data as a model, allowing so that we don't lose our data and
+can later retrieve, query and manipulate it. First let's create a
+basic OpenERP model in which our data will be stored:
+
+.. patch::
+
+then let's add saving times to the database every time the stopwatch
+is stopped, using :js:class:`the "high-level" Model API
+<openerp.web.Model.call>`:
+
+.. patch::
+
+A look at the "Network" tab of your preferred browser's developer
+tools while playing with the stopwatch will show that the save
+(creation) request is indeed sent (and replied to, even though we're
+ignoring the response at this point).
+
+These saved data should now be loaded and displayed when first opening
+the action, so the user can see his previously recorded times. This is
+done by overloading the model's ``start`` method: the purpose of
+:js:func:`~openerp.base.Widget.start()` is to perform *asynchronous*
+initialization steps, so the rest of the web client knows to "wait"
+and gets a readiness signal. In this case, it will fetch the data
+recorded previously using the :js:class:`~openerp.web.Query` interface
+and add this data to an ordered list added to the widget's template:
+
+.. patch::
+
+And for consistency's sake (so that the display a user leaves is
+pretty much the same as the one he comes back to), newly created
+records should also automatically be added to the list:
+
+.. patch::
+
+Note that we're only displaying the record once we know it's been
+saved from the database (the ``create`` call has returned without
+error).
+
+Mic check, is this working?
+---------------------------
+
+So far, features have been implemented, code has been worked and
+tentatively tried. However, there is no guarantee they will *keep
+working* as new changes are performed, new features added, …
+
+The original author (you, dear reader) could keep a notebook with a
+list of workflows to check, to ensure everything keeps working. And
+follow the notebook day after day, every time something is changed in
+the module.
+
+That gets repetitive after a while. And computers are good at doing
+repetitive stuff, as long as you tell them how to do it.
+
+So let's add test to the module, so that in the future the computer
+can take care of ensuring what works today keeps working tomorrow.
+
+.. note::
+
+ Here we're writing tests after having implemented the widget. This
+ may or may not work, we may need to alter bits and pieces of code
+ to get them in a testable state. An other testing methodology is
+ :abbr:`TDD (Test-Driven Development)` where the tests are written
+ first, and the code necessary to make these tests pass is written
+ afterwards.
+
+ Both methods have their opponents and detractors, advantages and
+ inconvenients. Pick the one you prefer.
+
+The first step of :doc:`testing` is to set up the basic testing
+structure:
+
+1. Creating a javascript file
+
+ .. patch::
+
+2. Containing a test section (and a few tests to make sure the tests
+ are correctly run)
+
+ .. patch::
+
+3. Then declaring the test file in the module's manifest
+
+ .. patch::
+
+4. And finally — after restarting OpenERP — navigating to the test
+ runner at ``/web/tests`` and selecting your soon-to-be-tested
+ module:
+
+ .. image:: module/testing_0.png
+ :align: center
+
+ the testing result do indeed match the test.
+
+The simplest tests to write are for synchronous pure
+functions. Synchronous means no RPC call or any other such thing
+(e.g. ``setTimeout``), only direct data processing, and pure means no
+side-effect: the function takes some input, manipulates it and yields
+an output.
+
+In our widget, only ``format_time`` fits the bill: it takes a duration
+(in milliseconds) and returns an ``hours:minutes:second`` formatting
+of it. Let's test it:
+
+.. patch::
+
+This series of simple tests passes with no issue. The next easy-ish
+test type is to test basic DOM alterations from provided input, such
+as (for our widget) updating the counter or displaying a record to the
+records list: while it's not pure (it alters the DOM "in-place") it
+has well-delimited side-effects and these side-effects come solely
+from the provided input.
+
+Because these methods alter the widget's DOM, the widget needs a
+DOM. Looking up :doc:`a widget's lifecycle <widget>`, the widget
+really only gets its DOM when adding it to the document. However a
+side-effect of this is to :js:func:`~openerp.web.Widget.start` it,
+which for us means going to query the user's times.
+
+We don't have any records to get in our test, and we don't want to
+test the initialization yet! So let's cheat a bit: we can manually
+:js:func:`set a widget's DOM <openerp.web.Widget.setElement>`, let's
+create a basic DOM matching what each method expects then call the
+method:
+
+.. patch::
+
+The next group of patches (in terms of setup/complexity) is RPC tests:
+testing components/methods which perform network calls (RPC
+requests). In our module, ``start`` and ``watch_stop`` are in that
+case: ``start`` fetches the user's recorded times and ``watch_stop``
+creates a new record with the current watch.
+
+By default, tests don't allow RPC requests and will generate an error
+when trying to perform one:
+
+.. image:: module/testing_1.png
+ :align: center
+
+To allow them, the test case (or the test suite) has to explicitly opt
+into :js:attr:`rpc support <TestOptions.rpc>` by adding the ``rpc:
+'mock'`` option to the test case, and providing its own "rpc
+responses":
+
+.. patch::
+
+.. note::
+
+ By default, tests cases don't load templates either. We had not
+ needed to perform any template rendering before here, so we must
+ now enable templates loading via :js:attr:`the corresponding
+ option <TestOptions.templates>`.
+
+Our final test requires altering the module's code: asynchronous tests
+use :doc:`deferred </async>` to know when a test ends and the other
+one can start (otherwise test content will execute non-linearly and
+the assertions of a test will be executed during the next test or
+worse), but although ``watch_stop`` performs an asynchronous
+``create`` operation it doesn't return a deferred we can synchronize
+on. We simply need to return its result:
+
+.. patch::
+
+This makes no difference to the original code, but allows us to write
+our test:
+
+.. patch::
+
+.. [#DOM-building] they are not alternative solutions: they work very
+ well together. Templates are used to build "just
+ DOM", sub-widgets are used to build DOM subsections
+ *and* delegate part of the behavior (e.g. events
+ handling).
+
+.. _javascript module:
+ http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript
diff --git a/addons/web/doc/module/0 b/addons/web/doc/module/0
new file mode 100644
index 00000000..17b70a86
--- /dev/null
+++ b/addons/web/doc/module/0
@@ -0,0 +1,17 @@
+# HG changeset patch
+# Parent 0000000000000000000000000000000000000000
+
+diff --git a/__init__.py b/__init__.py
+new file mode 100644
+diff --git a/__manifest__.py b/__manifest__.py
+new file mode 100644
+--- /dev/null
++++ b/__manifest__.py
+@@ -0,0 +1,7 @@
++# __manifest__.py
++{
++ 'name': "Web Example",
++ 'description': "Basic example of a (future) web module",
++ 'category': 'Hidden',
++ 'depends': ['base'],
++}
diff --git a/addons/web/doc/module/10 b/addons/web/doc/module/10
new file mode 100644
index 00000000..dc3b670c
--- /dev/null
+++ b/addons/web/doc/module/10
@@ -0,0 +1,13 @@
+# HG changeset patch
+# Parent 72d9d59a93fcee06ba28cf0b98a1075331dcc8f4
+diff --git a/static/src/css/web_example.css b/static/src/css/web_example.css
+new file mode 100644
+--- /dev/null
++++ b/static/src/css/web_example.css
+@@ -0,0 +1,6 @@
++.openerp .oe_web_example {
++ color: white;
++ background-color: black;
++ height: 100%;
++ font-size: 400%;
++}
diff --git a/addons/web/doc/module/11 b/addons/web/doc/module/11
new file mode 100644
index 00000000..889ee98e
--- /dev/null
+++ b/addons/web/doc/module/11
@@ -0,0 +1,11 @@
+# HG changeset patch
+# Parent 3ed382d9a8fe64fbb8e2bf4045e3fcd5c74c92bc
+diff --git a/__manifest__.py b/__manifest__.py
+--- a/__manifest__.py
++++ b/__manifest__.py
+@@ -6,4 +6,5 @@
+ 'depends': ['web'],
+ 'data': ['web_example.xml'],
+ 'js': ['static/src/js/first_module.js'],
++ 'css': ['static/src/css/web_example.css'],
+ }
diff --git a/addons/web/doc/module/12 b/addons/web/doc/module/12
new file mode 100644
index 00000000..85c931c3
--- /dev/null
+++ b/addons/web/doc/module/12
@@ -0,0 +1,28 @@
+# HG changeset patch
+# Parent 43f21611dacb7c2b2f3810baeeef359ad7c329f0
+
+diff --git a/__manifest__.py b/__manifest__.py
+--- a/__manifest__.py
++++ b/__manifest__.py
+@@ -7,4 +7,5 @@
+ 'data': ['web_example.xml'],
+ 'js': ['static/src/js/first_module.js'],
+ 'css': ['static/src/css/web_example.css'],
++ 'qweb': ['static/src/xml/web_example.xml'],
+ }
+diff --git a/static/src/xml/web_example.xml b/static/src/xml/web_example.xml
+new file mode 100644
+--- /dev/null
++++ b/static/src/xml/web_example.xml
+@@ -0,0 +1,11 @@
++<templates>
++<div t-name="web_example.action" class="oe_web_example oe_web_example_stopped">
++ <h4 class="oe_web_example_timer">00:00:00</h4>
++ <p class="oe_web_example_start">
++ <button type="button">Start</button>
++ </p>
++ <p class="oe_web_example_stop">
++ <button type="button">Stop</button>
++ </p>
++</div>
++</templates>
diff --git a/addons/web/doc/module/14 b/addons/web/doc/module/14
new file mode 100644
index 00000000..a908cbc6
--- /dev/null
+++ b/addons/web/doc/module/14
@@ -0,0 +1,17 @@
+# HG changeset patch
+# Parent ae3b427c96b532794a65357b3f075129cc991276
+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
+--- a/static/src/js/first_module.js
++++ b/static/src/js/first_module.js
+@@ -2,10 +2,6 @@
+ openerp.web_example = function (instance) {
+ instance.web.client_actions.add('example.action', 'instance.web_example.Action');
+ instance.web_example.Action = instance.web.Widget.extend({
+- className: 'oe_web_example',
+- start: function () {
+- this.$el.text("Hello, world!");
+- return this._super();
+- }
++ template: 'web_example.action'
+ });
+ };
diff --git a/addons/web/doc/module/15 b/addons/web/doc/module/15
new file mode 100644
index 00000000..d82abee3
--- /dev/null
+++ b/addons/web/doc/module/15
@@ -0,0 +1,19 @@
+# HG changeset patch
+# Parent e2d2e1a4cc2d2496aebeb05d94768384427c9e8b
+diff --git a/static/src/css/web_example.css b/static/src/css/web_example.css
+--- a/static/src/css/web_example.css
++++ b/static/src/css/web_example.css
+@@ -2,5 +2,12 @@
+ color: white;
+ background-color: black;
+ height: 100%;
+- font-size: 400%;
+ }
++.openerp .oe_web_example h4 {
++ margin: 0;
++ font-size: 200%;
++}
++.openerp .oe_web_example.oe_web_example_started .oe_web_example_start button,
++.openerp .oe_web_example.oe_web_example_stopped .oe_web_example_stop button {
++ display: none
++}
diff --git a/addons/web/doc/module/16 b/addons/web/doc/module/16
new file mode 100644
index 00000000..816c23aa
--- /dev/null
+++ b/addons/web/doc/module/16
@@ -0,0 +1,25 @@
+# HG changeset patch
+# Parent 2645d7a09dcba7f6d6074a33252c16c03c56fdf3
+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
+--- a/static/src/js/first_module.js
++++ b/static/src/js/first_module.js
+@@ -2,6 +2,18 @@
+ openerp.web_example = function (instance) {
+ instance.web.client_actions.add('example.action', 'instance.web_example.Action');
+ instance.web_example.Action = instance.web.Widget.extend({
+- template: 'web_example.action'
++ template: 'web_example.action',
++ events: {
++ 'click .oe_web_example_start button': 'watch_start',
++ 'click .oe_web_example_stop button': 'watch_stop'
++ },
++ watch_start: function () {
++ this.$el.addClass('oe_web_example_started')
++ .removeClass('oe_web_example_stopped');
++ },
++ watch_stop: function () {
++ this.$el.removeClass('oe_web_example_started')
++ .addClass('oe_web_example_stopped');
++ },
+ });
+ };
diff --git a/addons/web/doc/module/17 b/addons/web/doc/module/17
new file mode 100644
index 00000000..d6d6ecc7
--- /dev/null
+++ b/addons/web/doc/module/17
@@ -0,0 +1,52 @@
+# HG changeset patch
+# Parent 2921a545adc3406d3139be7951f3225e94493466
+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
+--- a/static/src/js/first_module.js
++++ b/static/src/js/first_module.js
+@@ -7,13 +7,46 @@ openerp.web_example = function (instance
+ 'click .oe_web_example_start button': 'watch_start',
+ 'click .oe_web_example_stop button': 'watch_stop'
+ },
++ init: function () {
++ this._super.apply(this, arguments);
++ this._start = null;
++ this._watch = null;
++ },
++ update_counter: function () {
++ var h, m, s;
++ // Subtracting javascript dates returns the difference in milliseconds
++ var diff = new Date() - this._start;
++ s = diff / 1000;
++ m = Math.floor(s / 60);
++ s -= 60*m;
++ h = Math.floor(m / 60);
++ m -= 60*h;
++ this.$('.oe_web_example_timer').text(
++ _.str.sprintf("%02d:%02d:%02d", h, m, s));
++ },
+ watch_start: function () {
+ this.$el.addClass('oe_web_example_started')
+ .removeClass('oe_web_example_stopped');
++ this._start = new Date();
++ // Update the UI to the current time
++ this.update_counter();
++ // Update the counter at 30 FPS (33ms/frame)
++ this._watch = setInterval(
++ this.proxy('update_counter'),
++ 33);
+ },
+ watch_stop: function () {
++ clearInterval(this._watch);
++ this.update_counter();
++ this._start = this._watch = null;
+ this.$el.removeClass('oe_web_example_started')
+ .addClass('oe_web_example_stopped');
+ },
++ destroy: function () {
++ if (this._watch) {
++ clearInterval(this._watch);
++ }
++ this._super();
++ }
+ });
+ };
diff --git a/addons/web/doc/module/18 b/addons/web/doc/module/18
new file mode 100644
index 00000000..6781c985
--- /dev/null
+++ b/addons/web/doc/module/18
@@ -0,0 +1,19 @@
+# HG changeset patch
+# Parent e0cc13c2b2ec4d6f6bfdb033b189a32e44106f2e
+diff --git a/__init__.py b/__init__.py
+--- a/__init__.py
++++ b/__init__.py
+@@ -0,0 +1,13 @@
++# __init__.py
++from openerp.osv import orm, fields
++
++
++class Times(orm.Model):
++ _name = 'web_example.stopwatch'
++
++ _columns = {
++ 'time': fields.integer("Time", required=True,
++ help="Measured time in milliseconds"),
++ 'user_id': fields.many2one('res.users', "User", required=True,
++ help="User who registered the measurement")
++ }
diff --git a/addons/web/doc/module/19 b/addons/web/doc/module/19
new file mode 100644
index 00000000..d95a89a3
--- /dev/null
+++ b/addons/web/doc/module/19
@@ -0,0 +1,52 @@
+# HG changeset patch
+# Parent 05797cc75b49634e640f44b24347f2905b464022
+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
+--- a/static/src/js/first_module.js
++++ b/static/src/js/first_module.js
+@@ -12,11 +12,13 @@ openerp.web_example = function (instance
+ this._start = null;
+ this._watch = null;
+ },
+- update_counter: function () {
++ current: function () {
++ // Subtracting javascript dates returns the difference in milliseconds
++ return new Date() - this._start;
++ },
++ update_counter: function (time) {
+ var h, m, s;
+- // Subtracting javascript dates returns the difference in milliseconds
+- var diff = new Date() - this._start;
+- s = diff / 1000;
++ s = time / 1000;
+ m = Math.floor(s / 60);
+ s -= 60*m;
+ h = Math.floor(m / 60);
+@@ -29,18 +31,24 @@ openerp.web_example = function (instance
+ .removeClass('oe_web_example_stopped');
+ this._start = new Date();
+ // Update the UI to the current time
+- this.update_counter();
++ this.update_counter(this.current());
+ // Update the counter at 30 FPS (33ms/frame)
+- this._watch = setInterval(
+- this.proxy('update_counter'),
++ this._watch = setInterval(function () {
++ this.update_counter(this.current());
++ }.bind(this),
+ 33);
+ },
+ watch_stop: function () {
+ clearInterval(this._watch);
+- this.update_counter();
++ var time = this.current();
++ this.update_counter(time);
+ this._start = this._watch = null;
+ this.$el.removeClass('oe_web_example_started')
+ .addClass('oe_web_example_stopped');
++ new instance.web.Model('web_example.stopwatch').call('create', [{
++ user_id: instance.session.uid,
++ time: time,
++ }]);
+ },
+ destroy: function () {
+ if (this._watch) {
diff --git a/addons/web/doc/module/2 b/addons/web/doc/module/2
new file mode 100644
index 00000000..7d8e9f05
--- /dev/null
+++ b/addons/web/doc/module/2
@@ -0,0 +1,12 @@
+# HG changeset patch
+# Parent 8a986919a3e22cd7cca51210820c09d4545dc60d
+diff --git a/__manifest__.py b/__manifest__.py
+--- a/__manifest__.py
++++ b/__manifest__.py
+@@ -3,5 +3,5 @@
+ 'name': "Web Example",
+ 'description': "Basic example of a (future) web module",
+ 'category': 'Hidden',
+- 'depends': ['base'],
++ 'depends': ['web'],
+ }
diff --git a/addons/web/doc/module/20 b/addons/web/doc/module/20
new file mode 100644
index 00000000..042ff280
--- /dev/null
+++ b/addons/web/doc/module/20
@@ -0,0 +1,64 @@
+Index: web_example/static/src/js/first_module.js
+===================================================================
+--- web_example.orig/static/src/js/first_module.js
++++ web_example/static/src/js/first_module.js
+@@ -11,20 +11,36 @@ openerp.web_example = function (instance
+ this._super.apply(this, arguments);
+ this._start = null;
+ this._watch = null;
++ this.model = new instance.web.Model('web_example.stopwatch');
++ },
++ start: function () {
++ var display = this.display_record.bind(this);
++ return this.model.query()
++ .filter([['user_id', '=', instance.session.uid]])
++ .all().done(function (records) {
++ _(records).each(display);
++ });
+ },
+ current: function () {
+ // Subtracting javascript dates returns the difference in milliseconds
+ return new Date() - this._start;
+ },
+- update_counter: function (time) {
++ display_record: function (record) {
++ $('<li>')
++ .text(this.format_time(record.time))
++ .appendTo(this.$('.oe_web_example_saved'));
++ },
++ format_time: function (time) {
+ var h, m, s;
+ s = time / 1000;
+ m = Math.floor(s / 60);
+ s -= 60*m;
+ h = Math.floor(m / 60);
+ m -= 60*h;
+- this.$('.oe_web_example_timer').text(
+- _.str.sprintf("%02d:%02d:%02d", h, m, s));
++ return _.str.sprintf("%02d:%02d:%02d", h, m, s);
++ },
++ update_counter: function (time) {
++ this.$('.oe_web_example_timer').text(this.format_time(time));
+ },
+ watch_start: function () {
+ this.$el.addClass('oe_web_example_started')
+@@ -45,7 +61,7 @@ openerp.web_example = function (instance
+ this._start = this._watch = null;
+ this.$el.removeClass('oe_web_example_started')
+ .addClass('oe_web_example_stopped');
+- new instance.web.Model('web_example.stopwatch').call('create', [{
++ this.model.call('create', [{
+ user_id: instance.session.uid,
+ time: time,
+ }]);
+Index: web_example/static/src/xml/web_example.xml
+===================================================================
+--- web_example.orig/static/src/xml/web_example.xml
++++ web_example/static/src/xml/web_example.xml
+@@ -7,5 +7,6 @@
+ <p class="oe_web_example_stop">
+ <button type="button">Stop</button>
+ </p>
++ <ol class="oe_web_example_saved"></ol>
+ </div>
+ </templates>
diff --git a/addons/web/doc/module/21 b/addons/web/doc/module/21
new file mode 100644
index 00000000..0acfac90
--- /dev/null
+++ b/addons/web/doc/module/21
@@ -0,0 +1,27 @@
+Index: web_example/static/src/js/first_module.js
+===================================================================
+--- web_example.orig/static/src/js/first_module.js
++++ web_example/static/src/js/first_module.js
+@@ -55,16 +55,20 @@ openerp.web_example = function (instance
+ 33);
+ },
+ watch_stop: function () {
++ var self = this;
+ clearInterval(this._watch);
+ var time = this.current();
+ this.update_counter(time);
+ this._start = this._watch = null;
+ this.$el.removeClass('oe_web_example_started')
+ .addClass('oe_web_example_stopped');
+- this.model.call('create', [{
++ var record = {
+ user_id: instance.session.uid,
+ time: time,
+- }]);
++ };
++ this.model.call('create', [record]).done(function () {
++ self.display_record(record);
++ });
+ },
+ destroy: function () {
+ if (this._watch) {
diff --git a/addons/web/doc/module/22 b/addons/web/doc/module/22
new file mode 100644
index 00000000..5df76b30
--- /dev/null
+++ b/addons/web/doc/module/22
@@ -0,0 +1,6 @@
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- /dev/null
++++ web_example/static/src/tests/timer.js
+@@ -0,0 +1 @@
++
diff --git a/addons/web/doc/module/23 b/addons/web/doc/module/23
new file mode 100644
index 00000000..d08a026a
--- /dev/null
+++ b/addons/web/doc/module/23
@@ -0,0 +1,14 @@
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- web_example.orig/static/src/tests/timer.js
++++ web_example/static/src/tests/timer.js
+@@ -1 +1,8 @@
+-
++openerp.testing.section('timer', function (test) {
++ test('successful test', function () {
++ ok(true, "should work");
++ });
++ test('unsuccessful test', function () {
++ ok(false, "shoud fail");
++ });
++});
diff --git a/addons/web/doc/module/24 b/addons/web/doc/module/24
new file mode 100644
index 00000000..0972e856
--- /dev/null
+++ b/addons/web/doc/module/24
@@ -0,0 +1,10 @@
+Index: web_example/__manifest__.py
+===================================================================
+--- web_example.orig/__manifest__.py
++++ web_example/__manifest__.py
+@@ -8,4 +8,5 @@
+ 'js': ['static/src/js/first_module.js'],
+ 'css': ['static/src/css/web_example.css'],
+ 'qweb': ['static/src/xml/web_example.xml'],
++ 'test': ['static/src/tests/timer.js'],
+ }
diff --git a/addons/web/doc/module/25 b/addons/web/doc/module/25
new file mode 100644
index 00000000..1d63dc7f
--- /dev/null
+++ b/addons/web/doc/module/25
@@ -0,0 +1,55 @@
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- web_example.orig/static/src/tests/timer.js
++++ web_example/static/src/tests/timer.js
+@@ -1,8 +1,45 @@
+ openerp.testing.section('timer', function (test) {
+- test('successful test', function () {
+- ok(true, "should work");
+- });
+- test('unsuccessful test', function () {
+- ok(false, "shoud fail");
++ test('format_time', function (instance) {
++ var w = new instance.web_example.Action();
++
++ strictEqual(
++ w.format_time(0),
++ '00:00:00');
++ strictEqual(
++ w.format_time(543),
++ '00:00:00',
++ "should round sub-second times down to zero");
++ strictEqual(
++ w.format_time(5340),
++ '00:00:05',
++ "should floor sub-second extents to the previous second");
++ strictEqual(
++ w.format_time(60000),
++ '00:01:00');
++ strictEqual(
++ w.format_time(3600000),
++ '01:00:00');
++ strictEqual(
++ w.format_time(86400000),
++ '24:00:00');
++ strictEqual(
++ w.format_time(604800000),
++ '168:00:00');
++
++ strictEqual(
++ w.format_time(22733958),
++ '06:18:53');
++ strictEqual(
++ w.format_time(41676639),
++ '11:34:36');
++ strictEqual(
++ w.format_time(57802094),
++ '16:03:22');
++ strictEqual(
++ w.format_time(73451828),
++ '20:24:11');
++ strictEqual(
++ w.format_time(84092336),
++ '23:21:32');
+ });
+ });
diff --git a/addons/web/doc/module/26 b/addons/web/doc/module/26
new file mode 100644
index 00000000..ec0b345a
--- /dev/null
+++ b/addons/web/doc/module/26
@@ -0,0 +1,38 @@
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- web_example.orig/static/src/tests/timer.js
++++ web_example/static/src/tests/timer.js
+@@ -42,4 +42,33 @@ openerp.testing.section('timer', functio
+ w.format_time(84092336),
+ '23:21:32');
+ });
++ test('update_counter', function (instance, $fixture) {
++ var w = new instance.web_example.Action();
++ // $fixture is a DOM tree whose content gets cleaned up before
++ // each test, so we can add whatever we need to it
++ $fixture.append('<div class="oe_web_example_timer">');
++ // Then set it on the widget
++ w.setElement($fixture);
++
++ // Update the counter with a known value
++ w.update_counter(22733958);
++ // And check the DOM matches
++ strictEqual($fixture.text(), '06:18:53');
++
++ w.update_counter(73451828)
++ strictEqual($fixture.text(), '20:24:11');
++ });
++ test('display_record', function (instance, $fixture) {
++ var w = new instance.web_example.Action();
++ $fixture.append('<ol class="oe_web_example_saved">')
++ w.setElement($fixture);
++
++ w.display_record({time: 41676639});
++ w.display_record({time: 84092336});
++
++ var $lis = $fixture.find('li');
++ strictEqual($lis.length, 2, "should have printed 2 records");
++ strictEqual($lis[0].textContent, '11:34:36');
++ strictEqual($lis[1].textContent, '23:21:32');
++ });
+ });
diff --git a/addons/web/doc/module/27 b/addons/web/doc/module/27
new file mode 100644
index 00000000..2061b700
--- /dev/null
+++ b/addons/web/doc/module/27
@@ -0,0 +1,28 @@
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- web_example.orig/static/src/tests/timer.js
++++ web_example/static/src/tests/timer.js
+@@ -71,4 +71,23 @@ openerp.testing.section('timer', functio
+ strictEqual($lis[0].textContent, '11:34:36');
+ strictEqual($lis[1].textContent, '23:21:32');
+ });
++ test('start', {templates: true, rpc: 'mock', asserts: 3}, function (instance, $fixture, mock) {
++ // Rather odd-looking shortcut for search+read in a single RPC call
++ mock('/web/dataset/search_read', function () {
++ // ignore parameters, just return a pair of records.
++ return {records: [
++ {time: 22733958},
++ {time: 84092336}
++ ]};
++ });
++
++ var w = new instance.web_example.Action();
++ return w.appendTo($fixture)
++ .then(function () {
++ var $lis = $fixture.find('li');
++ strictEqual($lis.length, 2);
++ strictEqual($lis[0].textContent, '06:18:53');
++ strictEqual($lis[1].textContent, '23:21:32');
++ });
++ });
+ });
diff --git a/addons/web/doc/module/28 b/addons/web/doc/module/28
new file mode 100644
index 00000000..800e7a6f
--- /dev/null
+++ b/addons/web/doc/module/28
@@ -0,0 +1,13 @@
+Index: web_example/static/src/js/first_module.js
+===================================================================
+--- web_example.orig/static/src/js/first_module.js
++++ web_example/static/src/js/first_module.js
+@@ -66,7 +66,7 @@ openerp.web_example = function (instance
+ user_id: instance.session.uid,
+ time: time,
+ };
+- this.model.call('create', [record]).done(function () {
++ return this.model.call('create', [record]).done(function () {
+ self.display_record(record);
+ });
+ },
diff --git a/addons/web/doc/module/29 b/addons/web/doc/module/29
new file mode 100644
index 00000000..509d4b78
--- /dev/null
+++ b/addons/web/doc/module/29
@@ -0,0 +1,37 @@
+Index: web_example/static/src/tests/timer.js
+===================================================================
+--- web_example.orig/static/src/tests/timer.js
++++ web_example/static/src/tests/timer.js
+@@ -90,4 +90,32 @@ openerp.testing.section('timer', functio
+ strictEqual($lis[1].textContent, '23:21:32');
+ });
+ });
++ test('watch_stop', {templates: true, rpc: 'mock', asserts: 3}, function (instance, $fix, mock) {
++ var created = false;
++ mock('web_example.stopwatch:create', function (args, kwargs) {
++ created = true;
++ // return a fake id (unused)
++ return 42;
++ });
++ mock('/web/dataset/search_read', function () {
++ return {records: []};
++ });
++
++ var w = new instance.web_example.Action();
++ return w.appendTo($fix)
++ .then(function () {
++ // Virtual start point 5s before 'now'
++ w._start = new Date() - 5000;
++ return w.watch_stop();
++ })
++ .done(function () {
++ ok(created, "should have called create()");
++ strictEqual($fix.find('.oe_web_example_timer').text(),
++ '00:00:05',
++ "should have updated the timer");
++ strictEqual($fix.find('li')[0].textContent,
++ '00:00:05',
++ "should have added the new time to the list");
++ });
++ });
+ });
diff --git a/addons/web/doc/module/3 b/addons/web/doc/module/3
new file mode 100644
index 00000000..c09925f9
--- /dev/null
+++ b/addons/web/doc/module/3
@@ -0,0 +1,9 @@
+# HG changeset patch
+# Parent dcf661a5eef8f82503831bdb8e6c9d2f9beb285e
+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
+new file mode 100644
+--- /dev/null
++++ b/static/src/js/first_module.js
+@@ -0,0 +1,2 @@
++// static/src/js/first_module.js
++console.log("Debug statement: file loaded");
diff --git a/addons/web/doc/module/4 b/addons/web/doc/module/4
new file mode 100644
index 00000000..99c073d6
--- /dev/null
+++ b/addons/web/doc/module/4
@@ -0,0 +1,11 @@
+# HG changeset patch
+# Parent 139dae60de67efa0017f5032f71ab774685c5507
+diff --git a/__manifest__.py b/__manifest__.py
+--- a/__manifest__.py
++++ b/__manifest__.py
+@@ -4,4 +4,5 @@
+ 'description': "Basic example of a (future) web module",
+ 'category': 'Hidden',
+ 'depends': ['web'],
++ 'js': ['static/src/js/first_module.js'],
+ }
diff --git a/addons/web/doc/module/5 b/addons/web/doc/module/5
new file mode 100644
index 00000000..49acad9c
--- /dev/null
+++ b/addons/web/doc/module/5
@@ -0,0 +1,11 @@
+# HG changeset patch
+# Parent c8ae7646cce3f271698c844eb2d67f9a8719650d
+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
+--- a/static/src/js/first_module.js
++++ b/static/src/js/first_module.js
+@@ -1,2 +1,4 @@
+ // static/src/js/first_module.js
+-console.log("Debug statement: file loaded");
++openerp.web_example = function (instance) {
++ console.log("Module loaded");
++};
diff --git a/addons/web/doc/module/6 b/addons/web/doc/module/6
new file mode 100644
index 00000000..3a1232fa
--- /dev/null
+++ b/addons/web/doc/module/6
@@ -0,0 +1,29 @@
+# HG changeset patch
+# Parent 0026cb80097a724db8d36371bc00da993a51a06f
+
+diff --git a/__manifest__.py b/__manifest__.py
+--- a/__manifest__.py
++++ b/__manifest__.py
+@@ -4,5 +4,6 @@
+ 'description': "Basic example of a (future) web module",
+ 'category': 'Hidden',
+ 'depends': ['web'],
++ 'data': ['web_example.xml'],
+ 'js': ['static/src/js/first_module.js'],
+ }
+diff --git a/web_example.xml b/web_example.xml
+new file mode 100644
+--- /dev/null
++++ b/web_example.xml
+@@ -0,0 +1,11 @@
++<!-- web_example/web_example.xml -->
++<openerp>
++ <data>
++ <record model="ir.actions.client" id="action_client_example">
++ <field name="name">Example Client Action</field>
++ <field name="tag">example.action</field>
++ </record>
++ <menuitem action="action_client_example"
++ id="menu_client_example"/>
++ </data>
++</openerp>
diff --git a/addons/web/doc/module/8 b/addons/web/doc/module/8
new file mode 100644
index 00000000..83e6c371
--- /dev/null
+++ b/addons/web/doc/module/8
@@ -0,0 +1,14 @@
+# HG changeset patch
+# Parent d987c9edd884de1de30f2ceb70d2e554474b8dd1
+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
+--- a/static/src/js/first_module.js
++++ b/static/src/js/first_module.js
+@@ -1,4 +1,7 @@
+ // static/src/js/first_module.js
+ openerp.web_example = function (instance) {
+- console.log("Module loaded");
++ instance.web.client_actions.add('example.action', 'instance.web_example.action');
++ instance.web_example.action = function (parent, action) {
++ console.log("Executed the action", action);
++ };
+ };
diff --git a/addons/web/doc/module/9 b/addons/web/doc/module/9
new file mode 100644
index 00000000..9113f914
--- /dev/null
+++ b/addons/web/doc/module/9
@@ -0,0 +1,21 @@
+# HG changeset patch
+# Parent 6a1a7240ea0e63182f60abb1eb5c631089d56dbe
+diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
+--- a/static/src/js/first_module.js
++++ b/static/src/js/first_module.js
+@@ -1,7 +1,11 @@
+ // static/src/js/first_module.js
+ openerp.web_example = function (instance) {
+- instance.web.client_actions.add('example.action', 'instance.web_example.action');
+- instance.web_example.action = function (parent, action) {
+- console.log("Executed the action", action);
+- };
++ instance.web.client_actions.add('example.action', 'instance.web_example.Action');
++ instance.web_example.Action = instance.web.Widget.extend({
++ className: 'oe_web_example',
++ start: function () {
++ this.$el.text("Hello, world!");
++ return this._super();
++ }
++ });
+ };
diff --git a/addons/web/doc/module/series b/addons/web/doc/module/series
new file mode 100644
index 00000000..ff1a909a
--- /dev/null
+++ b/addons/web/doc/module/series
@@ -0,0 +1,27 @@
+0
+2
+3
+4
+5
+6
+8
+9
+10
+11
+12
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
diff --git a/addons/web/doc/module/testing_0.png b/addons/web/doc/module/testing_0.png
new file mode 100644
index 00000000..62711799
--- /dev/null
+++ b/addons/web/doc/module/testing_0.png
Binary files differ
diff --git a/addons/web/doc/module/testing_1.png b/addons/web/doc/module/testing_1.png
new file mode 100644
index 00000000..40cf3249
--- /dev/null
+++ b/addons/web/doc/module/testing_1.png
Binary files differ
diff --git a/addons/web/doc/search_view.rst b/addons/web/doc/search_view.rst
new file mode 100644
index 00000000..76692a0d
--- /dev/null
+++ b/addons/web/doc/search_view.rst
@@ -0,0 +1,560 @@
+Search View
+===========
+
+OpenERP Web 7.0 implements a unified facets-based search view instead
+of the previous form-like search view (composed of buttons and
+multiple fields). The goal for this change is twofold:
+
+* Avoid the common issue of users confusing the search view with a
+ form view and trying to create their records through it (or entering
+ all their data, hitting the ``Create`` button expecting their record
+ to be created and losing everything).
+
+* Improve the looks and behaviors of the view, and the fit within
+ OpenERP Web's new design.
+
+The internal structure of the faceted search is inspired by
+`VisualSearch <http://documentcloud.github.com/visualsearch/>`_
+[#previous]_.
+
+As does VisualSearch, the new search view is based on `Backbone`_ and
+makes significant use of Backbone's models and collections (OpenERP
+Web's widgets make a good replacement for Backbone's own views). As a
+result, understanding the implementation details of the OpenERP Web 7
+search view also requires a basic understanding of Backbone's models,
+collections and events.
+
+.. note::
+
+ This document may mention *fetching* data. This is a shortcut for
+ "returning a :js:class:`Deferred` to [whatever is being
+ fetched]". Unless further noted, the function or method may opt to
+ return nothing by fetching ``null`` (which can easily be done by
+ returning ``$.when(null)``, which simply wraps the ``null`` in a
+ Deferred).
+
+Working with the search view: creating new inputs
+-------------------------------------------------
+
+The primary component of search views, as with all other OpenERP
+views, are inputs. The search view has two types of inputs — filters
+and fields — but only one is easly customizable: fields.
+
+The mapping from OpenERP field types (and widgets) to search view
+objects is stored in the ``openerp.web.search.fields``
+:js:class:`~openerp.web.Registry` where new field types and widgets
+can be added.
+
+Search view inputs have four main roles:
+
+Loading defaults
+++++++++++++++++
+
+Once the search view has initialized all its inputs, it will call
+:js:func:`~openerp.web.search.Input.facet_for_defaults` on each input,
+passing it a mapping (a javascript object) of ``name:value`` extracted
+from the action's context.
+
+This method should fetch a :js:class:`~openerp.web.search.Facet` (or
+an equivalent object) for the field's default value if applicable (if
+a default value for the field is found in the ``defaults`` mapping).
+
+A default implementation is provided which checks if ``defaults``
+contains a non-falsy value for the field's ``@name`` and calls
+:js:func:`openerp.web.search.Input.facet_for` with that value.
+
+There is no default implementation of
+:js:func:`openerp.web.search.Input.facet_for` [#no_impl]_, but
+:js:class:`openerp.web.search.Field` provides one, which uses the
+value as-is to fetch a :js:class:`~openerp.web.search.Facet`.
+
+Providing completions
++++++++++++++++++++++
+
+An important component of the new search view is the auto-completion
+pane, and the task of providing completion items is delegated to
+inputs through the :js:func:`~openerp.web.search.Input.complete`
+method.
+
+This method should take a single argument (the string being typed by
+the user) and should fetch an ``Array`` of possible completions
+[#completion]_.
+
+A default implementation is provided which fetches nothing.
+
+A completion item is a javascript object with two keys (technically it
+can have any number of keys, but only these two will be used by the
+search view):
+
+``label``
+
+ The string which will be displayed in the completion pane. It may
+ be formatted using HTML (inline only), as a result if ``value`` is
+ interpolated into it it *must* be escaped. ``_.escape`` can be
+ used for this.
+
+``facet``
+
+ Either a :js:class:`~openerp.web.search.Facet` object or (more
+ commonly) the corresponding attributes object. This is the facet
+ which will be inserted into the search query if the completion
+ item is selected by the user.
+
+If the ``facet`` is not provided (not present, ``null``, ``undefined``
+or any other falsy value), the completion item will not be selectable
+and will act as a section title of sort (the ``label`` will be
+formatted differently). If an input *may* fetch multiple completion
+items, it *should* prefix those with a section title using its own
+name. This has no technical consequence but is clearer for users.
+
+.. note::
+
+ If a field is :js:func:`invisible
+ <openerp.web.search.Input.visible>`, its completion function will
+ *not* be called.
+
+Providing drawer/supplementary UI
++++++++++++++++++++++++++++++++++
+
+For some inputs (fields or not), interaction via autocompletion may be
+awkward or even impossible.
+
+These may opt to being rendered in a "drawer" as well or instead. In
+that case, they will undergo the normal widget lifecycle and be
+rendered inside the drawer.
+
+.. Found no good type-based way to handle this, since there is no MI
+ (so no type-tagging) and it's possible for both Field and non-Field
+ input to be put into the drawer, for whatever reason (e.g. some
+ sort of auto-detector completion item for date widgets, but a
+ second more usual calendar widget in the drawer for more
+ obvious/precise interactions)
+
+Any input can note its desire to be rendered in the drawer by
+returning a truthy value from
+:js:func:`~openerp.web.search.Input.in_drawer`.
+
+By default, :js:func:`~openerp.web.search.Input.in_drawer` returns the
+value of :js:attr:`~openerp.web.search.Input._in_drawer`, which is
+``false``. The behavior can be toggled either by redefining the
+attribute to ``true`` (either on the class or on the input), or by
+overriding :js:func:`~openerp.web.search.Input.in_drawer` itself.
+
+The input will be rendered in the full width of the drawer, it will be
+started only once (per view).
+
+.. todo:: drawer API (if a widget wants to close the drawer in some
+ way), part of the low-level SearchView API/interactions?
+
+
+.. todo:: handle filters and filter groups via a "driver" input which
+ dynamically collects, lays out and renders filters? =>
+ exercises drawer thingies
+
+.. note::
+
+ An :js:func:`invisible <openerp.web.search.Input.visible>` input
+ will not be inserted into the drawer.
+
+Converting from facet objects
++++++++++++++++++++++++++++++
+
+Ultimately, the point of the search view is to allow searching. In
+OpenERP this is done via :ref:`domains <openerpserver:domains>`. On
+the other hand, the OpenERP Web 7 search view's state is modelled
+after a collection of :js:class:`~openerp.web.search.Facet`, and each
+field of a search view may have special requirements when it comes to
+the domains it produces [#special]_.
+
+So there needs to be some way of mapping
+:js:class:`~openerp.web.search.Facet` objects to OpenERP search data.
+
+This is done via an input's
+:js:func:`~openerp.web.search.Input.get_domain` and
+:js:func:`~openerp.web.search.Input.get_context`. Each takes a
+:js:class:`~openerp.web.search.Facet` and returns whatever it's
+supposed to generate (a domain or a context, respectively). Either can
+return ``null`` if the current value does not map to a domain or
+context, and can throw an :js:class:`~openerp.web.search.Invalid`
+exception if the value is not valid at all for the field.
+
+.. note::
+
+ The :js:class:`~openerp.web.search.Facet` object can have any
+ number of values (from 1 upwards)
+
+.. note::
+
+ There is a third conversion method,
+ :js:func:`~openerp.web.search.Input.get_groupby`, which returns an
+ ``Array`` of groupby domains rather than a single context. At this
+ point, it is only implemented on (and used by) filters.
+
+Programmatic interactions: internal model
+-----------------------------------------
+
+This new searchview is built around an instance of
+:js:class:`~openerp.web.search.SearchQuery` available as
+:js:attr:`openerp.web.SearchView.query`.
+
+The query is a `backbone collection`_ of
+:js:class:`~openerp.web.search.Facet` objects, which can be interacted
+with directly by external objects or search view controls
+(e.g. widgets displayed in the drawer).
+
+.. js:class:: openerp.web.search.SearchQuery
+
+ The current search query of the search view, provides convenience
+ behaviors for manipulating :js:class:`~openerp.web.search.Facet`
+ on top of the usual `backbone collection`_ methods.
+
+ The query ensures all of its facets contain at least one
+ :js:class:`~openerp.web.search.FacetValue` instance. Otherwise,
+ the facet is automatically removed from the query.
+
+ .. js:function:: openerp.web.search.SearchQuery.add(values, options)
+
+ Overridden from the base ``add`` method so that adding a facet
+ which is *already* in the collection will merge the value of
+ the new facet into the old one rather than add a second facet
+ with different values.
+
+ :param values: facet, facet attributes or array thereof
+ :returns: the collection itself
+
+ .. js:function:: openerp.web.search.SearchQuery.toggle(value, options)
+
+ Convenience method for toggling facet values in a query:
+ removes the values (through the facet itself) if they are
+ present, adds them if they are not. If the facet itself is not
+ in the collection, adds it automatically.
+
+ A toggling is atomic: only one change event will be triggered
+ on the facet regardless of the number of values added to or
+ removed from the facet (if the facet already exists), and the
+ facet is only removed from the query if it has no value *at
+ the end* of the toggling.
+
+ :param value: facet or facet attributes
+ :returns: the collection
+
+.. js:class:: openerp.web.search.Facet
+
+ A `backbone model`_ representing a single facet of the current
+ search. May map to a search field, or to a more complex or
+ fuzzier input (e.g. a custom filter or an advanced search).
+
+ .. js:attribute:: category
+
+ The displayed name of the facet, as a ``String``. This is a
+ backbone model attribute.
+
+ .. js:attribute:: field
+
+ The :js:class:`~openerp.web.search.Input` instance which
+ originally created the facet [#facet-field]_, used to delegate
+ some operations (such as serializing the facet's values to
+ domains and contexts). This is a backbone model attribute.
+
+ .. js:attribute:: values
+
+ :js:class:`~openerp.web.search.FacetValues` as a javascript
+ attribute, stores all the values for the facet and helps
+ propagate their events to the facet. Is also available as a
+ backbone attribute (via ``#get`` and ``#set``) in which cases
+ it serializes to and deserializes from javascript arrays (via
+ ``Collection#toJSON`` and ``Collection#reset``).
+
+ .. js:attribute:: [icon]
+
+ optional, a single ASCII letter (a-z or A-Z) mapping to the
+ bundled mnmliconsRegular icon font.
+
+ When a facet with an ``icon`` attribute is rendered, the icon
+ is displayed (in the icon font) in the first section of the
+ facet instead of the ``category``.
+
+ By default, only filters make use of this facility.
+
+.. js:class:: openerp.web.search.FacetValues
+
+ `Backbone collection`_ of
+ :js:class:`~openerp.web.search.FacetValue` instances.
+
+.. js:class:: openerp.web.search.FacetValue
+
+ `Backbone model`_ representing a single value within a facet,
+ represents a pair of (displayed name, logical value).
+
+ .. js:attribute:: label
+
+ Backbone model attribute storing the "displayable"
+ representation of the value, visually output to the
+ user. Must be a string.
+
+ .. js:attribute:: value
+
+ Backbone model attribute storing the logical/internal value
+ (of itself), will be used by
+ :js:class:`~openerp.web.search.Input` to serialize to domains
+ and contexts.
+
+ Can be of any type.
+
+Field services
+--------------
+
+:js:class:`~openerp.web.search.Field` provides a default
+implementation of :js:func:`~openerp.web.search.Input.get_domain` and
+:js:func:`~openerp.web.search.Input.get_context` taking care of most
+of the peculiarities pertaining to OpenERP's handling of fields in
+search views. It also provides finer hooks to let developers of new
+fields and widgets customize the behavior they want without
+necessarily having to reimplement all of
+:js:func:`~openerp.web.search.Input.get_domain` or
+:js:func:`~openerp.web.search.Input.get_context`:
+
+.. js:function:: openerp.web.search.Field.get_context(facet)
+
+ If the field has no ``@context``, simply returns
+ ``null``. Otherwise, calls
+ :js:func:`~openerp.web.search.Field.value_from` once for each
+ :js:class:`~openerp.web.search.FacetValue` of the current
+ :js:class:`~openerp.web.search.Facet` (in order to extract the
+ basic javascript object from the
+ :js:class:`~openerp.web.search.FacetValue` then evaluates
+ ``@context`` with each of these values set as ``self``, and
+ returns the union of all these contexts.
+
+ :param facet:
+ :type facet: openerp.web.search.Facet
+ :returns: a context (literal or compound)
+
+.. js:function:: openerp.web.search.Field.get_domain(facet)
+
+ If the field has no ``@filter_domain``, calls
+ :js:func:`~openerp.web.search.Field.make_domain` once with each
+ :js:class:`~openerp.web.search.FacetValue` of the current
+ :js:class:`~openerp.web.search.Facet` as well as the field's
+ ``@name`` and either its ``@operator`` or
+ :js:attr:`~openerp.web.search.Field.default_operator`.
+
+ If the field has an ``@filter_value``, calls
+ :js:func:`~openerp.web.search.Field.value_from` once per
+ :js:class:`~openerp.web.search.FacetValue` and evaluates
+ ``@filter_value`` with each of these values set as ``self``.
+
+ In either case, "ors" all of the resulting domains (using ``|``)
+ if there is more than one
+ :js:class:`~openerp.web.search.FacetValue` and returns the union
+ of the result.
+
+ :param facet:
+ :type facet: openerp.web.search.Facet
+ :returns: a domain (literal or compound)
+
+.. js:function:: openerp.web.search.Field.make_domain(name, operator, facetValue)
+
+ Builds a literal domain from the provided data. Calls
+ :js:func:`~openerp.web.search.Field.value_from` on the
+ :js:class:`~openerp.web.search.FacetValue` and evaluates and sets
+ it as the domain's third value, uses the other two parameters as
+ the first two values.
+
+ Can be overridden to build more complex default domains.
+
+ :param String name: the field's name
+ :param String operator: the operator to use in the field's domain
+ :param facetValue:
+ :type facetValue: openerp.web.search.FacetValue
+ :returns: Array<(String, String, Object)>
+
+.. js:function:: openerp.web.search.Field.value_from(facetValue)
+
+ Extracts a "bare" javascript value from the provided
+ :js:class:`~openerp.web.search.FacetValue`, and returns it.
+
+ The default implementation will simply return the ``value``
+ backbone property of the argument.
+
+ :param facetValue:
+ :type facetValue: openerp.web.search.FacetValue
+ :returns: Object
+
+.. js:attribute:: openerp.web.search.Field.default_operator
+
+ Operator used to build a domain when a field has no ``@operator``
+ or ``@filter_domain``. ``"="`` for
+ :js:class:`~openerp.web.search.Field`
+
+Arbitrary data storage
+----------------------
+
+:js:class:`~openerp.web.search.Facet` and
+:js:class:`~openerp.web.search.FacetValue` objects (and structures)
+provided by your widgets should never be altered by the search view
+(or an other widget). This means you are free to add arbitrary fields
+in these structures if you need to (because you have more complex
+needs than the attributes described in this document).
+
+Ideally this should be avoided, but the possibility remains.
+
+Changes
+-------
+
+.. todo:: merge in changelog instead?
+
+The displaying of the search view was significantly altered from
+OpenERP Web 6.1 to OpenERP Web 7.
+
+As a result, while the external API used to interact with the search
+view does not change many internal details — including the interaction
+between the search view and its widgets — were significantly altered:
+
+Internal operations
++++++++++++++++++++
+
+* :js:func:`openerp.web.SearchView.do_clear` has been removed
+* :js:func:`openerp.web.SearchView.do_toggle_filter` has been removed
+
+Widgets API
++++++++++++
+
+* :js:func:`openerp.web.search.Widget.render` has been removed
+
+* :js:func:`openerp.web.search.Widget.make_id` has been removed
+
+* Search field objects are not openerp widgets anymore, their
+ ``start`` is not generally called
+
+* :js:func:`~openerp.web.search.Input.clear` has been removed since
+ clearing the search view now simply consists of removing all search
+ facets
+
+* :js:func:`~openerp.web.search.Input.get_domain` and
+ :js:func:`~openerp.web.search.Input.get_context` now take a
+ :js:class:`~openerp.web.search.Facet` as parameter, from which it's
+ their job to get whatever value they want
+
+* :js:func:`~openerp.web.search.Input.get_groupby` has been added. It returns
+ an :js:class:`Array` of context-like constructs. By default, it does not do
+ anything in :js:class:`~openerp.web.search.Field` and it returns the various
+ contexts of its enabled filters in
+ :js:class:`~openerp.web.search.FilterGroup`.
+
+Filters
++++++++
+
+* :js:func:`openerp.web.search.Filter.is_enabled` has been removed
+
+* :js:class:`~openerp.web.search.FilterGroup` instances are still
+ rendered (and started) in the "advanced search" drawer.
+
+Fields
+++++++
+
+* ``get_value`` has been replaced by
+ :js:func:`~openerp.web.search.Field.value_from` as it now takes a
+ :js:class:`~openerp.web.search.FacetValue` argument (instead of no
+ argument). It provides a default implementation returning the
+ ``value`` property of its argument.
+
+* The third argument to
+ :js:func:`~openerp.web.search.Field.make_domain` is now a
+ :js:class:`~openerp.web.search.FacetValue` so child classes have all
+ the information they need to derive the "right" resulting domain.
+
+Custom filters
+++++++++++++++
+
+Instead of being an intrinsic part of the search view, custom filters
+are now a special case of filter groups. They are treated specially
+still, but much less so than they used to be.
+
+Many To One
++++++++++++
+
+* Because the autocompletion service is now provided by the search
+ view itself,
+ :js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
+ been removed.
+
+Advanced Search
++++++++++++++++
+
+* The advanced search is now a more standard
+ :js:class:`~openerp.web.search.Input` configured to be rendered in
+ the drawer.
+
+* :js:class:`~openerp.web.search.ExtendedSearchProposition.Field` are
+ now standard widgets, with the "right" behaviors (they don't rebind
+ their ``$element`` in ``start()``)
+
+* The ad-hoc optional setting of the openerp field descriptor on a
+ :js:class:`~openerp.web.search.ExtendedSearchProposition.Field` has
+ been removed, the field descriptor is now passed as second argument
+ to the
+ :js:class:`~openerp.web.search.ExtendedSearchProposition.Field`'s
+ constructor, and bound to its
+ :js:attr:`~openerp.web.search.ExtendedSearchProposition.Field.field`.
+
+* Instead of its former domain triplet ``(field, operator, value)``,
+ :js:func:`~openerp.web.search.ExtendedSearchProposition.get_proposition`
+ now returns an object with two fields ``label`` and ``value``,
+ respectively a human-readable version of the proposition and the
+ corresponding domain triplet for the proposition.
+
+.. [#previous]
+
+ the original view was implemented on top of a monkey-patched
+ VisualSearch, but as our needs diverged from VisualSearch's goal
+ this made less and less sense ultimately leading to a clean-room
+ reimplementation
+
+.. [#no_impl]
+
+ In case you are extending the search view with a brand new type of
+ input
+
+.. [#completion]
+
+ Ideally this array should not hold more than about 10 items, but
+ the search view does not put any constraint on this at the
+ moment. Note that this may change.
+
+.. [#facet-field]
+
+ ``field`` does not actually need to be an instance of
+ :js:class:`~openerp.web.search.Input`, nor does it need to be what
+ created the facet, it just needs to provide the three
+ facet-serialization methods
+ :js:func:`~openerp.web.search.Input.get_domain`,
+ :js:func:`~openerp.web.search.Input.get_context` and
+ :js:func:`~openerp.web.search.Input.get_gropuby`, existing
+ :js:class:`~openerp.web.search.Input` subtypes merely provide
+ convenient base implementation for those methods.
+
+ Complex search view inputs (especially those living in the drawer)
+ may prefer using object literals with the right slots returning
+ closed-over values or some other scheme un-bound to an actual
+ :js:class:`~openerp.web.search.Input`, as
+ :js:class:`~openerp.web.search.CustomFilters` and
+ :js:class:`~openerp.web.search.Advanced` do.
+
+.. [#special]
+
+ search view fields may also bundle context data to add to the
+ search context
+
+.. _Backbone:
+ http://documentcloud.github.com/backbone/
+
+.. _Backbone.Collection:
+.. _Backbone collection:
+ http://documentcloud.github.com/backbone/#Collection
+
+.. _Backbone model:
+ http://documentcloud.github.com/backbone/#Model
+
+.. _commit 3fca87101d:
+ https://github.com/documentcloud/visualsearch/commit/3fca87101d