[REM] web: old documentation

AFAIK, this documentation isn't built anywhere and is not maintained anyway.
Since the developer documentation was recently removed from odoo/odoo repository,
it's the right time to remove this old doc content.

closes odoo/odoo#71898

Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
This commit is contained in:
Victor Feyens
2021-06-09 08:27:56 +00:00
parent 497a4700fb
commit dc6db1450d
62 changed files with 0 additions and 4237 deletions
-154
View File
@@ -1,154 +0,0 @@
# 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."
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

-16
View File
@@ -1,16 +0,0 @@
<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>
-3
View File
@@ -1,3 +0,0 @@
<p class="logo"><a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/openerp.png', 1) }}" alt="Logo"/>
</a></p>
-37
View File
@@ -1,37 +0,0 @@
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.
-31
View File
@@ -1,31 +0,0 @@
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
-25
View File
@@ -1,25 +0,0 @@
{%- 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 %}
-19
View File
@@ -1,19 +0,0 @@
<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>
-396
View File
@@ -1,396 +0,0 @@
/*
* 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;
}
-70
View File
@@ -1,70 +0,0 @@
/*
* 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;
}
-9
View File
@@ -1,9 +0,0 @@
[theme]
inherit = basic
stylesheet = flasky.css
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo = ''
index_logo_height = 120px
touch_icon =
-22
View File
@@ -1,22 +0,0 @@
{% 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 %}
-287
View File
@@ -1,287 +0,0 @@
/*
* 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;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

-10
View File
@@ -1,10 +0,0 @@
[theme]
inherit = basic
stylesheet = flasky.css
nosidebar = true
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo = ''
index_logo_height = 120px
github_fork = ''
-86
View File
@@ -1,86 +0,0 @@
# 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'
}
-12
View File
@@ -1,12 +0,0 @@
<addon name>
+-- __manifest__.py
+-- controllers/
+-- static/
+-- lib/
+-- src/
+-- css/
+-- img/
+-- js/
+-- xml/
+-- test/
+-- test/
-114
View File
@@ -1,114 +0,0 @@
.. 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
}));
}
-260
View File
@@ -1,260 +0,0 @@
# -*- 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),
}
-55
View File
@@ -1,55 +0,0 @@
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
-194
View File
@@ -1,194 +0,0 @@
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

-55
View File
@@ -1,55 +0,0 @@
.. 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`
-531
View File
@@ -1,531 +0,0 @@
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
-170
View File
@@ -1,170 +0,0 @@
@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
-442
View File
@@ -1,442 +0,0 @@
.. _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
-17
View File
@@ -1,17 +0,0 @@
# 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'],
+}
-13
View File
@@ -1,13 +0,0 @@
# 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%;
+}
-11
View File
@@ -1,11 +0,0 @@
# 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'],
}
-28
View File
@@ -1,28 +0,0 @@
# 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>
-17
View File
@@ -1,17 +0,0 @@
# 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'
});
};
-19
View File
@@ -1,19 +0,0 @@
# 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
+}
-25
View File
@@ -1,25 +0,0 @@
# 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');
+ },
});
};
-52
View File
@@ -1,52 +0,0 @@
# 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();
+ }
});
};
-19
View File
@@ -1,19 +0,0 @@
# 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")
+ }
-52
View File
@@ -1,52 +0,0 @@
# 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) {
-12
View File
@@ -1,12 +0,0 @@
# 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'],
}
-64
View File
@@ -1,64 +0,0 @@
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>
-27
View File
@@ -1,27 +0,0 @@
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) {
-6
View File
@@ -1,6 +0,0 @@
Index: web_example/static/src/tests/timer.js
===================================================================
--- /dev/null
+++ web_example/static/src/tests/timer.js
@@ -0,0 +1 @@
+
-14
View File
@@ -1,14 +0,0 @@
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");
+ });
+});
-10
View File
@@ -1,10 +0,0 @@
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'],
}
-55
View File
@@ -1,55 +0,0 @@
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');
});
});
-38
View File
@@ -1,38 +0,0 @@
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');
+ });
});
-28
View File
@@ -1,28 +0,0 @@
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');
+ });
+ });
});
-13
View File
@@ -1,13 +0,0 @@
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);
});
},
-37
View File
@@ -1,37 +0,0 @@
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");
+ });
+ });
});
-9
View File
@@ -1,9 +0,0 @@
# 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");
-11
View File
@@ -1,11 +0,0 @@
# 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'],
}
-11
View File
@@ -1,11 +0,0 @@
# 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");
+};
-29
View File
@@ -1,29 +0,0 @@
# 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>
-14
View File
@@ -1,14 +0,0 @@
# 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);
+ };
};
-21
View File
@@ -1,21 +0,0 @@
# 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();
+ }
+ });
};
-27
View File
@@ -1,27 +0,0 @@
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

-560
View File
@@ -1,560 +0,0 @@
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