[MERGE] remove standalone web client
bzr revid: al@openerp.com-20121010204451-nq3eatsl1x9zi1cf
This commit is contained in:
-154
@@ -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."
|
||||
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
Vendored
-16
@@ -1,16 +0,0 @@
|
||||
<p class="logo"><a href="http://doc.openerp.com/">
|
||||
<img class="logo" src="{{ pathto('_static/openerp.png', 1) }}" alt="Logo"/>
|
||||
</a></p>
|
||||
|
||||
<h3>Other Docs</h3>
|
||||
<ul>
|
||||
<li><a href="http://doc.openerp.com/trunk/developers">OpenERP Developers Documentation</a></li>
|
||||
<li><a href="http://doc.openerp.com/trunk/developers/server">OpenERP Server Developers Documentation</a></li>
|
||||
<li><a href="http://doc.openerp.com/trunk/users">OpenERP Users Documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="http://www.openerp.com/">The OpenERP website</a></li>
|
||||
<li><a href="http://python.org/">The Python programming language</a></li>
|
||||
</ul>
|
||||
Vendored
-3
@@ -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>
|
||||
Vendored
-37
@@ -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.
|
||||
Vendored
-31
@@ -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
|
||||
Vendored
-25
@@ -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">
|
||||
© 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 %}
|
||||
Vendored
-19
@@ -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>
|
||||
-395
@@ -1,395 +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 {
|
||||
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
@@ -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;
|
||||
}
|
||||
Vendored
-9
@@ -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 =
|
||||
Vendored
-22
@@ -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 href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
|
||||
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block sidebar1 %}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
-287
@@ -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;
|
||||
}
|
||||
Vendored
-10
@@ -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 = ''
|
||||
Vendored
-86
@@ -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'
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<addon name>
|
||||
+-- __openerp__.py
|
||||
+-- controllers/
|
||||
+-- static/
|
||||
+-- lib/
|
||||
+-- src/
|
||||
+-- css/
|
||||
+-- img/
|
||||
+-- js/
|
||||
+-- xml/
|
||||
+-- test/
|
||||
+-- test/
|
||||
-353
@@ -1,353 +0,0 @@
|
||||
Don't stop the world now: asynchronous development and Javascript
|
||||
=================================================================
|
||||
|
||||
As a language (and runtime), javascript is fundamentally
|
||||
single-threaded. This means any blocking request or computation will
|
||||
blocks the whole page (and, in older browsers, the software itself
|
||||
even preventing users from switching to an other tab): a javascript
|
||||
environment can be seen as an event-based runloop where application
|
||||
developers have no control over the runloop itself.
|
||||
|
||||
As a result, performing long-running synchronous network requests or
|
||||
other types of complex and expensive accesses is frowned upon and
|
||||
asynchronous APIs are used instead.
|
||||
|
||||
Asynchronous code rarely comes naturally, especially for developers
|
||||
used to synchronous server-side code (in Python, Java or C#) where the
|
||||
code will just block until the deed is gone. This is increased further
|
||||
when asynchronous programming is not a first-class concept and is
|
||||
instead implemented on top of callbacks-based programming, which is
|
||||
the case in javascript.
|
||||
|
||||
The goal of this guide is to provide some tools to deal with
|
||||
asynchronous systems, and warn against systematic issues or dangers.
|
||||
|
||||
Deferreds
|
||||
---------
|
||||
|
||||
Deferreds are a form of `promises`_. OpenERP Web currently uses
|
||||
`jQuery's deferred`_, but any `CommonJS Promises/A`_ implementation
|
||||
should work.
|
||||
|
||||
The core idea of deferreds is that potentially asynchronous methods
|
||||
will return a :js:class:`Deferred` object instead of an arbitrary
|
||||
value or (most commonly) nothing.
|
||||
|
||||
This object can then be used to track the end of the asynchronous
|
||||
operation by adding callbacks onto it, either success callbacks or
|
||||
error callbacks.
|
||||
|
||||
A great advantage of deferreds over simply passing callback functions
|
||||
directly to asynchronous methods is the ability to :ref:`compose them
|
||||
<deferred-composition>`.
|
||||
|
||||
Using deferreds
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
`CommonJS Promises/A`_ deferreds have only one method of importance:
|
||||
:js:func:`Deferred.then`. This method is used to attach new callbacks
|
||||
to the deferred object.
|
||||
|
||||
* the first parameter attaches a success callback, called when the
|
||||
deferred object is successfully resolved and provided with the
|
||||
resolved value(s) for the asynchronous operation.
|
||||
|
||||
* the second parameter attaches a failure callback, called when the
|
||||
deferred object is rejected and provided with rejection values
|
||||
(often some sort of error message).
|
||||
|
||||
Callbacks attached to deferreds are never "lost": if a callback is
|
||||
attached to an already resolved or rejected deferred, the callback
|
||||
will be called (or ignored) immediately. A deferred is also only ever
|
||||
resolved or rejected once, and is either resolved or rejected: a given
|
||||
deferred can not call a single success callback twice, or call both a
|
||||
success and a failure callbacks.
|
||||
|
||||
:js:func:`~Deferred.then` should be the method you'll use most often
|
||||
when interacting with deferred objects (and thus asynchronous APIs).
|
||||
|
||||
Building deferreds
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
After using asynchronous APIs may come the time to build them: for
|
||||
`mocks`_, to compose deferreds from multiple source in a complex
|
||||
manner, in order to let the current operations repaint the screen or
|
||||
give other events the time to unfold, ...
|
||||
|
||||
This is easy using jQuery's deferred objects.
|
||||
|
||||
.. note:: this section is an implementation detail of jQuery Deferred
|
||||
objects, the creation of promises is not part of any
|
||||
standard (even tentative) that I know of. If you are using
|
||||
deferred objects which are not jQuery's, their API may (and
|
||||
often will) be completely different.
|
||||
|
||||
Deferreds are created by invoking their constructor [#]_ without any
|
||||
argument. This creates a :js:class:`Deferred` instance object with the
|
||||
following methods:
|
||||
|
||||
:js:func:`Deferred.resolve`
|
||||
|
||||
As its name indicates, this method moves the deferred to the
|
||||
"Resolved" state. It can be provided as many arguments as
|
||||
necessary, these arguments will be provided to any pending success
|
||||
callback.
|
||||
|
||||
:js:func:`Deferred.reject`
|
||||
|
||||
Similar to :js:func:`~Deferred.resolve`, but moves the deferred to
|
||||
the "Rejected" state and calls pending failure handlers.
|
||||
|
||||
:js:func:`Deferred.promise`
|
||||
|
||||
Creates a readonly view of the deferred object. It is generally a
|
||||
good idea to return a promise view of the deferred to prevent
|
||||
callers from resolving or rejecting the deferred in your stead.
|
||||
|
||||
:js:func:`~Deferred.reject` and :js:func:`~Deferred.resolve` are used
|
||||
to inform callers that the asynchronous operation has failed (or
|
||||
succeeded). These methods should simply be called when the
|
||||
asynchronous operation has ended, to notify anybody interested in its
|
||||
result(s).
|
||||
|
||||
.. _deferred-composition:
|
||||
|
||||
Composing deferreds
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
What we've seen so far is pretty nice, but mostly doable by passing
|
||||
functions to other functions (well adding functions post-facto would
|
||||
probably be a chore... still, doable).
|
||||
|
||||
Deferreds truly shine when code needs to compose asynchronous
|
||||
operations in some way or other, as they can be used as a basis for
|
||||
such composition.
|
||||
|
||||
There are two main forms of compositions over deferred: multiplexing
|
||||
and piping/cascading.
|
||||
|
||||
Deferred multiplexing
|
||||
`````````````````````
|
||||
|
||||
The most common reason for multiplexing deferred is simply performing
|
||||
2+ asynchronous operations and wanting to wait until all of them are
|
||||
done before moving on (and executing more stuff).
|
||||
|
||||
The jQuery multiplexing function for promises is :js:func:`when`.
|
||||
|
||||
.. note:: the multiplexing behavior of jQuery's :js:func:`when` is an
|
||||
(incompatible, mostly) extension of the behavior defined in
|
||||
`CommonJS Promises/B`_.
|
||||
|
||||
This function can take any number of promises [#]_ and will return a
|
||||
promise.
|
||||
|
||||
This returned promise will be resolved when *all* multiplexed promises
|
||||
are resolved, and will be rejected as soon as one of the multiplexed
|
||||
promises is rejected (it behaves like Python's ``all()``, but with
|
||||
promise objects instead of boolean-ish).
|
||||
|
||||
The resolved values of the various promises multiplexed via
|
||||
:js:func:`when` are mapped to the arguments of :js:func:`when`'s
|
||||
success callback, if they are needed. The resolved values of a promise
|
||||
are at the same index in the callback's arguments as the promise in
|
||||
the :js:func:`when` call so you will have:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
$.when(p0, p1, p2, p3).then(
|
||||
function (results0, results1, results2, results3) {
|
||||
// code
|
||||
});
|
||||
|
||||
.. warning::
|
||||
|
||||
in a normal mapping, each parameter to the callback would be an
|
||||
array: each promise is conceptually resolved with an array of 0..n
|
||||
values and these values are passed to :js:func:`when`'s
|
||||
callback. But jQuery treats deferreds resolving a single value
|
||||
specially, and "unwraps" that value.
|
||||
|
||||
For instance, in the code block above if the index of each promise
|
||||
is the number of values it resolves (0 to 3), ``results0`` is an
|
||||
empty array, ``results2`` is an array of 2 elements (a pair) but
|
||||
``results1`` is the actual value resolved by ``p1``, not an array.
|
||||
|
||||
Deferred chaining
|
||||
`````````````````
|
||||
|
||||
A second useful composition is starting an asynchronous operation as
|
||||
the result of an other asynchronous operation, and wanting the result
|
||||
of both: :js:func:`Deferred.then` returns the deferred on which it was
|
||||
called, so handle e.g. OpenERP's search/read sequence with this would
|
||||
require something along the lines of:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var result = $.Deferred();
|
||||
Model.search(condition).then(function (ids) {
|
||||
Model.read(ids, fields).then(function (records) {
|
||||
result.resolve(records);
|
||||
});
|
||||
});
|
||||
return result.promise();
|
||||
|
||||
While it doesn't look too bad for trivial code, this quickly gets
|
||||
unwieldy.
|
||||
|
||||
Instead, jQuery provides a tool to handle this kind of chains:
|
||||
:js:func:`Deferred.pipe`.
|
||||
|
||||
:js:func:`~Deferred.pipe` has the same signature as
|
||||
:js:func:`~Deferred.then` and could be used in the same manner
|
||||
provided its return value was not used.
|
||||
|
||||
It differs from :js:func:`~Deferred.then` in two ways: it returns a
|
||||
new promise object, not the one it was called with, and the return
|
||||
values of the callbacks is actually important to it: whichever
|
||||
callback is called,
|
||||
|
||||
* If the callback is not set (not provided or left to null), the
|
||||
resolution or rejection value(s) is simply forwarded to
|
||||
:js:func:`~Deferred.pipe`'s promise (it's essentially a noop)
|
||||
|
||||
* If the callback is set and does not return an observable object (a
|
||||
deferred or a promise), the value it returns (``undefined`` if it
|
||||
does not return anything) will replace the value it was given, e.g.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
promise.pipe(function () {
|
||||
console.log('called');
|
||||
});
|
||||
|
||||
will resolve with the sole value ``undefined``.
|
||||
|
||||
* If the callback is set and returns an observable object, that object
|
||||
will be the actual resolution (and result) of the pipe. This means a
|
||||
resolved promise from the failure callback will resolve the pipe,
|
||||
and a failure promise from the success callback will reject the
|
||||
pipe.
|
||||
|
||||
This provides an easy way to chain operation successes, and the
|
||||
previous piece of code can now be rewritten:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
return Model.search(condition).pipe(function (ids) {
|
||||
return Model.read(ids, fields);
|
||||
});
|
||||
|
||||
the result of the whole expression will encode failure if either
|
||||
``search`` or ``read`` fails (with the right rejection values), and
|
||||
will be resolved with ``read``'s resolution values if the chain
|
||||
executes correctly.
|
||||
|
||||
:js:func:`~Deferred.pipe` is also useful to adapt third-party
|
||||
promise-based APIs, in order to filter their resolution value counts
|
||||
for instance (to take advantage of :js:func:`when` 's special treatment
|
||||
of single-value promises).
|
||||
|
||||
jQuery.Deferred API
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. js:function:: when(deferreds…)
|
||||
|
||||
:param deferreds: deferred objects to multiplex
|
||||
:returns: a multiplexed deferred
|
||||
:rtype: :js:class:`Deferred`
|
||||
|
||||
.. js:class:: Deferred
|
||||
|
||||
.. js:function:: Deferred.then(doneCallback[, failCallback])
|
||||
|
||||
Attaches new callbacks to the resolution or rejection of the
|
||||
deferred object. Callbacks are executed in the order they are
|
||||
attached to the deferred.
|
||||
|
||||
To provide only a failure callback, pass ``null`` as the
|
||||
``doneCallback``, to provide only a success callback the
|
||||
second argument can just be ignored (and not passed at all).
|
||||
|
||||
:param doneCallback: function called when the deferred is resolved
|
||||
:type doneCallback: Function
|
||||
:param failCallback: function called when the deferred is rejected
|
||||
:type failCallback: Function
|
||||
:returns: the deferred object on which it was called
|
||||
:rtype: :js:class:`Deferred`
|
||||
|
||||
.. js:function:: Deferred.done(doneCallback)
|
||||
|
||||
Attaches a new success callback to the deferred, shortcut for
|
||||
``deferred.then(doneCallback)``.
|
||||
|
||||
This is a jQuery extension to `CommonJS Promises/A`_ providing
|
||||
little value over calling :js:func:`~Deferred.then` directly,
|
||||
it should be avoided.
|
||||
|
||||
:param doneCallback: function called when the deferred is resolved
|
||||
:type doneCallback: Function
|
||||
:returns: the deferred object on which it was called
|
||||
:rtype: :js:class:`Deferred`
|
||||
|
||||
.. js:function:: Deferred.fail(failCallback)
|
||||
|
||||
Attaches a new failure callback to the deferred, shortcut for
|
||||
``deferred.then(null, failCallback)``.
|
||||
|
||||
A second jQuery extension to `Promises/A <CommonJS
|
||||
Promises/A>`_. Although it provides more value than
|
||||
:js:func:`~Deferred.done`, it still is not much and should be
|
||||
avoided as well.
|
||||
|
||||
:param failCallback: function called when the deferred is rejected
|
||||
:type failCallback: Function
|
||||
:returns: the deferred object on which it was called
|
||||
:rtype: :js:class:`Deferred`
|
||||
|
||||
.. js:function:: Deferred.promise()
|
||||
|
||||
Returns a read-only view of the deferred object, with all
|
||||
mutators (resolve and reject) methods removed.
|
||||
|
||||
.. js:function:: Deferred.resolve(value…)
|
||||
|
||||
Called to resolve a deferred, any value provided will be
|
||||
passed onto the success handlers of the deferred object.
|
||||
|
||||
Resolving a deferred which has already been resolved or
|
||||
rejected has no effect.
|
||||
|
||||
.. js:function:: Deferred.reject(value…)
|
||||
|
||||
Called to reject (fail) a deferred, any value provided will be
|
||||
passed onto the failure handler of the deferred object.
|
||||
|
||||
Rejecting a deferred which has already been resolved or
|
||||
rejected has no effect.
|
||||
|
||||
.. js:function:: Deferred.pipe(doneFilter[, failFilter])
|
||||
|
||||
Filters the result of a deferred, able to transform a success
|
||||
into failure and a failure into success, or to delay
|
||||
resolution further.
|
||||
|
||||
.. [#] or simply calling :js:class:`Deferred` as a function, the
|
||||
result is the same
|
||||
|
||||
.. [#] or not-promises, the `CommonJS Promises/B`_ role of
|
||||
:js:func:`when` is to be able to treat values and promises
|
||||
uniformly: :js:func:`when` will pass promises through directly,
|
||||
but non-promise values and objects will be transformed into a
|
||||
resolved promise (resolving themselves with the value itself).
|
||||
|
||||
jQuery's :js:func:`when` keeps this behavior making deferreds
|
||||
easy to build from "static" values, or allowing defensive code
|
||||
where expected promises are wrapped in :js:func:`when` just in
|
||||
case.
|
||||
|
||||
.. _promises: http://en.wikipedia.org/wiki/Promise_(programming)
|
||||
.. _jQuery's deferred: http://api.jquery.com/category/deferred-object/
|
||||
.. _CommonJS Promises/A: http://wiki.commonjs.org/wiki/Promises/A
|
||||
.. _CommonJS Promises/B: http://wiki.commonjs.org/wiki/Promises/B
|
||||
.. _mocks: http://en.wikipedia.org/wiki/Mock_object
|
||||
@@ -1,108 +0,0 @@
|
||||
API changes from OpenERP Web 6.1 to 7.0
|
||||
=======================================
|
||||
|
||||
DataSet -> Model
|
||||
----------------
|
||||
|
||||
The 6.1 ``DataSet`` API has been deprecated in favor of the smaller
|
||||
and more orthogonal :doc:`Model </rpc>` API, which more closely
|
||||
matches the API in OpenERP Web's Python side and in OpenObject addons
|
||||
and removes most stateful behavior of DataSet.
|
||||
|
||||
Migration guide
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
* Actual arbitrary RPC calls can just be remapped on a
|
||||
:js:class:`~openerp.web.Model` instance:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
dataset.call(method, args)
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
dataset.call_and_eval(method, args)
|
||||
|
||||
can be replaced by calls to :js:func:`openerp.web.Model.call`:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
model.call(method, args)
|
||||
|
||||
If callbacks are passed directly to the older methods, they need to
|
||||
be added to the new one via ``.then()``.
|
||||
|
||||
.. note::
|
||||
|
||||
The ``context_index`` and ``domain_index`` features were not
|
||||
ported, context and domain now need to be passed in "in full",
|
||||
they won't be automatically filled with the user's current
|
||||
context.
|
||||
|
||||
* Shorcut methods (``name_get``, ``name_search``, ``unlink``,
|
||||
``write``, ...) should be ported to
|
||||
:js:func:`openerp.web.Model.call`, using the server's original
|
||||
signature. On the other hand, the non-shortcut equivalents can now
|
||||
use keyword arguments (see :js:func:`~openerp.web.Model.call`'s
|
||||
signature for details)
|
||||
|
||||
* ``read_slice``, which allowed a single round-trip to perform a
|
||||
search and a read, should be reimplemented via
|
||||
:js:class:`~openerp.web.Query` objects (see:
|
||||
:js:func:`~openerp.web.Model.query`) for clearer and simpler
|
||||
code. ``read_index`` should be replaced by a
|
||||
:js:class:`~openerp.web.Query` as well, combining
|
||||
:js:func:`~openerp.web.Query.offset` and
|
||||
:js:func:`~openerp.web.Query.first`.
|
||||
|
||||
Rationale
|
||||
~~~~~~~~~
|
||||
|
||||
Renaming
|
||||
|
||||
The name *DataSet* exists in the CS community consciousness, and
|
||||
(as its name implies) it's a set of data (often fetched from a
|
||||
database, maybe lazily). OpenERP Web's dataset behaves very
|
||||
differently as it does not store (much) data (only a bunch of ids
|
||||
and just enough state to break things). The name "Model" matches
|
||||
the one used on the Python side for the task of building an RPC
|
||||
proxy to OpenERP objects.
|
||||
|
||||
API simplification
|
||||
|
||||
``DataSet`` has a number of methods which serve as little more
|
||||
than shortcuts, or are there due to domain and context evaluation
|
||||
issues in 6.1.
|
||||
|
||||
The shortcuts really add little value, and OpenERP Web 6.2 embeds
|
||||
a restricted Python evaluator (in javascript) meaning most of the
|
||||
context and domain parsing & evaluation can be moved to the
|
||||
javascript code and does not require cooperative RPC bridging.
|
||||
|
||||
DataGroup -> also Model
|
||||
-----------------------
|
||||
|
||||
Alongside the deprecation of ``DataSet`` for
|
||||
:js:class:`~openerp.web.Model`, OpenERP Web 7.0 also deprecates
|
||||
``DataGroup`` and its subtypes in favor of a single method on
|
||||
:js:class:`~openerp.web.Query`:
|
||||
:js:func:`~openerp.web.Query.group_by`.
|
||||
|
||||
Migration guide
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Rationale
|
||||
~~~~~~~~~
|
||||
|
||||
While the ``DataGroup`` API worked (mostly), it is quite odd and
|
||||
alien-looking, a bit too Smalltalk-inspired (behaves like a
|
||||
self-contained flow-control structure for reasons which may or may not
|
||||
have been good).
|
||||
|
||||
Because it is heavily related to ``DataSet`` (as it *yields*
|
||||
``DataSet`` objects), deprecating ``DataSet`` automatically deprecates
|
||||
``DataGroup`` (if we want to stay consistent), which is a good time to
|
||||
make the API more imperative and look more like what most developers
|
||||
are used to.
|
||||
-257
@@ -1,257 +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.insert(0, os.path.abspath('.'))
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
sys.path.append(os.path.abspath('..'))
|
||||
sys.path.append(os.path.abspath('../openerp'))
|
||||
|
||||
# -- 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']
|
||||
|
||||
# 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 Server 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.0b'
|
||||
|
||||
# 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-server-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-server-doc.tex', u'OpenERP Server 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-server-doc', u'OpenERP Server 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', 'OpenERPServerDocumentation', u'OpenERP Server Developers Documentation',
|
||||
u'OpenERP s.a.', 'OpenERPServerDocumentation', 'Developers documentation for the openobject-server 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'
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org/', None),
|
||||
'openerpweb': ('http://doc.openerp.com/trunk/developers/web', None),
|
||||
'openerpdev': ('http://doc.openerp.com/trunk/developers', None),
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,112 +0,0 @@
|
||||
.. highlight:: javascript
|
||||
|
||||
Creating a new client action
|
||||
============================
|
||||
|
||||
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 simply 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.
|
||||
|
||||
* 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', 'openerp.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.
|
||||
|
||||
If the client action takes parameters, these parameters are passed in as a
|
||||
second positional parameter to the constructor::
|
||||
|
||||
init: function (parent, params) {
|
||||
// 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 = 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 ``$.Deferred`` so callers know when it's
|
||||
ready for interaction.
|
||||
|
||||
Although generally speaking client actions are not really
|
||||
interacted with.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
start: function () {
|
||||
return $.when(
|
||||
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
|
||||
}));
|
||||
}
|
||||
@@ -1,32 +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.
|
||||
|
||||
Welcome to OpenERP Web's documentation!
|
||||
=======================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog-7.0
|
||||
|
||||
async
|
||||
rpc
|
||||
|
||||
widget
|
||||
search-view
|
||||
|
||||
list-view
|
||||
form-notes
|
||||
|
||||
guides/client-action
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
@@ -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
|
||||
|
||||
``.oe_readonly``
|
||||
|
||||
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:
|
||||
|
||||
``.oe_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-notes>` 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
@@ -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
|
||||
-276
@@ -1,276 +0,0 @@
|
||||
Outside the box: network interactions
|
||||
=====================================
|
||||
|
||||
Building static displays is all nice and good and allows for neat
|
||||
effects (and sometimes you're given data to display from third parties
|
||||
so you don't have to make any effort), but a point generally comes
|
||||
where you'll want to talk to the world and make some network requests.
|
||||
|
||||
OpenERP Web provides two primary APIs to handle this, a low-level
|
||||
JSON-RPC based API communicating with the Python section of OpenERP
|
||||
Web (and of your addon, if you have a Python part) and a high-level
|
||||
API above that allowing your code to talk directly to the OpenERP
|
||||
server, using familiar-looking calls.
|
||||
|
||||
All networking APIs are :doc:`asynchronous </async>`. As a result, all
|
||||
of them will return :js:class:`Deferred` objects (whether they resolve
|
||||
those with values or not). Understanding how those work before before
|
||||
moving on is probably necessary.
|
||||
|
||||
High-level API: calling into OpenERP models
|
||||
-------------------------------------------
|
||||
|
||||
Access to OpenERP object methods (made available through XML-RPC from
|
||||
the server) is done via the :js:class:`openerp.web.Model` class. This
|
||||
class maps onto the OpenERP server objects via two primary methods,
|
||||
:js:func:`~openerp.web.Model.call` and
|
||||
:js:func:`~openerp.web.Model.query`.
|
||||
|
||||
:js:func:`~openerp.web.Model.call` is a direct mapping to the
|
||||
corresponding method of the OpenERP server object. Its usage is
|
||||
similar to that of the OpenERP Model API, with three differences:
|
||||
|
||||
* The interface is :doc:`asynchronous </async>`, so instead of
|
||||
returning results directly RPC method calls will return
|
||||
:js:class:`Deferred` instances, which will themselves resolve to the
|
||||
result of the matching RPC call.
|
||||
|
||||
* Because ECMAScript 3/Javascript 1.5 doesnt feature any equivalent to
|
||||
``__getattr__`` or ``method_missing``, there needs to be an explicit
|
||||
method to dispatch RPC methods.
|
||||
|
||||
* No notion of pooler, the model proxy is instantiated where needed,
|
||||
not fetched from an other (somewhat global) object
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var Users = new Model('res.users');
|
||||
|
||||
Users.call('change_password', ['oldpassword', 'newpassword'],
|
||||
{context: some_context}).then(function (result) {
|
||||
// do something with change_password result
|
||||
});
|
||||
|
||||
:js:func:`~openerp.web.Model.query` is a shortcut for a builder-style
|
||||
interface to searches (``search`` + ``read`` in OpenERP RPC terms). It
|
||||
returns a :js:class:`~openerp.web.Query` object which is immutable but
|
||||
allows building new :js:class:`~openerp.web.Query` instances from the
|
||||
first one, adding new properties or modifiying the parent object's:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
Users.query(['name', 'login', 'user_email', 'signature'])
|
||||
.filter([['active', '=', true], ['company_id', '=', main_company]])
|
||||
.limit(15)
|
||||
.all().then(function (users) {
|
||||
// do work with users records
|
||||
});
|
||||
|
||||
The query is only actually performed when calling one of the query
|
||||
serialization methods, :js:func:`~openerp.web.Query.all` and
|
||||
:js:func:`~openerp.web.Query.first`. These methods will perform a new
|
||||
RPC call every time they are called.
|
||||
|
||||
For that reason, it's actually possible to keep "intermediate" queries
|
||||
around and use them differently/add new specifications on them.
|
||||
|
||||
.. js:class:: openerp.web.Model(name)
|
||||
|
||||
.. js:attribute:: openerp.web.Model.name
|
||||
|
||||
name of the OpenERP model this object is bound to
|
||||
|
||||
.. js:function:: openerp.web.Model.call(method[, args][, kwargs])
|
||||
|
||||
Calls the ``method`` method of the current model, with the
|
||||
provided positional and keyword arguments.
|
||||
|
||||
:param String method: method to call over rpc on the
|
||||
:js:attr:`~openerp.web.Model.name`
|
||||
:param Array<> args: positional arguments to pass to the
|
||||
method, optional
|
||||
:param Object<> kwargs: keyword arguments to pass to the
|
||||
method, optional
|
||||
:rtype: Deferred<>
|
||||
|
||||
.. js:function:: openerp.web.Model.query(fields)
|
||||
|
||||
:param Array<String> fields: list of fields to fetch during
|
||||
the search
|
||||
:returns: a :js:class:`~openerp.web.Query` object
|
||||
representing the search to perform
|
||||
|
||||
.. js:class:: openerp.web.Query(fields)
|
||||
|
||||
The first set of methods is the "fetching" methods. They perform
|
||||
RPC queries using the internal data of the object they're called
|
||||
on.
|
||||
|
||||
.. js:function:: openerp.web.Query.all()
|
||||
|
||||
Fetches the result of the current
|
||||
:js:class:`~openerp.web.Query` object's search.
|
||||
|
||||
:rtype: Deferred<Array<>>
|
||||
|
||||
.. js:function:: openerp.web.Query.first()
|
||||
|
||||
Fetches the **first** result of the current
|
||||
:js:class:`~openerp.web.Query`, or ``null`` if the current
|
||||
:js:class:`~openerp.web.Query` does have any result.
|
||||
|
||||
:rtype: Deferred<Object | null>
|
||||
|
||||
.. js:function:: openerp.web.Query.count()
|
||||
|
||||
Fetches the number of records the current
|
||||
:js:class:`~openerp.web.Query` would retrieve.
|
||||
|
||||
:rtype: Deferred<Number>
|
||||
|
||||
.. js:function:: openerp.web.Query.group_by(grouping...)
|
||||
|
||||
Fetches the groups for the query, using the first specified
|
||||
grouping parameter
|
||||
|
||||
:param Array<String> grouping: Lists the levels of grouping
|
||||
asked of the server. Grouping
|
||||
can actually be an array or
|
||||
varargs.
|
||||
:rtype: Deferred<Array<openerp.web.QueryGroup>> | null
|
||||
|
||||
The second set of methods is the "mutator" methods, they create a
|
||||
**new** :js:class:`~openerp.web.Query` object with the relevant
|
||||
(internal) attribute either augmented or replaced.
|
||||
|
||||
.. js:function:: openerp.web.Query.context(ctx)
|
||||
|
||||
Adds the provided ``ctx`` to the query, on top of any existing
|
||||
context
|
||||
|
||||
.. js:function:: openerp.web.Query.filter(domain)
|
||||
|
||||
Adds the provided domain to the query, this domain is
|
||||
``AND``-ed to the existing query domain.
|
||||
|
||||
.. js:function:: opeenrp.web.Query.offset(offset)
|
||||
|
||||
Sets the provided offset on the query. The new offset
|
||||
*replaces* the old one.
|
||||
|
||||
.. js:function:: openerp.web.Query.limit(limit)
|
||||
|
||||
Sets the provided limit on the query. The new limit *replaces*
|
||||
the old one.
|
||||
|
||||
.. js:function:: openerp.web.Query.order_by(fields…)
|
||||
|
||||
Overrides the model's natural order with the provided field
|
||||
specifications. Behaves much like Django's `QuerySet.order_by
|
||||
<https://docs.djangoproject.com/en/dev/ref/models/querysets/#order-by>`_:
|
||||
|
||||
* Takes 1..n field names, in order of most to least importance
|
||||
(the first field is the first sorting key). Fields are
|
||||
provided as strings.
|
||||
|
||||
* A field specifies an ascending order, unless it is prefixed
|
||||
with the minus sign "``-``" in which case the field is used
|
||||
in the descending order
|
||||
|
||||
Divergences from Django's sorting include a lack of random sort
|
||||
(``?`` field) and the inability to "drill down" into relations
|
||||
for sorting.
|
||||
|
||||
Aggregation (grouping)
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OpenERP has powerful grouping capacities, but they are kind-of strange
|
||||
in that they're recursive, and level n+1 relies on data provided
|
||||
directly by the grouping at level n. As a result, while ``read_group``
|
||||
works it's not a very intuitive API.
|
||||
|
||||
OpenERP Web 7.0 eschews direct calls to ``read_group`` in favor of
|
||||
calling a method of :js:class:`~openerp.web.Query`, `much in the way
|
||||
it is one in SQLAlchemy
|
||||
<http://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.group_by>`_ [#]_:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
some_query.group_by(['field1', 'field2']).then(function (groups) {
|
||||
// do things with the fetched groups
|
||||
});
|
||||
|
||||
This method is asynchronous when provided with 1..n fields (to group
|
||||
on) as argument, but it can also be called without any field (empty
|
||||
fields collection or nothing at all). In this case, instead of
|
||||
returning a Deferred object it will return ``null``.
|
||||
|
||||
When grouping criterion come from a third-party and may or may not
|
||||
list fields (e.g. could be an empty list), this provides two ways to
|
||||
test the presence of actual subgroups (versus the need to perform a
|
||||
regular query for records):
|
||||
|
||||
* A check on ``group_by``'s result and two completely separate code
|
||||
paths
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var groups;
|
||||
if (groups = some_query.group_by(gby)) {
|
||||
groups.then(function (gs) {
|
||||
// groups
|
||||
});
|
||||
}
|
||||
// no groups
|
||||
|
||||
* Or a more coherent code path using :js:func:`when`'s ability to
|
||||
coerce values into deferreds:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
$.when(some_query.group_by(gby)).then(function (groups) {
|
||||
if (!groups) {
|
||||
// No grouping
|
||||
} else {
|
||||
// grouping, even if there are no groups (groups
|
||||
// itself could be an empty array)
|
||||
}
|
||||
});
|
||||
|
||||
The result of a (successful) :js:func:`~openerp.web.Query.group_by` is
|
||||
an array of :js:class:`~openerp.web.QueryGroup`.
|
||||
|
||||
Low-level API: RPC calls to Python side
|
||||
---------------------------------------
|
||||
|
||||
While the previous section is great for calling core OpenERP code
|
||||
(models code), it does not work if you want to call the Python side of
|
||||
OpenERP Web.
|
||||
|
||||
For this, a lower-level API exists on on
|
||||
:js:class:`~openerp.web.Connection` objects (usually available through
|
||||
``openerp.connection``): the ``rpc`` method.
|
||||
|
||||
This method simply takes an absolute path (which is the combination of
|
||||
the Python controller's ``_cp_path`` attribute and the name of the
|
||||
method you want to call) and a mapping of attributes to values (applied
|
||||
as keyword arguments on the Python method [#]_). This function fetches
|
||||
the return value of the Python methods, converted to JSON.
|
||||
|
||||
For instance, to call the ``eval_domain_and_context`` of the
|
||||
:class:`~web.controllers.main.Session` controller:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
openerp.connection.rpc('/web/session/eval_domain_and_context', {
|
||||
domains: ds,
|
||||
contexts: cs
|
||||
}).then(function (result) {
|
||||
// handle result
|
||||
});
|
||||
|
||||
.. [#] with a small twist: SQLAlchemy's ``orm.query.Query.group_by``
|
||||
is not terminal, it returns a query which can still be altered.
|
||||
|
||||
.. [#] except for ``context``, which is extracted and stored in the
|
||||
request object itself.
|
||||
@@ -1,549 +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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
research. 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
|
||||
-274
@@ -1,274 +0,0 @@
|
||||
User Interaction: Widget
|
||||
========================
|
||||
|
||||
This is the base class for all visual components. It corresponds to an MVC
|
||||
view. It provides a number of services to handle a section of a page:
|
||||
|
||||
* Rendering with QWeb
|
||||
|
||||
* Parenting-child relations
|
||||
|
||||
* Life-cycle management (including facilitating children destruction when a
|
||||
parent object is removed)
|
||||
|
||||
* DOM insertion, via jQuery-powered insertion methods. Insertion targets can
|
||||
be anything the corresponding jQuery method accepts (generally selectors,
|
||||
DOM nodes and jQuery objects):
|
||||
|
||||
:js:func:`~openerp.base.Widget.appendTo`
|
||||
Renders the widget and inserts it as the last child of the target, uses
|
||||
`.appendTo()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.prependTo`
|
||||
Renders the widget and inserts it as the first child of the target, uses
|
||||
`.prependTo()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.insertAfter`
|
||||
Renders the widget and inserts it as the preceding sibling of the target,
|
||||
uses `.insertAfter()`_
|
||||
|
||||
:js:func:`~openerp.base.Widget.insertBefore`
|
||||
Renders the widget and inserts it as the following sibling of the target,
|
||||
uses `.insertBefore()`_
|
||||
|
||||
* Backbone-compatible shortcuts
|
||||
|
||||
DOM Root
|
||||
--------
|
||||
|
||||
A :js:class:`~openerp.web.Widget` is responsible for a section of the
|
||||
page materialized by the DOM root of the widget. The DOM root is
|
||||
available via the :js:attr:`~openerp.web.Widget.el` and
|
||||
:js:attr:`~openerp.web.Widget.$element` attributes, which are
|
||||
respectively the raw DOM Element and the jQuery wrapper around the DOM
|
||||
element.
|
||||
|
||||
There are two main ways to define and generate this DOM root:
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.template
|
||||
|
||||
Should be set to the name of a QWeb template (a
|
||||
:js:class:`String`). If set, the template will be rendered after
|
||||
the widget has been initialized but before it has been
|
||||
started. The root element generated by the template will be set as
|
||||
the DOM root of the widget.
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.tagName
|
||||
|
||||
Used if the widget has no template defined. Defaults to ``div``,
|
||||
will be used as the tag name to create the DOM element to set as
|
||||
the widget's DOM root. It is possible to further customize this
|
||||
generated DOM root with the following attributes:
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.id
|
||||
|
||||
Used to generate an ``id`` attribute on the generated DOM
|
||||
root.
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.className
|
||||
|
||||
Used to generate a ``class`` attribute on the generated DOM root.
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.attributes
|
||||
|
||||
Mapping (object literal) of attribute names to attribute
|
||||
values. Each of these k:v pairs will be set as a DOM attribute
|
||||
on the generated DOM root.
|
||||
|
||||
None of these is used in case a template is specified on the widget.
|
||||
|
||||
The DOM root can also be defined programmatically by overridding
|
||||
|
||||
.. js:function:: openerp.web.Widget.renderElement
|
||||
|
||||
Renders the widget's DOM root and sets it. The default
|
||||
implementation will render a set template or generate an element
|
||||
as described above, and will call
|
||||
:js:func:`~openerp.web.Widget.setElement` on the result.
|
||||
|
||||
Any override to :js:func:`~openerp.web.Widget.renderElement` which
|
||||
does not call its ``_super`` **must** call
|
||||
:js:func:`~openerp.web.Widget.setElement` with whatever it
|
||||
generated or the widget's behavior is undefined.r
|
||||
|
||||
.. note::
|
||||
|
||||
The default :js:func:`~openerp.web.Widget.renderElement` can
|
||||
be called repeatedly, it will *replace* the previous DOM root
|
||||
(using ``replaceWith``). However, this requires that the
|
||||
widget correctly sets and unsets its events (and children
|
||||
widgets). Generally,
|
||||
:js:func:`~openerp.web.Widget.renderElement` should not be
|
||||
called repeatedly unless the widget advertizes this feature.
|
||||
|
||||
Accessing DOM content
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because a widget is only responsible for the content below its DOM
|
||||
root, there is a shortcut for selecting sub-sections of a widget's
|
||||
DOM:
|
||||
|
||||
.. js:function:: openerp.web.Widget.$(selector)
|
||||
|
||||
Applies the CSS selector specified as parameter to the widget's
|
||||
DOM root.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
this.$(selector);
|
||||
|
||||
is functionally identical to:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
this.$element.find(selector);
|
||||
|
||||
:param String selector: CSS selector
|
||||
:returns: jQuery object
|
||||
|
||||
.. note:: this helper method is compatible with
|
||||
``Backbone.View.$``
|
||||
|
||||
Resetting the DOM root
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. js:function:: openerp.web.Widget.setElement(element)
|
||||
|
||||
Re-sets the widget's DOM root to the provided element, also
|
||||
handles re-setting the various aliases of the DOM root as well as
|
||||
unsetting and re-setting delegated events.
|
||||
|
||||
:param Element element: a DOM element or jQuery object to set as
|
||||
the widget's DOM root
|
||||
|
||||
.. note:: should be mostly compatible with `Backbone's
|
||||
setElement`_
|
||||
|
||||
DOM events handling
|
||||
-------------------
|
||||
|
||||
A widget will generally need to respond to user action within its
|
||||
section of the page. This entails binding events to DOM elements.
|
||||
|
||||
To this end, :js:class:`~openerp.web.Widget` provides an shortcut:
|
||||
|
||||
.. js:attribute:: openerp.web.Widget.events
|
||||
|
||||
Events are a mapping of ``event selector`` (an event name and a
|
||||
CSS selector separated by a space) to a callback. The callback can
|
||||
be either a method name in the widget or a function. In either
|
||||
case, the ``this`` will be set to the widget.
|
||||
|
||||
The selector is used for jQuery's `event delegation`_, the
|
||||
callback will only be triggered for descendants of the DOM root
|
||||
matching the selector [0]_. If the selector is left out (only an
|
||||
event name is specified), the event will be set directly on the
|
||||
widget's DOM root.
|
||||
|
||||
.. js:function:: openerp.web.Widget.delegateEvents
|
||||
|
||||
This method is in charge of binding
|
||||
:js:attr:`~openerp.web.Widget.events` to the DOM. It is
|
||||
automatically called after setting the widget's DOM root.
|
||||
|
||||
It can be overridden to set up more complex events than the
|
||||
:js:attr:`~openerp.web.Widget.events` map allows, but the parent
|
||||
should always be called (or :js:attr:`~openerp.web.Widget.events`
|
||||
won't be handled correctly).
|
||||
|
||||
.. js:function:: openerp.web.Widget.undelegateEvents
|
||||
|
||||
This method is in charge of unbinding
|
||||
:js:attr:`~openerp.web.Widget.events` from the DOM root when the
|
||||
widget is destroyed or the DOM root is reset, in order to avoid
|
||||
leaving "phantom" events.
|
||||
|
||||
It should be overridden to un-set any event set in an override of
|
||||
:js:func:`~openerp.web.Widget.delegateEvents`.
|
||||
|
||||
.. note:: this behavior should be compatible with `Backbone's
|
||||
delegateEvents`_, apart from not accepting any argument.
|
||||
|
||||
Subclassing Widget
|
||||
------------------
|
||||
|
||||
:js:class:`~openerp.base.Widget` is subclassed in the standard manner (via the
|
||||
:js:func:`~openerp.base.Class.extend` method), and provides a number of
|
||||
abstract properties and concrete methods (which you may or may not want to
|
||||
override). Creating a subclass looks like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyWidget = openerp.base.Widget.extend({
|
||||
// QWeb template to use when rendering the object
|
||||
template: "MyQWebTemplate",
|
||||
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
// insert code to execute before rendering, for object
|
||||
// initialization
|
||||
},
|
||||
start: function() {
|
||||
this._super();
|
||||
// post-rendering initialization code, at this point
|
||||
// ``this.$element`` has been initialized
|
||||
this.$element.find(".my_button").click(/* an example of event binding * /);
|
||||
|
||||
// if ``start`` is asynchronous, return a promise object so callers
|
||||
// know when the object is done initializing
|
||||
return this.rpc(/* … */)
|
||||
}
|
||||
});
|
||||
|
||||
The new class can then be used in the following manner:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// Create the instance
|
||||
var my_widget = new MyWidget(this);
|
||||
// Render and insert into DOM
|
||||
my_widget.appendTo(".some-div");
|
||||
|
||||
After these two lines have executed (and any promise returned by ``appendTo``
|
||||
has been resolved if needed), the widget is ready to be used.
|
||||
|
||||
.. note:: the insertion methods will start the widget themselves, and will
|
||||
return the result of :js:func:`~openerp.base.Widget.start()`.
|
||||
|
||||
If for some reason you do not want to call these methods, you will
|
||||
have to first call :js:func:`~openerp.base.Widget.render()` on the
|
||||
widget, then insert it into your DOM and start it.
|
||||
|
||||
If the widget is not needed anymore (because it's transient), simply terminate
|
||||
it:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
my_widget.destroy();
|
||||
|
||||
will unbind all DOM events, remove the widget's content from the DOM and
|
||||
destroy all widget data.
|
||||
|
||||
.. [0] not all DOM events are compatible with events delegation
|
||||
|
||||
.. _.appendTo():
|
||||
http://api.jquery.com/appendTo/
|
||||
|
||||
.. _.prependTo():
|
||||
http://api.jquery.com/prependTo/
|
||||
|
||||
.. _.insertAfter():
|
||||
http://api.jquery.com/insertAfter/
|
||||
|
||||
.. _.insertBefore():
|
||||
http://api.jquery.com/insertBefore/
|
||||
|
||||
.. _event delegation:
|
||||
http://api.jquery.com/delegate/
|
||||
|
||||
.. _Backbone's setElement:
|
||||
http://backbonejs.org/#View-setElement
|
||||
|
||||
.. _Backbone's delegateEvents:
|
||||
http://backbonejs.org/#View-delegateEvents
|
||||
|
||||
Reference in New Issue
Block a user