[REM] web: old documentation
AFAIK, this documentation isn't built anywhere and is not maintained anyway. Since the developer documentation was recently removed from odoo/odoo repository, it's the right time to remove this old doc content. closes odoo/odoo#71898 Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
@@ -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."
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,16 +0,0 @@
|
||||
<p class="logo"><a href="https://doc.odoo.com/">
|
||||
<img class="logo" src="{{ pathto('_static/openerp.png', 1) }}" alt="Logo"/>
|
||||
</a></p>
|
||||
|
||||
<h3>Other Docs</h3>
|
||||
<ul>
|
||||
<li><a href="https://doc.odoo.com/trunk">Odoo Developers Documentation</a></li>
|
||||
<li><a href="https://doc.odoo.com/trunk/server">Odoo Server Developers Documentation</a></li>
|
||||
<li><a href="https://doc.odoo.com/book">Odoo Users Documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://www.odoo.com/">The Odoo website</a></li>
|
||||
<li><a href="http://python.org/">The Python programming language</a></li>
|
||||
</ul>
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
@@ -1,396 +0,0 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
{% set page_width = '80em' %}
|
||||
{% set sidebar_width = '16em' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 15px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: {{ page_width }};
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: {{ sidebar_width }};
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 0px 0 0px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: {{ page_width }};
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
color: #444;
|
||||
font-size: 22px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
{% if theme_index_logo %}
|
||||
div.indexwrapper h1 {
|
||||
/* We don't want it, I don't know why theme_index_logo is triggered. */
|
||||
/* text-indent: -999999px; */
|
||||
background: url({{ theme_index_logo }}) no-repeat center center;
|
||||
height: {{ theme_index_logo_height }};
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 22px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #eee;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 =
|
||||
@@ -1,22 +0,0 @@
|
||||
{% extends "basic/layout.html" %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{# do not display relbars #}
|
||||
{% block relbar1 %}{% endblock %}
|
||||
{% block relbar2 %}
|
||||
{% if theme_github_fork %}
|
||||
<a target="_blank" href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
|
||||
src="static/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block sidebar1 %}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
@@ -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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -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 = ''
|
||||
@@ -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>
|
||||
+-- __manifest__.py
|
||||
+-- controllers/
|
||||
+-- static/
|
||||
+-- lib/
|
||||
+-- src/
|
||||
+-- css/
|
||||
+-- img/
|
||||
+-- js/
|
||||
+-- xml/
|
||||
+-- test/
|
||||
+-- test/
|
||||
@@ -1,114 +0,0 @@
|
||||
.. highlight:: javascript
|
||||
|
||||
Client actions
|
||||
==============
|
||||
|
||||
Client actions are the client-side version of OpenERP's "Server
|
||||
Actions": instead of allowing for semi-arbitrary code to be executed
|
||||
in the server, they allow for execution of client-customized code.
|
||||
|
||||
On the server side, a client action is an action of type
|
||||
``ir.actions.client``, which has (at most) two properties: a mandatory
|
||||
``tag``, which is an arbitrary string by which the client will
|
||||
identify the action, and an optional ``params`` which is simply a map
|
||||
of keys and values sent to the client as-is (this way, client actions
|
||||
can be made generic and reused in multiple contexts).
|
||||
|
||||
General Structure
|
||||
-----------------
|
||||
|
||||
In the OpenERP Web code, a client action only requires two pieces of
|
||||
information:
|
||||
|
||||
* Mapping the action's ``tag`` to an object
|
||||
|
||||
* Providing said object. Two different types of objects can be mapped
|
||||
to a client action:
|
||||
|
||||
* An OpenERP Web widget, which must inherit from
|
||||
:js:class:`openerp.web.Widget`
|
||||
|
||||
* A regular javascript function
|
||||
|
||||
The major difference is in the lifecycle of these:
|
||||
|
||||
* if the client action maps to a function, the function will be called
|
||||
when executing the action. The function can have no further
|
||||
interaction with the Web Client itself, although it can return an
|
||||
action which will be executed after it.
|
||||
|
||||
The function takes 2 parameters: the ActionManager calling it and
|
||||
the descriptor for the current action (the ``ir.actions.client``
|
||||
dictionary).
|
||||
|
||||
* if, on the other hand, the client action maps to a
|
||||
:js:class:`~openerp.web.Widget`, that
|
||||
:js:class:`~openerp.web.Widget` will be instantiated and added to
|
||||
the web client's canvas, with the usual
|
||||
:js:class:`~openerp.web.Widget` lifecycle (essentially, it will
|
||||
either take over the content area of the client or it will be
|
||||
integrated within a dialog).
|
||||
|
||||
For example, to create a client action displaying a ``res.widget``
|
||||
object::
|
||||
|
||||
// Registers the object 'openerp.web_dashboard.Widget' to the client
|
||||
// action tag 'board.home.widgets'
|
||||
instance.web.client_actions.add(
|
||||
'board.home.widgets', 'instance.web_dashboard.Widget');
|
||||
instance.web_dashboard.Widget = instance.web.Widget.extend({
|
||||
template: 'HomeWidget'
|
||||
});
|
||||
|
||||
At this point, the generic :js:class:`~openerp.web.Widget` lifecycle
|
||||
takes over, the template is rendered, inserted in the client DOM,
|
||||
bound on the object's ``$el`` property and the object is started.
|
||||
|
||||
The second parameter to the constructor is the descriptor for the
|
||||
action itself, which contains any parameter provided::
|
||||
|
||||
init: function (parent, action) {
|
||||
// execute the Widget's init
|
||||
this._super(parent);
|
||||
// board.home.widgets only takes a single param, the identifier of the
|
||||
// res.widget object it should display. Store it for later
|
||||
this.widget_id = action.params.widget_id;
|
||||
}
|
||||
|
||||
More complex initialization (DOM manipulations, RPC requests, ...)
|
||||
should be performed in the :js:func:`~openerp.web.Widget.start()`
|
||||
method.
|
||||
|
||||
.. note::
|
||||
|
||||
As required by :js:class:`~openerp.web.Widget`'s contract, if
|
||||
:js:func:`~openerp.web.Widget.start()` executes any asynchronous
|
||||
code it should return a ``Promise`` so callers know when it's
|
||||
ready for interaction.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
start: function () {
|
||||
return Promise.all([
|
||||
this._super(),
|
||||
// Simply read the res.widget object this action should display
|
||||
new instance.web.Model('res.widget').call(
|
||||
'read', [[this.widget_id], ['title']])
|
||||
.then(this.proxy('on_widget_loaded')
|
||||
]);
|
||||
}
|
||||
|
||||
The client action can then behave exactly as it wishes to within its
|
||||
root (``this.$el``). In this case, it performs further renderings once
|
||||
its widget's content is retrieved::
|
||||
|
||||
on_widget_loaded: function (widgets) {
|
||||
var widget = widgets[0];
|
||||
var url = _.sprintf(
|
||||
'/web_dashboard/widgets/content?session_id=%s&widget_id=%d',
|
||||
this.session.session_id, widget.id);
|
||||
this.$el.html(QWeb.render('HomeWidget.content', {
|
||||
widget: widget,
|
||||
url: url
|
||||
}));
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# OpenERP Technical Documentation configuration file, created by
|
||||
# sphinx-quickstart on Fri Feb 17 16:14:06 2012.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
sys.path.insert(0, os.path.abspath('../addons'))
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.todo', 'sphinx.ext.viewcode',
|
||||
'patchqueue'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'OpenERP Web Developers Documentation'
|
||||
copyright = u'2012, OpenERP s.a.'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '7.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '7.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'flask'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_themes']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
html_sidebars = {
|
||||
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
|
||||
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
|
||||
'sourcelink.html', 'searchbox.html']
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'openerp-web-doc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'openerp-web-doc.tex', u'OpenERP Web Developers Documentation',
|
||||
u'OpenERP s.a.', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'openerp-web-doc', u'OpenERP Web Developers Documentation',
|
||||
[u'OpenERP s.a.'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'OpenERPWebDocumentation', u'OpenERP Web Developers Documentation',
|
||||
u'OpenERP s.a.', 'OpenERPWebDocumentation', 'Developers documentation for the openerp-web project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
todo_include_todos = True
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org/', None),
|
||||
'openerpserver': ('http://doc.openerp.com/trunk/developers/server', None),
|
||||
}
|
||||
@@ -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,194 +0,0 @@
|
||||
Guidelines and Recommendations
|
||||
==============================
|
||||
|
||||
Web Module Recommendations
|
||||
--------------------------
|
||||
|
||||
Identifiers (``id`` attribute) should be avoided
|
||||
''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
In generic applications and modules, ``@id`` limits the reusabily of
|
||||
components and tends to make code more brittle.
|
||||
|
||||
Just about all the time, they can be replaced with nothing, with
|
||||
classes or with keeping a reference to a DOM node or a jQuery element
|
||||
around.
|
||||
|
||||
.. note::
|
||||
|
||||
If it is *absolutely necessary* to have an ``@id`` (because a
|
||||
third-party library requires one and can't take a DOM element), it
|
||||
should be generated with `_.uniqueId
|
||||
<http://underscorejs.org/#uniqueId>`_ or some other similar
|
||||
method.
|
||||
|
||||
Avoid predictable/common CSS class names
|
||||
''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Class names such as "content" or "navigation" might match the desired
|
||||
meaning/semantics, but it is likely an other developer will have the
|
||||
same need, creating a naming conflict and unintended behavior. Generic
|
||||
class names should be prefixed with e.g. the name of the component
|
||||
they belong to (creating "informal" namespaces, much as in C or
|
||||
Objective-C)
|
||||
|
||||
Global selectors should be avoided
|
||||
''''''''''''''''''''''''''''''''''
|
||||
|
||||
Because a component may be used several times in a single page (an
|
||||
example in OpenERP is dashboards), queries should be restricted to a
|
||||
given component's scope. Unfiltered selections such as ``$(selector)``
|
||||
or ``document.querySelectorAll(selector)`` will generally lead to
|
||||
unintended or incorrect behavior.
|
||||
|
||||
OpenERP Web's :js:class:`~openerp.web.Widget` has an attribute
|
||||
providing its DOM root :js:attr:`Widget.$el <openerp.web.Widget.$el>`,
|
||||
and a shortcut to select nodes directly :js:attr:`Widget.$
|
||||
<openerp.web.Widget.$>`.
|
||||
|
||||
More generally, never assume your components own or controls anything
|
||||
beyond its own personal DOM.
|
||||
|
||||
Understand deferreds
|
||||
''''''''''''''''''''
|
||||
|
||||
Deferreds, promises, futures, …
|
||||
|
||||
Known under many names, these objects are essential to and (in OpenERP
|
||||
Web) widely used for making :doc:`asynchronous javascript operations
|
||||
<async>` palatable and understandable.
|
||||
|
||||
OpenERP Web guidelines
|
||||
----------------------
|
||||
|
||||
* HTML templating/rendering should use :doc:`qweb` unless absolutely
|
||||
trivial.
|
||||
|
||||
* All interactive components (components displaying information to the
|
||||
screen or intercepting DOM events) must inherit from
|
||||
:class:`~openerp.web.Widget` and correctly implement and use its API
|
||||
and lifecycle.
|
||||
|
||||
* All css classes must be prefixed with *oe_* .
|
||||
|
||||
* Asynchronous functions (functions which call :ref:`session.rpc
|
||||
<rpc_rpc>` directly or indirectly at the very least) *must* return
|
||||
deferreds, so that callers of overriders can correctly synchronize
|
||||
with them.
|
||||
|
||||
New Javascript guidelines
|
||||
-------------------------
|
||||
|
||||
From v11, we introduce a new coding standard for Odoo Javascript code. Here it
|
||||
is:
|
||||
|
||||
* add "use strict"; on top of every odoo JS module
|
||||
|
||||
* name all entities exported by a JS module. So, instead of
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
return Widget.extend({
|
||||
...
|
||||
});
|
||||
|
||||
you should use:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyWidget = Widget.extend({
|
||||
...
|
||||
});
|
||||
|
||||
return MyWidget
|
||||
|
||||
* there should be one space between function and the left parenthesis:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function (a, b) {}
|
||||
|
||||
* JS files should have a (soft) limit of 80 chars width, and a hard limit of 100
|
||||
|
||||
* document every functions and every files, with the style JSDoc.
|
||||
|
||||
* for function overriding other functions, consider adding the tag @override in
|
||||
the JS Doc. Also, you can mention which method is overridden:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/**
|
||||
* When a save operation has been confirmed from the model, this method is
|
||||
* called.
|
||||
*
|
||||
* @override method from field manager mixin
|
||||
* @param {string} id
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_confirmSave: function (id) {
|
||||
|
||||
* there should be an empty line between the main function comments and the tags,
|
||||
or parameter descriptions
|
||||
|
||||
* avoid introspection: don't build dynamically a method name and call it. It is
|
||||
more fragile and more difficult to refactor
|
||||
|
||||
* methods should be private if possible
|
||||
|
||||
* never read an attribute of an attribute on somethig that you have a reference.
|
||||
So, this is not good:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
this.myObject.propA.propB
|
||||
|
||||
* never use a reference to the parent widget
|
||||
|
||||
* avoid using the 'include' functionality: extending a class is fine and does
|
||||
not cause issue, including a class is much more fragile, and may not work.
|
||||
|
||||
* For the widgets, here is how the various attributes/functions should be
|
||||
ordered:
|
||||
|
||||
1. all static attributes, such as template, events, custom_events, ...
|
||||
|
||||
2. all methods from the lifecycle of a widget, in this order: init, willStart,
|
||||
start, destroy
|
||||
|
||||
3. If there are public methods, a section titled "Public", with an empty line
|
||||
before and after
|
||||
|
||||
4. all public methods, camelcased, in alphabetic order
|
||||
|
||||
5. If there are private methods, a section titled "Private", with an empty line
|
||||
before and after
|
||||
|
||||
6. all private methods, camelcased and prefixed with _, in alphabetic order
|
||||
|
||||
7. If there are event handlers, a section titled "Handlers", with an empty line
|
||||
before and after
|
||||
|
||||
8. all handlers, camelcased and prefixed with _on, in alphabetic order
|
||||
|
||||
9. If there are static methods, they should be in a section titled "Static".
|
||||
All static methods are considered public, camelcased with no _.
|
||||
|
||||
* write unit tests
|
||||
|
||||
* for the event handlers defined by the key 'event' or 'custom_events', don't
|
||||
inline the function. Always add a string name, and add the definition in the
|
||||
handler section
|
||||
|
||||
* one space after if and for
|
||||
|
||||
* never call private methods on another object
|
||||
|
||||
* object definition on more than one line: each element should have a trailing
|
||||
comma.
|
||||
|
||||
* strings: double quotes for all textual strings (such as "Hello"), and single
|
||||
quotes for all other strings, such as a css selector '.o_form_view'
|
||||
|
||||
* always use this._super.apply(this, arguments);
|
||||
|
||||
* keys in an object: ordered by alphabetic order
|
||||
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,55 +0,0 @@
|
||||
.. OpenERP Web documentation master file, created by
|
||||
sphinx-quickstart on Fri Mar 18 16:31:55 2011.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
OpenERP Web Reference Documentation
|
||||
===================================
|
||||
|
||||
See also the `OpenERP Web Training`_.
|
||||
|
||||
.. _OpenERP Web Training: https://doc.openerp.com/trunk/training/
|
||||
|
||||
Basics
|
||||
------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
module
|
||||
changelog-7.0
|
||||
|
||||
Server-Side Web Framework
|
||||
-------------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
web_controllers
|
||||
|
||||
Javascript
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
guidelines
|
||||
client_action
|
||||
testing
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
search_view
|
||||
list_view
|
||||
form_view
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
@@ -1,531 +0,0 @@
|
||||
List View
|
||||
=========
|
||||
|
||||
Style Hooks
|
||||
-----------
|
||||
|
||||
The list view provides a few style hook classes for re-styling of list views in
|
||||
various situations:
|
||||
|
||||
``.oe_list``
|
||||
|
||||
The root element of the list view, styling rules should be rooted
|
||||
on that class.
|
||||
|
||||
``table.oe_list_content``
|
||||
|
||||
The root table for the listview, accessory components may be
|
||||
generated or added outside this section, this is the list view
|
||||
"proper".
|
||||
|
||||
``.oe_list_buttons``
|
||||
|
||||
The action buttons array for the list view, with its sub-elements
|
||||
|
||||
``.oe_list_add``
|
||||
|
||||
The default "Create"/"Add" button of the list view
|
||||
|
||||
``.oe_alternative``
|
||||
|
||||
The "alternative choice" for the list view, by default text
|
||||
along the lines of "or import" with a link.
|
||||
|
||||
``.oe_list_field_cell``
|
||||
|
||||
The cell (``td``) for a given field of the list view, cells which
|
||||
are *not* fields (e.g. name of a group, or number of items in a
|
||||
group) will not have this class. The field cell can be further
|
||||
specified:
|
||||
|
||||
``.oe_number``
|
||||
|
||||
Numeric cell types (integer and float)
|
||||
|
||||
``.oe_button``
|
||||
|
||||
Action button (``button`` tag in the view) inside the cell
|
||||
|
||||
``.o_readonly_modifier``
|
||||
|
||||
Readonly field cell
|
||||
|
||||
``.oe_list_field_$type``
|
||||
|
||||
Additional class for the precise type of the cell, ``$type``
|
||||
is the field's @widget if there is one, otherwise it's the
|
||||
field's type.
|
||||
|
||||
``.oe_list_record_selector``
|
||||
|
||||
Selector cells
|
||||
|
||||
Editable list view
|
||||
++++++++++++++++++
|
||||
|
||||
The editable list view module adds a few supplementary style hook
|
||||
classes, for edition situations:
|
||||
|
||||
``.o_list_editable``
|
||||
|
||||
Added to the ``.oe_list`` when the list is editable (however that
|
||||
was done). The class may be removed on-the-fly if the list becomes
|
||||
non-editable.
|
||||
|
||||
``.oe_editing``
|
||||
|
||||
Added to both ``.oe_list`` and ``.oe_list_button`` (as the
|
||||
buttons may be outside of the list view) when a row of the list is
|
||||
currently being edited.
|
||||
|
||||
``tr.oe_edition``
|
||||
|
||||
Class set on the row being edited itself. Note that the edition
|
||||
form is *not* contained within the row, this allows for styling or
|
||||
modifying the row while it's being edited separately. Mostly for
|
||||
fields which can not be edited (e.g. read-only fields).
|
||||
|
||||
Columns display customization
|
||||
-----------------------------
|
||||
|
||||
The list view provides a registry to
|
||||
:js:class:`openerp.web.list.Column` objects allowing for the
|
||||
customization of a column's display (e.g. so that a binary field is
|
||||
rendered as a link to the binary file directly in the list view).
|
||||
|
||||
The registry is ``instance.web.list.columns``, the keys are of the
|
||||
form ``tag.type`` where ``tag`` can be ``field`` or ``button``, and
|
||||
``type`` can be either the field's type or the field's ``@widget`` (in
|
||||
the view).
|
||||
|
||||
Most of the time, you'll want to define a ``tag.widget`` key
|
||||
(e.g. ``field.progressbar``).
|
||||
|
||||
.. js:class:: openerp.web.list.Column(id, tag, attrs)
|
||||
|
||||
.. js:function:: openerp.web.list.Column.format(record_data, options)
|
||||
|
||||
Top-level formatting method, returns an empty string if the
|
||||
column is invisible (unless the ``process_modifiers=false``
|
||||
option is provided); returns ``options.value_if_empty`` or an
|
||||
empty string if there is no value in the record for the
|
||||
column.
|
||||
|
||||
Otherwise calls :js:func:`~openerp.web.list.Column._format`
|
||||
and returns its result.
|
||||
|
||||
This method only needs to be overridden if the column has no
|
||||
concept of values (and needs to bypass that check), for a
|
||||
button for instance.
|
||||
|
||||
Otherwise, custom columns should generally override
|
||||
:js:func:`~openerp.web.list.Column._format` instead.
|
||||
|
||||
:returns: String
|
||||
|
||||
.. js:function:: openerp.web.list.Column._format(record_data, options)
|
||||
|
||||
Never called directly, called if the column is visible and has
|
||||
a value.
|
||||
|
||||
The default implementation calls
|
||||
:js:func:`~openerp.web.format_value` and htmlescapes the
|
||||
result (via ``_.escape``).
|
||||
|
||||
Note that the implementation of
|
||||
:js:func:`~openerp.web.list.Column._format` *must* escape the
|
||||
data provided to it, its output will *not* be escaped by
|
||||
:js:func:`~openerp.web.list.Column.format`.
|
||||
|
||||
:returns: String
|
||||
|
||||
Editable list view
|
||||
------------------
|
||||
|
||||
List view edition is an extension to the base listview providing the
|
||||
capability of inline record edition by delegating to an embedded form
|
||||
view.
|
||||
|
||||
Editability status
|
||||
++++++++++++++++++
|
||||
|
||||
The editability status of a list view can be queried through the
|
||||
:js:func:`~openerp.web.ListView.editable` method, will return a falsy
|
||||
value if the listview is not currently editable.
|
||||
|
||||
The editability status is based on three flags:
|
||||
|
||||
``tree/@editable``
|
||||
|
||||
If present, can be either ``"top"`` or ``"bottom"``. Either will
|
||||
make the list view editable, with new records being respectively
|
||||
created at the top or at the bottom of the view.
|
||||
|
||||
``context.set_editable``
|
||||
|
||||
Boolean flag extracted from a search context (during the
|
||||
:js:func:`~openerp.web.ListView.do_search`` handler), ``true``
|
||||
will make the view editable (from the top), ``false`` or the
|
||||
absence of the flag is a noop.
|
||||
|
||||
``defaults.editable``
|
||||
|
||||
Like ``tree/@editable``, one of absent (``null``)), ``"top"`` or
|
||||
``"bottom"``, fallback for the list view if none of the previous
|
||||
two flags are set.
|
||||
|
||||
These three flags can only *make* a listview editable, they can *not*
|
||||
override a previously set flag. To do that, a listview user should
|
||||
instead cancel :ref:`the edit:before event <listview-edit-before>`.
|
||||
|
||||
The editable list view module adds a number of methods to the list
|
||||
view, on top of implementing the :js:class:`EditorDelegate` protocol:
|
||||
|
||||
Interaction Methods
|
||||
+++++++++++++++++++
|
||||
|
||||
.. js:function:: openerp.web.ListView.ensure_saved
|
||||
|
||||
Attempts to resolve the pending edition, if any, by saving the
|
||||
edited row's current state.
|
||||
|
||||
:returns: delegate resolving to all editions having been saved, or
|
||||
rejected if a pending edition could not be saved
|
||||
(e.g. validation failure)
|
||||
|
||||
.. js:function:: openerp.web.ListView.start_edition([record][, options])
|
||||
|
||||
Starts editing the provided record inline, through an overlay form
|
||||
view of editable fields in the record.
|
||||
|
||||
If no record is provided, creates a new one according to the
|
||||
editability configuration of the list view.
|
||||
|
||||
This method resolves any pending edition when invoked, before
|
||||
starting a new edition.
|
||||
|
||||
:param record: record to edit, or null to create a new record
|
||||
:type record: :js:class:`~openerp.web.list.Record`
|
||||
:param EditOptions options:
|
||||
:returns: delegate to the form used for the edition
|
||||
|
||||
.. js:function:: openerp.web.ListView.save_edition
|
||||
|
||||
Resolves the pending edition.
|
||||
|
||||
:returns: delegate to the save being completed, resolves to an
|
||||
object with two attributes ``created`` (flag indicating
|
||||
whether the saved record was just created or was
|
||||
updated) and ``record`` the reloaded record having been
|
||||
edited.
|
||||
|
||||
.. js:function:: openerp.web.ListView.cancel_edition([force=false])
|
||||
|
||||
Cancels pending edition, cleans up the list view in case of
|
||||
creation (removes the empty record being created).
|
||||
|
||||
:param Boolean force: doesn't check if the user has added any
|
||||
data, discards the edition unconditionally
|
||||
|
||||
Utility Methods
|
||||
+++++++++++++++
|
||||
|
||||
.. js:function:: openerp.web.ListView.get_cells_for(row)
|
||||
|
||||
Extracts the cells from a listview row, and puts them in a
|
||||
{fieldname: cell} mapping for analysis and manipulation.
|
||||
|
||||
:param jQuery row:
|
||||
:rtype: Object
|
||||
|
||||
.. js:function:: openerp.web.ListView.with_event(event_name, event, action[, args][, trigger_params])
|
||||
|
||||
Executes ``action`` in the context of the view's editor,
|
||||
bracketing it with cancellable event signals.
|
||||
|
||||
:param String event_name: base name for the bracketing event, will
|
||||
be postfixed by ``:before`` and
|
||||
``:after`` before being called
|
||||
(respectively before and after
|
||||
``action`` is executed)
|
||||
:param Object event: object passed to the ``:before`` event
|
||||
handlers.
|
||||
:param Function action: function called with the view's editor as
|
||||
its ``this``. May return a deferred.
|
||||
:param Array args: arguments passed to ``action``
|
||||
:param Array trigger_params: arguments passed to the ``:after``
|
||||
event handler alongside the results
|
||||
of ``action``
|
||||
|
||||
Behavioral Customizations
|
||||
+++++++++++++++++++++++++
|
||||
|
||||
.. js:function:: openerp.web.ListView.handle_onwrite(record)
|
||||
|
||||
Implements the handling of the ``onwrite`` listview attribute:
|
||||
calls the RPC methods specified by ``@onwrite``, and if that
|
||||
method returns an array of ids loads or reloads the records
|
||||
corresponding to those ids.
|
||||
|
||||
:param record: record being written having triggered the
|
||||
``onwrite`` callback
|
||||
:type record: openerp.web.list.Record
|
||||
:returns: deferred to all reloadings being done
|
||||
|
||||
Events
|
||||
++++++
|
||||
|
||||
For simpler interactions by/with external users of the listview, the
|
||||
view provides a number of dedicated events to its lifecycle.
|
||||
|
||||
.. note:: if an event is defined as *cancellable*, it means its first
|
||||
parameter is an object on which the ``cancel`` attribute can
|
||||
be set. If the ``cancel`` attribute is set, the view will
|
||||
abort its current behavior as soon as possible, and rollback
|
||||
any state modification.
|
||||
|
||||
Generally speaking, an event should only be cancelled (by
|
||||
setting the ``cancel`` flag to ``true``), uncancelling an
|
||||
event is undefined as event handlers are executed on a
|
||||
first-come-first-serve basis and later handlers may
|
||||
re-cancel an uncancelled event.
|
||||
|
||||
.. _listview-edit-before:
|
||||
|
||||
``edit:before`` *cancellable*
|
||||
|
||||
Invoked before the list view starts editing a record.
|
||||
|
||||
Provided with an event object with a single property ``record``,
|
||||
holding the attributes of the record being edited (``record`` is
|
||||
empty *but not null* for a new record)
|
||||
|
||||
``edit:after``
|
||||
|
||||
Invoked after the list view has gone into an edition state,
|
||||
provided with the attributes of the record being edited (see
|
||||
``edit:before``) as first parameter and the form used for the
|
||||
edition as second parameter.
|
||||
|
||||
``save:before`` *cancellable*
|
||||
|
||||
Invoked right before saving a pending edition, provided with an
|
||||
event object holding the listview's editor (``editor``) and the
|
||||
edition form (``form``)
|
||||
|
||||
``save:after``
|
||||
|
||||
Invoked after a save has been completed
|
||||
|
||||
``cancel:before`` *cancellable*
|
||||
|
||||
Invoked before cancelling a pending edition, provided with the
|
||||
same information as ``save:before``.
|
||||
|
||||
``cancel:after``
|
||||
|
||||
Invoked after a pending edition has been cancelled.
|
||||
|
||||
DOM events
|
||||
++++++++++
|
||||
|
||||
The list view has grown hooks for the ``keyup`` event on its edition
|
||||
form (during edition): any such event bubbling out of the edition form
|
||||
will be forwarded to a method ``keyup_EVENTNAME``, where ``EVENTNAME``
|
||||
is the name of the key in ``$.ui.keyCode``.
|
||||
|
||||
The method will also get the event object (originally passed to the
|
||||
``keyup`` handler) as its sole parameter.
|
||||
|
||||
The base editable list view has handlers for the ``ENTER`` and
|
||||
``ESCAPE`` keys.
|
||||
|
||||
Editor
|
||||
------
|
||||
|
||||
The list-edition modules does not generally interact with the embedded
|
||||
formview, delegating instead to its
|
||||
:js:class:`~openerp.web.list.Editor`.
|
||||
|
||||
.. js:class:: openerp.web.list.Editor(parent[, options])
|
||||
|
||||
The editor object provides a more convenient interface to form
|
||||
views, and simplifies the usage of form views for semi-arbitrary
|
||||
edition of stuff.
|
||||
|
||||
However, the editor does *not* task itself with being internally
|
||||
consistent at this point: calling
|
||||
e.g. :js:func:`~openerp.web.list.Editor.edit` multiple times in a
|
||||
row without saving or cancelling each edit is undefined.
|
||||
|
||||
:param parent:
|
||||
:type parent: :js:class:`~openerp.web.Widget`
|
||||
:param EditorOptions options:
|
||||
|
||||
.. js:function:: openerp.web.list.Editor.is_editing([record_state])
|
||||
|
||||
Indicates whether the editor is currently in the process of
|
||||
providing edition for a record.
|
||||
|
||||
Can be filtered by the state of the record being edited
|
||||
(whether it's a record being *created* or a record being
|
||||
*altered*), in which case it asserts both that an edition is
|
||||
underway and that the record being edited respectively does
|
||||
not yet exist in the database or already exists there.
|
||||
|
||||
:param record_state: state of the record being edited.
|
||||
Either ``"new"`` or ``"edit"``.
|
||||
:type record_state: String
|
||||
:rtype: Boolean
|
||||
|
||||
.. js:function:: openerp.web.list.Editor.edit(record, configureField[, options])
|
||||
|
||||
Loads the provided record into the internal form view and
|
||||
displays the form view.
|
||||
|
||||
Will also attempt to focus the first visible field of the form
|
||||
view.
|
||||
|
||||
:param Object record: record to load into the form view
|
||||
(key:value mapping similar to the result
|
||||
of a ``read``)
|
||||
:param configureField: function called with each field of the
|
||||
form view right after the form is
|
||||
displayed, lets whoever called this
|
||||
method do some last-minute
|
||||
configuration of form fields.
|
||||
:type configureField: Function<String, openerp.web.form.Field>
|
||||
:param EditOptions options:
|
||||
:returns: jQuery delegate to the form object
|
||||
|
||||
.. js:function:: openerp.web.list.Editor.save
|
||||
|
||||
Attempts to save the internal form, then hide it
|
||||
|
||||
:returns: delegate to the record under edition (with ``id``
|
||||
added for a creation). The record is not updated
|
||||
from when it was passed in, aside from the ``id``
|
||||
attribute.
|
||||
|
||||
.. js:function:: openerp.web.list.Editor.cancel([force=false])
|
||||
|
||||
Attemps to cancel the edition of the internal form, then hide
|
||||
the form
|
||||
|
||||
:param Boolean force: unconditionally cancels the edition of
|
||||
the internal form, even if the user has
|
||||
already entered data in it.
|
||||
:returns: delegate to the record under edition
|
||||
|
||||
.. js:class:: EditorOptions
|
||||
|
||||
.. js:attribute:: EditorOptions.formView
|
||||
|
||||
Form view (sub)-class to instantiate and delegate edition to.
|
||||
|
||||
By default, :js:class:`~openerp.web.FormView`
|
||||
|
||||
.. js:attribute:: EditorOptions.delegate
|
||||
|
||||
Object used to get various bits of information about how to
|
||||
display stuff.
|
||||
|
||||
By default, uses the editor's parent widget. See
|
||||
:js:class:`EditorDelegate` for the methods and attributes to
|
||||
provide.
|
||||
|
||||
.. js:class:: EditorDelegate
|
||||
|
||||
Informal protocol defining the methods and attributes expected of
|
||||
the :js:class:`~openerp.web.list.Editor`'s delegate.
|
||||
|
||||
.. js:attribute:: EditorDelegate.dataset
|
||||
|
||||
The dataset passed to the form view to synchronize the form
|
||||
view and the outer widget.
|
||||
|
||||
.. js:function:: EditorDelegate.edition_view(editor)
|
||||
|
||||
Called by the :js:class:`~openerp.web.list.Editor` object to
|
||||
get a form view (JSON) to pass along to the form view it
|
||||
created.
|
||||
|
||||
The result should be a valid form view, see :doc:`Form Notes
|
||||
<form_view>` for various peculiarities of the form view
|
||||
format.
|
||||
|
||||
:param editor: editor object asking for the view
|
||||
:type editor: :js:class:`~openerp.web.list.Editor`
|
||||
:returns: form view
|
||||
:rtype: Object
|
||||
|
||||
.. js:function:: EditorDelegate.prepends_on_create
|
||||
|
||||
By default, the :js:class:`~openerp.web.list.Editor` will
|
||||
append the ids of newly created records to the
|
||||
:js:attr:`EditorDelegate.dataset`. If this method returns
|
||||
``true``, it will prepend these ids instead.
|
||||
|
||||
:returns: whether new records should be prepended to the
|
||||
dataset (instead of appended)
|
||||
:rtype: Boolean
|
||||
|
||||
|
||||
.. js:class:: EditOptions
|
||||
|
||||
Options object optionally passed into a method starting an edition
|
||||
to configure its setup and behavior
|
||||
|
||||
.. js:attribute:: focus_field
|
||||
|
||||
Name of the field to set focus on after setting up the edition
|
||||
of the record.
|
||||
|
||||
If this option is not provided, or the requested field can not
|
||||
be focused (invisible, readonly or not in the view), the first
|
||||
visible non-readonly field is focused.
|
||||
|
||||
Changes from 6.1
|
||||
----------------
|
||||
|
||||
* The editable listview behavior has been rewritten pretty much from
|
||||
scratch, any code touching on editability will have to be modified
|
||||
|
||||
* The overloading of :js:class:`~openerp.web.ListView.Groups` and
|
||||
:js:class:`~openerp.web.ListView.List` for editability has been
|
||||
drastically simplified, and most of the behavior has been moved to
|
||||
the list view itself. Only
|
||||
:js:func:`~openerp.web.ListView.List.row_clicked` is still
|
||||
overridden.
|
||||
|
||||
* A new method ``get_row_for(record) -> jQuery(tr) | null`` has been
|
||||
added to both ListView.List and ListView.Group, it can be called
|
||||
from the list view to get the table row matching a record (if such
|
||||
a row exists).
|
||||
|
||||
* :js:func:`~openerp.web.ListView.do_button_action`'s core behavior
|
||||
has been split away to
|
||||
:js:func:`~openerp.web.ListView.handle_button`. This allows bypassing
|
||||
overrides of :js:func:`~openerp.web.ListView.do_button_action` in a
|
||||
parent class.
|
||||
|
||||
Ideally, :js:func:`~openerp.web.ListView.handle_button` should not be
|
||||
overridden.
|
||||
|
||||
* Modifiers handling has been improved (all modifiers information
|
||||
should now be available through :js:func:`~Column.modifiers_for`,
|
||||
not just ``invisible``)
|
||||
|
||||
* Changed some handling of the list view's record: a record may now
|
||||
have no id, and the listview will handle that correctly (for new
|
||||
records being created) as well as correctly handle the ``id`` being
|
||||
set.
|
||||
|
||||
* Extended the internal collections structure of the list view with
|
||||
`#find`_, `#succ`_ and `#pred`_.
|
||||
|
||||
.. _#find: http://underscorejs.org/#find
|
||||
|
||||
.. _#succ: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:succ
|
||||
|
||||
.. _#pred: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:pred
|
||||
@@ -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
|
||||
@@ -1,442 +0,0 @@
|
||||
.. _module:
|
||||
|
||||
.. queue:: module/series
|
||||
|
||||
Building a Web module
|
||||
=====================
|
||||
|
||||
There is no significant distinction between a Web module and
|
||||
a regular module, the web part is mostly additional data and code
|
||||
inside a regular module. This allows providing more seamless
|
||||
features by integrating your module deeper into the web client.
|
||||
|
||||
A Basic Module
|
||||
--------------
|
||||
|
||||
A very basic OpenERP module structure will be our starting point:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
web_example
|
||||
├── __init__.py
|
||||
└── __manifest__.py
|
||||
|
||||
.. patch::
|
||||
|
||||
This is a sufficient minimal declaration of a valid module.
|
||||
|
||||
Web Declaration
|
||||
---------------
|
||||
|
||||
There is no such thing as a "web module" declaration. An OpenERP
|
||||
module is automatically recognized as "web-enabled" if it contains a
|
||||
``static`` directory at its root, so:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
web_example
|
||||
├── __init__.py
|
||||
├── __manifest__.py
|
||||
└── static
|
||||
|
||||
is the extent of it. You should also change the dependency to list
|
||||
``web``:
|
||||
|
||||
.. patch::
|
||||
|
||||
.. note::
|
||||
|
||||
This does not matter in normal operation so you may not realize
|
||||
it's wrong (the web module does the loading of everything else, so
|
||||
it can only be loaded), but when e.g. testing the loading process
|
||||
is slightly different than normal, and incorrect dependency may
|
||||
lead to broken code.
|
||||
|
||||
This makes the "web" discovery system consider the module as having a
|
||||
"web part", and check if it has web controllers to mount or javascript
|
||||
files to load. The content of the ``static/`` folder is also
|
||||
automatically made available to web browser at the URL
|
||||
``$module-name/static/$file-path``. This is sufficient to provide
|
||||
pictures (of cats, usually) through your module. However there are
|
||||
still a few more steps to running javascript code.
|
||||
|
||||
Getting Things Done
|
||||
-------------------
|
||||
|
||||
The first one is to add javascript code. It's customary to put it in
|
||||
``static/src/js``, to have room for e.g. other file types, or
|
||||
third-party libraries.
|
||||
|
||||
.. patch::
|
||||
|
||||
The client won't load any file unless specified, thus the new file
|
||||
should be listed in the module's manifest file, under a new key ``js``
|
||||
(a list of file names, or glob patterns):
|
||||
|
||||
.. patch::
|
||||
|
||||
At this point, if the module is installed and the client reloaded the
|
||||
message should appear in your browser's development console.
|
||||
|
||||
.. note::
|
||||
|
||||
Because the manifest file has been edited, you will have to
|
||||
restart the OpenERP server itself for it to be taken in account.
|
||||
|
||||
You may also want to open your browser's console *before*
|
||||
reloading, depending on the browser messages printed while the
|
||||
console is closed may not work or may not appear after opening it.
|
||||
|
||||
.. note::
|
||||
|
||||
If the message does not appear, try cleaning your browser's caches
|
||||
and ensure the file is correctly loaded from the server logs or
|
||||
the "resources" tab of your browser's developers tools.
|
||||
|
||||
At this point the code runs, but it runs only once when the module is
|
||||
initialized, and it can't get access to the various APIs of the web
|
||||
client (such as making RPC requests to the server). This is done by
|
||||
providing a `javascript module`_:
|
||||
|
||||
.. patch::
|
||||
|
||||
If you reload the client, you'll see a message in the console exactly
|
||||
as previously. The differences, though invisible at this point, are:
|
||||
|
||||
* All javascript files specified in the manifest (only this one so
|
||||
far) have been fully loaded
|
||||
* An instance of the web client and a namespace inside that instance
|
||||
(with the same name as the module) have been created and are
|
||||
available for use
|
||||
|
||||
The latter point is what the ``instance`` parameter to the function
|
||||
provides: an instance of the OpenERP Web client, with the contents of
|
||||
all the new module's dependencies loaded in and initialized. These are
|
||||
the entry points to the web client's APIs.
|
||||
|
||||
To demonstrate, let's build a simple :doc:`client action
|
||||
<client_action>`: a stopwatch
|
||||
|
||||
First, the action declaration:
|
||||
|
||||
.. patch::
|
||||
|
||||
then set up the :doc:`client action hook <client_action>` to register
|
||||
a function (for now):
|
||||
|
||||
.. patch::
|
||||
|
||||
Updating the module (in order to load the XML description) and
|
||||
re-starting the server should display a new menu *Example Client
|
||||
Action* at the top-level. Opening said menu will make the message
|
||||
appear, as usual, in the browser's console.
|
||||
|
||||
Paint it black
|
||||
--------------
|
||||
|
||||
The next step is to take control of the page itself, rather than just
|
||||
print little messages in the console. This we can do by replacing our
|
||||
client action function by a :doc:`widget`. Our widget will simply use
|
||||
its :js:func:`~openerp.web.Widget.start` to add some content to its
|
||||
DOM:
|
||||
|
||||
.. patch::
|
||||
|
||||
after reloading the client (to update the javascript file), instead of
|
||||
printing to the console the menu item clears the whole screen and
|
||||
displays the specified message in the page.
|
||||
|
||||
Since we've added a class on the widget's :ref:`DOM root
|
||||
<widget-dom_root>` we can now see how to add a stylesheet to a module:
|
||||
first create the stylesheet file:
|
||||
|
||||
.. patch::
|
||||
|
||||
then add a reference to the stylesheet in the module's manifest (which
|
||||
will require restarting the OpenERP Server to see the changes, as
|
||||
usual):
|
||||
|
||||
.. patch::
|
||||
|
||||
the text displayed by the menu item should now be huge, and
|
||||
white-on-black (instead of small and black-on-white). From there on,
|
||||
the world's your canvas.
|
||||
|
||||
.. note::
|
||||
|
||||
Prefixing CSS rules with both ``.openerp`` (to ensure the rule
|
||||
will apply only within the confines of the OpenERP Web client) and
|
||||
a class at the root of your own hierarchy of widgets is strongly
|
||||
recommended to avoid "leaking" styles in case the code is running
|
||||
embedded in an other web page, and does not have the whole screen
|
||||
to itself.
|
||||
|
||||
So far we haven't built much (any, really) DOM content. It could all
|
||||
be done in :js:func:`~openerp.web.Widget.start` but that gets unwieldy
|
||||
and hard to maintain fast. It is also very difficult to extend by
|
||||
third parties (trying to add or change things in your widgets) unless
|
||||
broken up into multiple methods which each perform a little bit of the
|
||||
rendering.
|
||||
|
||||
The first way to handle this method is to delegate the content to
|
||||
plenty of sub-widgets, which can be individually overridden. An other
|
||||
method [#DOM-building]_ is to use `a template
|
||||
<http://en.wikipedia.org/wiki/Web_template>`_ to render a widget's
|
||||
DOM.
|
||||
|
||||
OpenERP Web's template language is :doc:`qweb`. Although any
|
||||
templating engine can be used (e.g. `mustache
|
||||
<http://mustache.github.com/>`_ or `_.template
|
||||
<http://underscorejs.org/#template>`_) QWeb has important features
|
||||
which other template engines may not provide, and has special
|
||||
integration to OpenERP Web widgets.
|
||||
|
||||
Adding a template file is similar to adding a style sheet:
|
||||
|
||||
.. patch::
|
||||
|
||||
The template can then easily be hooked in the widget:
|
||||
|
||||
.. patch::
|
||||
|
||||
And finally the CSS can be altered to style the new (and more complex)
|
||||
template-generated DOM, rather than the code-generated one:
|
||||
|
||||
.. patch::
|
||||
|
||||
.. note::
|
||||
|
||||
The last section of the CSS change is an example of "state
|
||||
classes": a CSS class (or set of classes) on the root of the
|
||||
widget, which is toggled when the state of the widget changes and
|
||||
can perform drastic alterations in rendering (usually
|
||||
showing/hiding various elements).
|
||||
|
||||
This pattern is both fairly simple (to read and understand) and
|
||||
efficient (because most of the hard work is pushed to the
|
||||
browser's CSS engine, which is usually highly optimized, and done
|
||||
in a single repaint after toggling the class).
|
||||
|
||||
The last step (until the next one) is to add some behavior and make
|
||||
our stopwatch watch. First hook some events on the buttons to toggle
|
||||
the widget's state:
|
||||
|
||||
.. patch::
|
||||
|
||||
This demonstrates the use of the "events hash" and event delegation to
|
||||
declaratively handle events on the widget's DOM. And already changes
|
||||
the button displayed in the UI. Then comes some actual logic:
|
||||
|
||||
.. patch::
|
||||
|
||||
* An initializer (the ``init`` method) is introduced to set-up a few
|
||||
internal variables: ``_start`` will hold the start of the timer (as
|
||||
a javascript Date object), and ``_watch`` will hold a ticker to
|
||||
update the interface regularly and display the "current time".
|
||||
|
||||
* ``update_counter`` is in charge of taking the time difference
|
||||
between "now" and ``_start``, formatting as ``HH:MM:SS`` and
|
||||
displaying the result on screen.
|
||||
|
||||
* ``watch_start`` is augmented to initialize ``_start`` with its value
|
||||
and set-up the update of the counter display every 33ms.
|
||||
|
||||
* ``watch_stop`` disables the updater, does a final update of the
|
||||
counter display and resets everything.
|
||||
|
||||
* Finally, because javascript Interval and Timeout objects execute
|
||||
"outside" the widget, they will keep going even after the widget has
|
||||
been destroyed (especially an issue with intervals as they repeat
|
||||
indefinitely). So ``_watch`` *must* be cleared when the widget is
|
||||
destroyed (then the ``_super`` must be called as well in order to
|
||||
perform the "normal" widget cleanup).
|
||||
|
||||
Starting and stopping the watch now works, and correctly tracks time
|
||||
since having started the watch, neatly formatted.
|
||||
|
||||
Burning through the skies
|
||||
-------------------------
|
||||
|
||||
All work so far has been "local" outside of the original impetus
|
||||
provided by the client action: the widget is self-contained and, once
|
||||
started, does not communicate with anything outside itself. Not only
|
||||
that, but it has no persistence: if the user leaves the stopwatch
|
||||
screen (to go and see his inbox, or do some well-deserved accounting,
|
||||
for instance) whatever was being timed will be lost.
|
||||
|
||||
To prevent this irremediable loss, we can use OpenERP's support for
|
||||
storing data as a model, allowing so that we don't lose our data and
|
||||
can later retrieve, query and manipulate it. First let's create a
|
||||
basic OpenERP model in which our data will be stored:
|
||||
|
||||
.. patch::
|
||||
|
||||
then let's add saving times to the database every time the stopwatch
|
||||
is stopped, using :js:class:`the "high-level" Model API
|
||||
<openerp.web.Model.call>`:
|
||||
|
||||
.. patch::
|
||||
|
||||
A look at the "Network" tab of your preferred browser's developer
|
||||
tools while playing with the stopwatch will show that the save
|
||||
(creation) request is indeed sent (and replied to, even though we're
|
||||
ignoring the response at this point).
|
||||
|
||||
These saved data should now be loaded and displayed when first opening
|
||||
the action, so the user can see his previously recorded times. This is
|
||||
done by overloading the model's ``start`` method: the purpose of
|
||||
:js:func:`~openerp.base.Widget.start()` is to perform *asynchronous*
|
||||
initialization steps, so the rest of the web client knows to "wait"
|
||||
and gets a readiness signal. In this case, it will fetch the data
|
||||
recorded previously using the :js:class:`~openerp.web.Query` interface
|
||||
and add this data to an ordered list added to the widget's template:
|
||||
|
||||
.. patch::
|
||||
|
||||
And for consistency's sake (so that the display a user leaves is
|
||||
pretty much the same as the one he comes back to), newly created
|
||||
records should also automatically be added to the list:
|
||||
|
||||
.. patch::
|
||||
|
||||
Note that we're only displaying the record once we know it's been
|
||||
saved from the database (the ``create`` call has returned without
|
||||
error).
|
||||
|
||||
Mic check, is this working?
|
||||
---------------------------
|
||||
|
||||
So far, features have been implemented, code has been worked and
|
||||
tentatively tried. However, there is no guarantee they will *keep
|
||||
working* as new changes are performed, new features added, …
|
||||
|
||||
The original author (you, dear reader) could keep a notebook with a
|
||||
list of workflows to check, to ensure everything keeps working. And
|
||||
follow the notebook day after day, every time something is changed in
|
||||
the module.
|
||||
|
||||
That gets repetitive after a while. And computers are good at doing
|
||||
repetitive stuff, as long as you tell them how to do it.
|
||||
|
||||
So let's add test to the module, so that in the future the computer
|
||||
can take care of ensuring what works today keeps working tomorrow.
|
||||
|
||||
.. note::
|
||||
|
||||
Here we're writing tests after having implemented the widget. This
|
||||
may or may not work, we may need to alter bits and pieces of code
|
||||
to get them in a testable state. An other testing methodology is
|
||||
:abbr:`TDD (Test-Driven Development)` where the tests are written
|
||||
first, and the code necessary to make these tests pass is written
|
||||
afterwards.
|
||||
|
||||
Both methods have their opponents and detractors, advantages and
|
||||
inconvenients. Pick the one you prefer.
|
||||
|
||||
The first step of :doc:`testing` is to set up the basic testing
|
||||
structure:
|
||||
|
||||
1. Creating a javascript file
|
||||
|
||||
.. patch::
|
||||
|
||||
2. Containing a test section (and a few tests to make sure the tests
|
||||
are correctly run)
|
||||
|
||||
.. patch::
|
||||
|
||||
3. Then declaring the test file in the module's manifest
|
||||
|
||||
.. patch::
|
||||
|
||||
4. And finally — after restarting OpenERP — navigating to the test
|
||||
runner at ``/web/tests`` and selecting your soon-to-be-tested
|
||||
module:
|
||||
|
||||
.. image:: module/testing_0.png
|
||||
:align: center
|
||||
|
||||
the testing result do indeed match the test.
|
||||
|
||||
The simplest tests to write are for synchronous pure
|
||||
functions. Synchronous means no RPC call or any other such thing
|
||||
(e.g. ``setTimeout``), only direct data processing, and pure means no
|
||||
side-effect: the function takes some input, manipulates it and yields
|
||||
an output.
|
||||
|
||||
In our widget, only ``format_time`` fits the bill: it takes a duration
|
||||
(in milliseconds) and returns an ``hours:minutes:second`` formatting
|
||||
of it. Let's test it:
|
||||
|
||||
.. patch::
|
||||
|
||||
This series of simple tests passes with no issue. The next easy-ish
|
||||
test type is to test basic DOM alterations from provided input, such
|
||||
as (for our widget) updating the counter or displaying a record to the
|
||||
records list: while it's not pure (it alters the DOM "in-place") it
|
||||
has well-delimited side-effects and these side-effects come solely
|
||||
from the provided input.
|
||||
|
||||
Because these methods alter the widget's DOM, the widget needs a
|
||||
DOM. Looking up :doc:`a widget's lifecycle <widget>`, the widget
|
||||
really only gets its DOM when adding it to the document. However a
|
||||
side-effect of this is to :js:func:`~openerp.web.Widget.start` it,
|
||||
which for us means going to query the user's times.
|
||||
|
||||
We don't have any records to get in our test, and we don't want to
|
||||
test the initialization yet! So let's cheat a bit: we can manually
|
||||
:js:func:`set a widget's DOM <openerp.web.Widget.setElement>`, let's
|
||||
create a basic DOM matching what each method expects then call the
|
||||
method:
|
||||
|
||||
.. patch::
|
||||
|
||||
The next group of patches (in terms of setup/complexity) is RPC tests:
|
||||
testing components/methods which perform network calls (RPC
|
||||
requests). In our module, ``start`` and ``watch_stop`` are in that
|
||||
case: ``start`` fetches the user's recorded times and ``watch_stop``
|
||||
creates a new record with the current watch.
|
||||
|
||||
By default, tests don't allow RPC requests and will generate an error
|
||||
when trying to perform one:
|
||||
|
||||
.. image:: module/testing_1.png
|
||||
:align: center
|
||||
|
||||
To allow them, the test case (or the test suite) has to explicitly opt
|
||||
into :js:attr:`rpc support <TestOptions.rpc>` by adding the ``rpc:
|
||||
'mock'`` option to the test case, and providing its own "rpc
|
||||
responses":
|
||||
|
||||
.. patch::
|
||||
|
||||
.. note::
|
||||
|
||||
By default, tests cases don't load templates either. We had not
|
||||
needed to perform any template rendering before here, so we must
|
||||
now enable templates loading via :js:attr:`the corresponding
|
||||
option <TestOptions.templates>`.
|
||||
|
||||
Our final test requires altering the module's code: asynchronous tests
|
||||
use :doc:`deferred </async>` to know when a test ends and the other
|
||||
one can start (otherwise test content will execute non-linearly and
|
||||
the assertions of a test will be executed during the next test or
|
||||
worse), but although ``watch_stop`` performs an asynchronous
|
||||
``create`` operation it doesn't return a deferred we can synchronize
|
||||
on. We simply need to return its result:
|
||||
|
||||
.. patch::
|
||||
|
||||
This makes no difference to the original code, but allows us to write
|
||||
our test:
|
||||
|
||||
.. patch::
|
||||
|
||||
.. [#DOM-building] they are not alternative solutions: they work very
|
||||
well together. Templates are used to build "just
|
||||
DOM", sub-widgets are used to build DOM subsections
|
||||
*and* delegate part of the behavior (e.g. events
|
||||
handling).
|
||||
|
||||
.. _javascript module:
|
||||
http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript
|
||||
@@ -1,17 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 0000000000000000000000000000000000000000
|
||||
|
||||
diff --git a/__init__.py b/__init__.py
|
||||
new file mode 100644
|
||||
diff --git a/__manifest__.py b/__manifest__.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/__manifest__.py
|
||||
@@ -0,0 +1,7 @@
|
||||
+# __manifest__.py
|
||||
+{
|
||||
+ 'name': "Web Example",
|
||||
+ 'description': "Basic example of a (future) web module",
|
||||
+ 'category': 'Hidden',
|
||||
+ 'depends': ['base'],
|
||||
+}
|
||||
@@ -1,13 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 72d9d59a93fcee06ba28cf0b98a1075331dcc8f4
|
||||
diff --git a/static/src/css/web_example.css b/static/src/css/web_example.css
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/static/src/css/web_example.css
|
||||
@@ -0,0 +1,6 @@
|
||||
+.openerp .oe_web_example {
|
||||
+ color: white;
|
||||
+ background-color: black;
|
||||
+ height: 100%;
|
||||
+ font-size: 400%;
|
||||
+}
|
||||
@@ -1,11 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 3ed382d9a8fe64fbb8e2bf4045e3fcd5c74c92bc
|
||||
diff --git a/__manifest__.py b/__manifest__.py
|
||||
--- a/__manifest__.py
|
||||
+++ b/__manifest__.py
|
||||
@@ -6,4 +6,5 @@
|
||||
'depends': ['web'],
|
||||
'data': ['web_example.xml'],
|
||||
'js': ['static/src/js/first_module.js'],
|
||||
+ 'css': ['static/src/css/web_example.css'],
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 43f21611dacb7c2b2f3810baeeef359ad7c329f0
|
||||
|
||||
diff --git a/__manifest__.py b/__manifest__.py
|
||||
--- a/__manifest__.py
|
||||
+++ b/__manifest__.py
|
||||
@@ -7,4 +7,5 @@
|
||||
'data': ['web_example.xml'],
|
||||
'js': ['static/src/js/first_module.js'],
|
||||
'css': ['static/src/css/web_example.css'],
|
||||
+ 'qweb': ['static/src/xml/web_example.xml'],
|
||||
}
|
||||
diff --git a/static/src/xml/web_example.xml b/static/src/xml/web_example.xml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/static/src/xml/web_example.xml
|
||||
@@ -0,0 +1,11 @@
|
||||
+<templates>
|
||||
+<div t-name="web_example.action" class="oe_web_example oe_web_example_stopped">
|
||||
+ <h4 class="oe_web_example_timer">00:00:00</h4>
|
||||
+ <p class="oe_web_example_start">
|
||||
+ <button type="button">Start</button>
|
||||
+ </p>
|
||||
+ <p class="oe_web_example_stop">
|
||||
+ <button type="button">Stop</button>
|
||||
+ </p>
|
||||
+</div>
|
||||
+</templates>
|
||||
@@ -1,17 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent ae3b427c96b532794a65357b3f075129cc991276
|
||||
diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
|
||||
--- a/static/src/js/first_module.js
|
||||
+++ b/static/src/js/first_module.js
|
||||
@@ -2,10 +2,6 @@
|
||||
openerp.web_example = function (instance) {
|
||||
instance.web.client_actions.add('example.action', 'instance.web_example.Action');
|
||||
instance.web_example.Action = instance.web.Widget.extend({
|
||||
- className: 'oe_web_example',
|
||||
- start: function () {
|
||||
- this.$el.text("Hello, world!");
|
||||
- return this._super();
|
||||
- }
|
||||
+ template: 'web_example.action'
|
||||
});
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent e2d2e1a4cc2d2496aebeb05d94768384427c9e8b
|
||||
diff --git a/static/src/css/web_example.css b/static/src/css/web_example.css
|
||||
--- a/static/src/css/web_example.css
|
||||
+++ b/static/src/css/web_example.css
|
||||
@@ -2,5 +2,12 @@
|
||||
color: white;
|
||||
background-color: black;
|
||||
height: 100%;
|
||||
- font-size: 400%;
|
||||
}
|
||||
+.openerp .oe_web_example h4 {
|
||||
+ margin: 0;
|
||||
+ font-size: 200%;
|
||||
+}
|
||||
+.openerp .oe_web_example.oe_web_example_started .oe_web_example_start button,
|
||||
+.openerp .oe_web_example.oe_web_example_stopped .oe_web_example_stop button {
|
||||
+ display: none
|
||||
+}
|
||||
@@ -1,25 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 2645d7a09dcba7f6d6074a33252c16c03c56fdf3
|
||||
diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
|
||||
--- a/static/src/js/first_module.js
|
||||
+++ b/static/src/js/first_module.js
|
||||
@@ -2,6 +2,18 @@
|
||||
openerp.web_example = function (instance) {
|
||||
instance.web.client_actions.add('example.action', 'instance.web_example.Action');
|
||||
instance.web_example.Action = instance.web.Widget.extend({
|
||||
- template: 'web_example.action'
|
||||
+ template: 'web_example.action',
|
||||
+ events: {
|
||||
+ 'click .oe_web_example_start button': 'watch_start',
|
||||
+ 'click .oe_web_example_stop button': 'watch_stop'
|
||||
+ },
|
||||
+ watch_start: function () {
|
||||
+ this.$el.addClass('oe_web_example_started')
|
||||
+ .removeClass('oe_web_example_stopped');
|
||||
+ },
|
||||
+ watch_stop: function () {
|
||||
+ this.$el.removeClass('oe_web_example_started')
|
||||
+ .addClass('oe_web_example_stopped');
|
||||
+ },
|
||||
});
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 2921a545adc3406d3139be7951f3225e94493466
|
||||
diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
|
||||
--- a/static/src/js/first_module.js
|
||||
+++ b/static/src/js/first_module.js
|
||||
@@ -7,13 +7,46 @@ openerp.web_example = function (instance
|
||||
'click .oe_web_example_start button': 'watch_start',
|
||||
'click .oe_web_example_stop button': 'watch_stop'
|
||||
},
|
||||
+ init: function () {
|
||||
+ this._super.apply(this, arguments);
|
||||
+ this._start = null;
|
||||
+ this._watch = null;
|
||||
+ },
|
||||
+ update_counter: function () {
|
||||
+ var h, m, s;
|
||||
+ // Subtracting javascript dates returns the difference in milliseconds
|
||||
+ var diff = new Date() - this._start;
|
||||
+ s = diff / 1000;
|
||||
+ m = Math.floor(s / 60);
|
||||
+ s -= 60*m;
|
||||
+ h = Math.floor(m / 60);
|
||||
+ m -= 60*h;
|
||||
+ this.$('.oe_web_example_timer').text(
|
||||
+ _.str.sprintf("%02d:%02d:%02d", h, m, s));
|
||||
+ },
|
||||
watch_start: function () {
|
||||
this.$el.addClass('oe_web_example_started')
|
||||
.removeClass('oe_web_example_stopped');
|
||||
+ this._start = new Date();
|
||||
+ // Update the UI to the current time
|
||||
+ this.update_counter();
|
||||
+ // Update the counter at 30 FPS (33ms/frame)
|
||||
+ this._watch = setInterval(
|
||||
+ this.proxy('update_counter'),
|
||||
+ 33);
|
||||
},
|
||||
watch_stop: function () {
|
||||
+ clearInterval(this._watch);
|
||||
+ this.update_counter();
|
||||
+ this._start = this._watch = null;
|
||||
this.$el.removeClass('oe_web_example_started')
|
||||
.addClass('oe_web_example_stopped');
|
||||
},
|
||||
+ destroy: function () {
|
||||
+ if (this._watch) {
|
||||
+ clearInterval(this._watch);
|
||||
+ }
|
||||
+ this._super();
|
||||
+ }
|
||||
});
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent e0cc13c2b2ec4d6f6bfdb033b189a32e44106f2e
|
||||
diff --git a/__init__.py b/__init__.py
|
||||
--- a/__init__.py
|
||||
+++ b/__init__.py
|
||||
@@ -0,0 +1,13 @@
|
||||
+# __init__.py
|
||||
+from openerp.osv import orm, fields
|
||||
+
|
||||
+
|
||||
+class Times(orm.Model):
|
||||
+ _name = 'web_example.stopwatch'
|
||||
+
|
||||
+ _columns = {
|
||||
+ 'time': fields.integer("Time", required=True,
|
||||
+ help="Measured time in milliseconds"),
|
||||
+ 'user_id': fields.many2one('res.users', "User", required=True,
|
||||
+ help="User who registered the measurement")
|
||||
+ }
|
||||
@@ -1,52 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 05797cc75b49634e640f44b24347f2905b464022
|
||||
diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
|
||||
--- a/static/src/js/first_module.js
|
||||
+++ b/static/src/js/first_module.js
|
||||
@@ -12,11 +12,13 @@ openerp.web_example = function (instance
|
||||
this._start = null;
|
||||
this._watch = null;
|
||||
},
|
||||
- update_counter: function () {
|
||||
+ current: function () {
|
||||
+ // Subtracting javascript dates returns the difference in milliseconds
|
||||
+ return new Date() - this._start;
|
||||
+ },
|
||||
+ update_counter: function (time) {
|
||||
var h, m, s;
|
||||
- // Subtracting javascript dates returns the difference in milliseconds
|
||||
- var diff = new Date() - this._start;
|
||||
- s = diff / 1000;
|
||||
+ s = time / 1000;
|
||||
m = Math.floor(s / 60);
|
||||
s -= 60*m;
|
||||
h = Math.floor(m / 60);
|
||||
@@ -29,18 +31,24 @@ openerp.web_example = function (instance
|
||||
.removeClass('oe_web_example_stopped');
|
||||
this._start = new Date();
|
||||
// Update the UI to the current time
|
||||
- this.update_counter();
|
||||
+ this.update_counter(this.current());
|
||||
// Update the counter at 30 FPS (33ms/frame)
|
||||
- this._watch = setInterval(
|
||||
- this.proxy('update_counter'),
|
||||
+ this._watch = setInterval(function () {
|
||||
+ this.update_counter(this.current());
|
||||
+ }.bind(this),
|
||||
33);
|
||||
},
|
||||
watch_stop: function () {
|
||||
clearInterval(this._watch);
|
||||
- this.update_counter();
|
||||
+ var time = this.current();
|
||||
+ this.update_counter(time);
|
||||
this._start = this._watch = null;
|
||||
this.$el.removeClass('oe_web_example_started')
|
||||
.addClass('oe_web_example_stopped');
|
||||
+ new instance.web.Model('web_example.stopwatch').call('create', [{
|
||||
+ user_id: instance.session.uid,
|
||||
+ time: time,
|
||||
+ }]);
|
||||
},
|
||||
destroy: function () {
|
||||
if (this._watch) {
|
||||
@@ -1,12 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 8a986919a3e22cd7cca51210820c09d4545dc60d
|
||||
diff --git a/__manifest__.py b/__manifest__.py
|
||||
--- a/__manifest__.py
|
||||
+++ b/__manifest__.py
|
||||
@@ -3,5 +3,5 @@
|
||||
'name': "Web Example",
|
||||
'description': "Basic example of a (future) web module",
|
||||
'category': 'Hidden',
|
||||
- 'depends': ['base'],
|
||||
+ 'depends': ['web'],
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
Index: web_example/static/src/js/first_module.js
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/js/first_module.js
|
||||
+++ web_example/static/src/js/first_module.js
|
||||
@@ -11,20 +11,36 @@ openerp.web_example = function (instance
|
||||
this._super.apply(this, arguments);
|
||||
this._start = null;
|
||||
this._watch = null;
|
||||
+ this.model = new instance.web.Model('web_example.stopwatch');
|
||||
+ },
|
||||
+ start: function () {
|
||||
+ var display = this.display_record.bind(this);
|
||||
+ return this.model.query()
|
||||
+ .filter([['user_id', '=', instance.session.uid]])
|
||||
+ .all().done(function (records) {
|
||||
+ _(records).each(display);
|
||||
+ });
|
||||
},
|
||||
current: function () {
|
||||
// Subtracting javascript dates returns the difference in milliseconds
|
||||
return new Date() - this._start;
|
||||
},
|
||||
- update_counter: function (time) {
|
||||
+ display_record: function (record) {
|
||||
+ $('<li>')
|
||||
+ .text(this.format_time(record.time))
|
||||
+ .appendTo(this.$('.oe_web_example_saved'));
|
||||
+ },
|
||||
+ format_time: function (time) {
|
||||
var h, m, s;
|
||||
s = time / 1000;
|
||||
m = Math.floor(s / 60);
|
||||
s -= 60*m;
|
||||
h = Math.floor(m / 60);
|
||||
m -= 60*h;
|
||||
- this.$('.oe_web_example_timer').text(
|
||||
- _.str.sprintf("%02d:%02d:%02d", h, m, s));
|
||||
+ return _.str.sprintf("%02d:%02d:%02d", h, m, s);
|
||||
+ },
|
||||
+ update_counter: function (time) {
|
||||
+ this.$('.oe_web_example_timer').text(this.format_time(time));
|
||||
},
|
||||
watch_start: function () {
|
||||
this.$el.addClass('oe_web_example_started')
|
||||
@@ -45,7 +61,7 @@ openerp.web_example = function (instance
|
||||
this._start = this._watch = null;
|
||||
this.$el.removeClass('oe_web_example_started')
|
||||
.addClass('oe_web_example_stopped');
|
||||
- new instance.web.Model('web_example.stopwatch').call('create', [{
|
||||
+ this.model.call('create', [{
|
||||
user_id: instance.session.uid,
|
||||
time: time,
|
||||
}]);
|
||||
Index: web_example/static/src/xml/web_example.xml
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/xml/web_example.xml
|
||||
+++ web_example/static/src/xml/web_example.xml
|
||||
@@ -7,5 +7,6 @@
|
||||
<p class="oe_web_example_stop">
|
||||
<button type="button">Stop</button>
|
||||
</p>
|
||||
+ <ol class="oe_web_example_saved"></ol>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -1,27 +0,0 @@
|
||||
Index: web_example/static/src/js/first_module.js
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/js/first_module.js
|
||||
+++ web_example/static/src/js/first_module.js
|
||||
@@ -55,16 +55,20 @@ openerp.web_example = function (instance
|
||||
33);
|
||||
},
|
||||
watch_stop: function () {
|
||||
+ var self = this;
|
||||
clearInterval(this._watch);
|
||||
var time = this.current();
|
||||
this.update_counter(time);
|
||||
this._start = this._watch = null;
|
||||
this.$el.removeClass('oe_web_example_started')
|
||||
.addClass('oe_web_example_stopped');
|
||||
- this.model.call('create', [{
|
||||
+ var record = {
|
||||
user_id: instance.session.uid,
|
||||
time: time,
|
||||
- }]);
|
||||
+ };
|
||||
+ this.model.call('create', [record]).done(function () {
|
||||
+ self.display_record(record);
|
||||
+ });
|
||||
},
|
||||
destroy: function () {
|
||||
if (this._watch) {
|
||||
@@ -1,6 +0,0 @@
|
||||
Index: web_example/static/src/tests/timer.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ web_example/static/src/tests/timer.js
|
||||
@@ -0,0 +1 @@
|
||||
+
|
||||
@@ -1,14 +0,0 @@
|
||||
Index: web_example/static/src/tests/timer.js
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/tests/timer.js
|
||||
+++ web_example/static/src/tests/timer.js
|
||||
@@ -1 +1,8 @@
|
||||
-
|
||||
+openerp.testing.section('timer', function (test) {
|
||||
+ test('successful test', function () {
|
||||
+ ok(true, "should work");
|
||||
+ });
|
||||
+ test('unsuccessful test', function () {
|
||||
+ ok(false, "shoud fail");
|
||||
+ });
|
||||
+});
|
||||
@@ -1,10 +0,0 @@
|
||||
Index: web_example/__manifest__.py
|
||||
===================================================================
|
||||
--- web_example.orig/__manifest__.py
|
||||
+++ web_example/__manifest__.py
|
||||
@@ -8,4 +8,5 @@
|
||||
'js': ['static/src/js/first_module.js'],
|
||||
'css': ['static/src/css/web_example.css'],
|
||||
'qweb': ['static/src/xml/web_example.xml'],
|
||||
+ 'test': ['static/src/tests/timer.js'],
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
Index: web_example/static/src/tests/timer.js
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/tests/timer.js
|
||||
+++ web_example/static/src/tests/timer.js
|
||||
@@ -1,8 +1,45 @@
|
||||
openerp.testing.section('timer', function (test) {
|
||||
- test('successful test', function () {
|
||||
- ok(true, "should work");
|
||||
- });
|
||||
- test('unsuccessful test', function () {
|
||||
- ok(false, "shoud fail");
|
||||
+ test('format_time', function (instance) {
|
||||
+ var w = new instance.web_example.Action();
|
||||
+
|
||||
+ strictEqual(
|
||||
+ w.format_time(0),
|
||||
+ '00:00:00');
|
||||
+ strictEqual(
|
||||
+ w.format_time(543),
|
||||
+ '00:00:00',
|
||||
+ "should round sub-second times down to zero");
|
||||
+ strictEqual(
|
||||
+ w.format_time(5340),
|
||||
+ '00:00:05',
|
||||
+ "should floor sub-second extents to the previous second");
|
||||
+ strictEqual(
|
||||
+ w.format_time(60000),
|
||||
+ '00:01:00');
|
||||
+ strictEqual(
|
||||
+ w.format_time(3600000),
|
||||
+ '01:00:00');
|
||||
+ strictEqual(
|
||||
+ w.format_time(86400000),
|
||||
+ '24:00:00');
|
||||
+ strictEqual(
|
||||
+ w.format_time(604800000),
|
||||
+ '168:00:00');
|
||||
+
|
||||
+ strictEqual(
|
||||
+ w.format_time(22733958),
|
||||
+ '06:18:53');
|
||||
+ strictEqual(
|
||||
+ w.format_time(41676639),
|
||||
+ '11:34:36');
|
||||
+ strictEqual(
|
||||
+ w.format_time(57802094),
|
||||
+ '16:03:22');
|
||||
+ strictEqual(
|
||||
+ w.format_time(73451828),
|
||||
+ '20:24:11');
|
||||
+ strictEqual(
|
||||
+ w.format_time(84092336),
|
||||
+ '23:21:32');
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
Index: web_example/static/src/tests/timer.js
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/tests/timer.js
|
||||
+++ web_example/static/src/tests/timer.js
|
||||
@@ -42,4 +42,33 @@ openerp.testing.section('timer', functio
|
||||
w.format_time(84092336),
|
||||
'23:21:32');
|
||||
});
|
||||
+ test('update_counter', function (instance, $fixture) {
|
||||
+ var w = new instance.web_example.Action();
|
||||
+ // $fixture is a DOM tree whose content gets cleaned up before
|
||||
+ // each test, so we can add whatever we need to it
|
||||
+ $fixture.append('<div class="oe_web_example_timer">');
|
||||
+ // Then set it on the widget
|
||||
+ w.setElement($fixture);
|
||||
+
|
||||
+ // Update the counter with a known value
|
||||
+ w.update_counter(22733958);
|
||||
+ // And check the DOM matches
|
||||
+ strictEqual($fixture.text(), '06:18:53');
|
||||
+
|
||||
+ w.update_counter(73451828)
|
||||
+ strictEqual($fixture.text(), '20:24:11');
|
||||
+ });
|
||||
+ test('display_record', function (instance, $fixture) {
|
||||
+ var w = new instance.web_example.Action();
|
||||
+ $fixture.append('<ol class="oe_web_example_saved">')
|
||||
+ w.setElement($fixture);
|
||||
+
|
||||
+ w.display_record({time: 41676639});
|
||||
+ w.display_record({time: 84092336});
|
||||
+
|
||||
+ var $lis = $fixture.find('li');
|
||||
+ strictEqual($lis.length, 2, "should have printed 2 records");
|
||||
+ strictEqual($lis[0].textContent, '11:34:36');
|
||||
+ strictEqual($lis[1].textContent, '23:21:32');
|
||||
+ });
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
Index: web_example/static/src/tests/timer.js
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/tests/timer.js
|
||||
+++ web_example/static/src/tests/timer.js
|
||||
@@ -71,4 +71,23 @@ openerp.testing.section('timer', functio
|
||||
strictEqual($lis[0].textContent, '11:34:36');
|
||||
strictEqual($lis[1].textContent, '23:21:32');
|
||||
});
|
||||
+ test('start', {templates: true, rpc: 'mock', asserts: 3}, function (instance, $fixture, mock) {
|
||||
+ // Rather odd-looking shortcut for search+read in a single RPC call
|
||||
+ mock('/web/dataset/search_read', function () {
|
||||
+ // ignore parameters, just return a pair of records.
|
||||
+ return {records: [
|
||||
+ {time: 22733958},
|
||||
+ {time: 84092336}
|
||||
+ ]};
|
||||
+ });
|
||||
+
|
||||
+ var w = new instance.web_example.Action();
|
||||
+ return w.appendTo($fixture)
|
||||
+ .then(function () {
|
||||
+ var $lis = $fixture.find('li');
|
||||
+ strictEqual($lis.length, 2);
|
||||
+ strictEqual($lis[0].textContent, '06:18:53');
|
||||
+ strictEqual($lis[1].textContent, '23:21:32');
|
||||
+ });
|
||||
+ });
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
Index: web_example/static/src/js/first_module.js
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/js/first_module.js
|
||||
+++ web_example/static/src/js/first_module.js
|
||||
@@ -66,7 +66,7 @@ openerp.web_example = function (instance
|
||||
user_id: instance.session.uid,
|
||||
time: time,
|
||||
};
|
||||
- this.model.call('create', [record]).done(function () {
|
||||
+ return this.model.call('create', [record]).done(function () {
|
||||
self.display_record(record);
|
||||
});
|
||||
},
|
||||
@@ -1,37 +0,0 @@
|
||||
Index: web_example/static/src/tests/timer.js
|
||||
===================================================================
|
||||
--- web_example.orig/static/src/tests/timer.js
|
||||
+++ web_example/static/src/tests/timer.js
|
||||
@@ -90,4 +90,32 @@ openerp.testing.section('timer', functio
|
||||
strictEqual($lis[1].textContent, '23:21:32');
|
||||
});
|
||||
});
|
||||
+ test('watch_stop', {templates: true, rpc: 'mock', asserts: 3}, function (instance, $fix, mock) {
|
||||
+ var created = false;
|
||||
+ mock('web_example.stopwatch:create', function (args, kwargs) {
|
||||
+ created = true;
|
||||
+ // return a fake id (unused)
|
||||
+ return 42;
|
||||
+ });
|
||||
+ mock('/web/dataset/search_read', function () {
|
||||
+ return {records: []};
|
||||
+ });
|
||||
+
|
||||
+ var w = new instance.web_example.Action();
|
||||
+ return w.appendTo($fix)
|
||||
+ .then(function () {
|
||||
+ // Virtual start point 5s before 'now'
|
||||
+ w._start = new Date() - 5000;
|
||||
+ return w.watch_stop();
|
||||
+ })
|
||||
+ .done(function () {
|
||||
+ ok(created, "should have called create()");
|
||||
+ strictEqual($fix.find('.oe_web_example_timer').text(),
|
||||
+ '00:00:05',
|
||||
+ "should have updated the timer");
|
||||
+ strictEqual($fix.find('li')[0].textContent,
|
||||
+ '00:00:05',
|
||||
+ "should have added the new time to the list");
|
||||
+ });
|
||||
+ });
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent dcf661a5eef8f82503831bdb8e6c9d2f9beb285e
|
||||
diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/static/src/js/first_module.js
|
||||
@@ -0,0 +1,2 @@
|
||||
+// static/src/js/first_module.js
|
||||
+console.log("Debug statement: file loaded");
|
||||
@@ -1,11 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 139dae60de67efa0017f5032f71ab774685c5507
|
||||
diff --git a/__manifest__.py b/__manifest__.py
|
||||
--- a/__manifest__.py
|
||||
+++ b/__manifest__.py
|
||||
@@ -4,4 +4,5 @@
|
||||
'description': "Basic example of a (future) web module",
|
||||
'category': 'Hidden',
|
||||
'depends': ['web'],
|
||||
+ 'js': ['static/src/js/first_module.js'],
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent c8ae7646cce3f271698c844eb2d67f9a8719650d
|
||||
diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
|
||||
--- a/static/src/js/first_module.js
|
||||
+++ b/static/src/js/first_module.js
|
||||
@@ -1,2 +1,4 @@
|
||||
// static/src/js/first_module.js
|
||||
-console.log("Debug statement: file loaded");
|
||||
+openerp.web_example = function (instance) {
|
||||
+ console.log("Module loaded");
|
||||
+};
|
||||
@@ -1,29 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 0026cb80097a724db8d36371bc00da993a51a06f
|
||||
|
||||
diff --git a/__manifest__.py b/__manifest__.py
|
||||
--- a/__manifest__.py
|
||||
+++ b/__manifest__.py
|
||||
@@ -4,5 +4,6 @@
|
||||
'description': "Basic example of a (future) web module",
|
||||
'category': 'Hidden',
|
||||
'depends': ['web'],
|
||||
+ 'data': ['web_example.xml'],
|
||||
'js': ['static/src/js/first_module.js'],
|
||||
}
|
||||
diff --git a/web_example.xml b/web_example.xml
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/web_example.xml
|
||||
@@ -0,0 +1,11 @@
|
||||
+<!-- web_example/web_example.xml -->
|
||||
+<openerp>
|
||||
+ <data>
|
||||
+ <record model="ir.actions.client" id="action_client_example">
|
||||
+ <field name="name">Example Client Action</field>
|
||||
+ <field name="tag">example.action</field>
|
||||
+ </record>
|
||||
+ <menuitem action="action_client_example"
|
||||
+ id="menu_client_example"/>
|
||||
+ </data>
|
||||
+</openerp>
|
||||
@@ -1,14 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent d987c9edd884de1de30f2ceb70d2e554474b8dd1
|
||||
diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
|
||||
--- a/static/src/js/first_module.js
|
||||
+++ b/static/src/js/first_module.js
|
||||
@@ -1,4 +1,7 @@
|
||||
// static/src/js/first_module.js
|
||||
openerp.web_example = function (instance) {
|
||||
- console.log("Module loaded");
|
||||
+ instance.web.client_actions.add('example.action', 'instance.web_example.action');
|
||||
+ instance.web_example.action = function (parent, action) {
|
||||
+ console.log("Executed the action", action);
|
||||
+ };
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
# HG changeset patch
|
||||
# Parent 6a1a7240ea0e63182f60abb1eb5c631089d56dbe
|
||||
diff --git a/static/src/js/first_module.js b/static/src/js/first_module.js
|
||||
--- a/static/src/js/first_module.js
|
||||
+++ b/static/src/js/first_module.js
|
||||
@@ -1,7 +1,11 @@
|
||||
// static/src/js/first_module.js
|
||||
openerp.web_example = function (instance) {
|
||||
- instance.web.client_actions.add('example.action', 'instance.web_example.action');
|
||||
- instance.web_example.action = function (parent, action) {
|
||||
- console.log("Executed the action", action);
|
||||
- };
|
||||
+ instance.web.client_actions.add('example.action', 'instance.web_example.Action');
|
||||
+ instance.web_example.Action = instance.web.Widget.extend({
|
||||
+ className: 'oe_web_example',
|
||||
+ start: function () {
|
||||
+ this.$el.text("Hello, world!");
|
||||
+ return this._super();
|
||||
+ }
|
||||
+ });
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
0
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
27
|
||||
28
|
||||
29
|
||||
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,560 +0,0 @@
|
||||
Search View
|
||||
===========
|
||||
|
||||
OpenERP Web 7.0 implements a unified facets-based search view instead
|
||||
of the previous form-like search view (composed of buttons and
|
||||
multiple fields). The goal for this change is twofold:
|
||||
|
||||
* Avoid the common issue of users confusing the search view with a
|
||||
form view and trying to create their records through it (or entering
|
||||
all their data, hitting the ``Create`` button expecting their record
|
||||
to be created and losing everything).
|
||||
|
||||
* Improve the looks and behaviors of the view, and the fit within
|
||||
OpenERP Web's new design.
|
||||
|
||||
The internal structure of the faceted search is inspired by
|
||||
`VisualSearch <http://documentcloud.github.com/visualsearch/>`_
|
||||
[#previous]_.
|
||||
|
||||
As does VisualSearch, the new search view is based on `Backbone`_ and
|
||||
makes significant use of Backbone's models and collections (OpenERP
|
||||
Web's widgets make a good replacement for Backbone's own views). As a
|
||||
result, understanding the implementation details of the OpenERP Web 7
|
||||
search view also requires a basic understanding of Backbone's models,
|
||||
collections and events.
|
||||
|
||||
.. note::
|
||||
|
||||
This document may mention *fetching* data. This is a shortcut for
|
||||
"returning a :js:class:`Deferred` to [whatever is being
|
||||
fetched]". Unless further noted, the function or method may opt to
|
||||
return nothing by fetching ``null`` (which can easily be done by
|
||||
returning ``$.when(null)``, which simply wraps the ``null`` in a
|
||||
Deferred).
|
||||
|
||||
Working with the search view: creating new inputs
|
||||
-------------------------------------------------
|
||||
|
||||
The primary component of search views, as with all other OpenERP
|
||||
views, are inputs. The search view has two types of inputs — filters
|
||||
and fields — but only one is easly customizable: fields.
|
||||
|
||||
The mapping from OpenERP field types (and widgets) to search view
|
||||
objects is stored in the ``openerp.web.search.fields``
|
||||
:js:class:`~openerp.web.Registry` where new field types and widgets
|
||||
can be added.
|
||||
|
||||
Search view inputs have four main roles:
|
||||
|
||||
Loading defaults
|
||||
++++++++++++++++
|
||||
|
||||
Once the search view has initialized all its inputs, it will call
|
||||
:js:func:`~openerp.web.search.Input.facet_for_defaults` on each input,
|
||||
passing it a mapping (a javascript object) of ``name:value`` extracted
|
||||
from the action's context.
|
||||
|
||||
This method should fetch a :js:class:`~openerp.web.search.Facet` (or
|
||||
an equivalent object) for the field's default value if applicable (if
|
||||
a default value for the field is found in the ``defaults`` mapping).
|
||||
|
||||
A default implementation is provided which checks if ``defaults``
|
||||
contains a non-falsy value for the field's ``@name`` and calls
|
||||
:js:func:`openerp.web.search.Input.facet_for` with that value.
|
||||
|
||||
There is no default implementation of
|
||||
:js:func:`openerp.web.search.Input.facet_for` [#no_impl]_, but
|
||||
:js:class:`openerp.web.search.Field` provides one, which uses the
|
||||
value as-is to fetch a :js:class:`~openerp.web.search.Facet`.
|
||||
|
||||
Providing completions
|
||||
+++++++++++++++++++++
|
||||
|
||||
An important component of the new search view is the auto-completion
|
||||
pane, and the task of providing completion items is delegated to
|
||||
inputs through the :js:func:`~openerp.web.search.Input.complete`
|
||||
method.
|
||||
|
||||
This method should take a single argument (the string being typed by
|
||||
the user) and should fetch an ``Array`` of possible completions
|
||||
[#completion]_.
|
||||
|
||||
A default implementation is provided which fetches nothing.
|
||||
|
||||
A completion item is a javascript object with two keys (technically it
|
||||
can have any number of keys, but only these two will be used by the
|
||||
search view):
|
||||
|
||||
``label``
|
||||
|
||||
The string which will be displayed in the completion pane. It may
|
||||
be formatted using HTML (inline only), as a result if ``value`` is
|
||||
interpolated into it it *must* be escaped. ``_.escape`` can be
|
||||
used for this.
|
||||
|
||||
``facet``
|
||||
|
||||
Either a :js:class:`~openerp.web.search.Facet` object or (more
|
||||
commonly) the corresponding attributes object. This is the facet
|
||||
which will be inserted into the search query if the completion
|
||||
item is selected by the user.
|
||||
|
||||
If the ``facet`` is not provided (not present, ``null``, ``undefined``
|
||||
or any other falsy value), the completion item will not be selectable
|
||||
and will act as a section title of sort (the ``label`` will be
|
||||
formatted differently). If an input *may* fetch multiple completion
|
||||
items, it *should* prefix those with a section title using its own
|
||||
name. This has no technical consequence but is clearer for users.
|
||||
|
||||
.. note::
|
||||
|
||||
If a field is :js:func:`invisible
|
||||
<openerp.web.search.Input.visible>`, its completion function will
|
||||
*not* be called.
|
||||
|
||||
Providing drawer/supplementary UI
|
||||
+++++++++++++++++++++++++++++++++
|
||||
|
||||
For some inputs (fields or not), interaction via autocompletion may be
|
||||
awkward or even impossible.
|
||||
|
||||
These may opt to being rendered in a "drawer" as well or instead. In
|
||||
that case, they will undergo the normal widget lifecycle and be
|
||||
rendered inside the drawer.
|
||||
|
||||
.. Found no good type-based way to handle this, since there is no MI
|
||||
(so no type-tagging) and it's possible for both Field and non-Field
|
||||
input to be put into the drawer, for whatever reason (e.g. some
|
||||
sort of auto-detector completion item for date widgets, but a
|
||||
second more usual calendar widget in the drawer for more
|
||||
obvious/precise interactions)
|
||||
|
||||
Any input can note its desire to be rendered in the drawer by
|
||||
returning a truthy value from
|
||||
:js:func:`~openerp.web.search.Input.in_drawer`.
|
||||
|
||||
By default, :js:func:`~openerp.web.search.Input.in_drawer` returns the
|
||||
value of :js:attr:`~openerp.web.search.Input._in_drawer`, which is
|
||||
``false``. The behavior can be toggled either by redefining the
|
||||
attribute to ``true`` (either on the class or on the input), or by
|
||||
overriding :js:func:`~openerp.web.search.Input.in_drawer` itself.
|
||||
|
||||
The input will be rendered in the full width of the drawer, it will be
|
||||
started only once (per view).
|
||||
|
||||
.. todo:: drawer API (if a widget wants to close the drawer in some
|
||||
way), part of the low-level SearchView API/interactions?
|
||||
|
||||
|
||||
.. todo:: handle filters and filter groups via a "driver" input which
|
||||
dynamically collects, lays out and renders filters? =>
|
||||
exercises drawer thingies
|
||||
|
||||
.. note::
|
||||
|
||||
An :js:func:`invisible <openerp.web.search.Input.visible>` input
|
||||
will not be inserted into the drawer.
|
||||
|
||||
Converting from facet objects
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
Ultimately, the point of the search view is to allow searching. In
|
||||
OpenERP this is done via :ref:`domains <openerpserver:domains>`. On
|
||||
the other hand, the OpenERP Web 7 search view's state is modelled
|
||||
after a collection of :js:class:`~openerp.web.search.Facet`, and each
|
||||
field of a search view may have special requirements when it comes to
|
||||
the domains it produces [#special]_.
|
||||
|
||||
So there needs to be some way of mapping
|
||||
:js:class:`~openerp.web.search.Facet` objects to OpenERP search data.
|
||||
|
||||
This is done via an input's
|
||||
:js:func:`~openerp.web.search.Input.get_domain` and
|
||||
:js:func:`~openerp.web.search.Input.get_context`. Each takes a
|
||||
:js:class:`~openerp.web.search.Facet` and returns whatever it's
|
||||
supposed to generate (a domain or a context, respectively). Either can
|
||||
return ``null`` if the current value does not map to a domain or
|
||||
context, and can throw an :js:class:`~openerp.web.search.Invalid`
|
||||
exception if the value is not valid at all for the field.
|
||||
|
||||
.. note::
|
||||
|
||||
The :js:class:`~openerp.web.search.Facet` object can have any
|
||||
number of values (from 1 upwards)
|
||||
|
||||
.. note::
|
||||
|
||||
There is a third conversion method,
|
||||
:js:func:`~openerp.web.search.Input.get_groupby`, which returns an
|
||||
``Array`` of groupby domains rather than a single context. At this
|
||||
point, it is only implemented on (and used by) filters.
|
||||
|
||||
Programmatic interactions: internal model
|
||||
-----------------------------------------
|
||||
|
||||
This new searchview is built around an instance of
|
||||
:js:class:`~openerp.web.search.SearchQuery` available as
|
||||
:js:attr:`openerp.web.SearchView.query`.
|
||||
|
||||
The query is a `backbone collection`_ of
|
||||
:js:class:`~openerp.web.search.Facet` objects, which can be interacted
|
||||
with directly by external objects or search view controls
|
||||
(e.g. widgets displayed in the drawer).
|
||||
|
||||
.. js:class:: openerp.web.search.SearchQuery
|
||||
|
||||
The current search query of the search view, provides convenience
|
||||
behaviors for manipulating :js:class:`~openerp.web.search.Facet`
|
||||
on top of the usual `backbone collection`_ methods.
|
||||
|
||||
The query ensures all of its facets contain at least one
|
||||
:js:class:`~openerp.web.search.FacetValue` instance. Otherwise,
|
||||
the facet is automatically removed from the query.
|
||||
|
||||
.. js:function:: openerp.web.search.SearchQuery.add(values, options)
|
||||
|
||||
Overridden from the base ``add`` method so that adding a facet
|
||||
which is *already* in the collection will merge the value of
|
||||
the new facet into the old one rather than add a second facet
|
||||
with different values.
|
||||
|
||||
:param values: facet, facet attributes or array thereof
|
||||
:returns: the collection itself
|
||||
|
||||
.. js:function:: openerp.web.search.SearchQuery.toggle(value, options)
|
||||
|
||||
Convenience method for toggling facet values in a query:
|
||||
removes the values (through the facet itself) if they are
|
||||
present, adds them if they are not. If the facet itself is not
|
||||
in the collection, adds it automatically.
|
||||
|
||||
A toggling is atomic: only one change event will be triggered
|
||||
on the facet regardless of the number of values added to or
|
||||
removed from the facet (if the facet already exists), and the
|
||||
facet is only removed from the query if it has no value *at
|
||||
the end* of the toggling.
|
||||
|
||||
:param value: facet or facet attributes
|
||||
:returns: the collection
|
||||
|
||||
.. js:class:: openerp.web.search.Facet
|
||||
|
||||
A `backbone model`_ representing a single facet of the current
|
||||
search. May map to a search field, or to a more complex or
|
||||
fuzzier input (e.g. a custom filter or an advanced search).
|
||||
|
||||
.. js:attribute:: category
|
||||
|
||||
The displayed name of the facet, as a ``String``. This is a
|
||||
backbone model attribute.
|
||||
|
||||
.. js:attribute:: field
|
||||
|
||||
The :js:class:`~openerp.web.search.Input` instance which
|
||||
originally created the facet [#facet-field]_, used to delegate
|
||||
some operations (such as serializing the facet's values to
|
||||
domains and contexts). This is a backbone model attribute.
|
||||
|
||||
.. js:attribute:: values
|
||||
|
||||
:js:class:`~openerp.web.search.FacetValues` as a javascript
|
||||
attribute, stores all the values for the facet and helps
|
||||
propagate their events to the facet. Is also available as a
|
||||
backbone attribute (via ``#get`` and ``#set``) in which cases
|
||||
it serializes to and deserializes from javascript arrays (via
|
||||
``Collection#toJSON`` and ``Collection#reset``).
|
||||
|
||||
.. js:attribute:: [icon]
|
||||
|
||||
optional, a single ASCII letter (a-z or A-Z) mapping to the
|
||||
bundled mnmliconsRegular icon font.
|
||||
|
||||
When a facet with an ``icon`` attribute is rendered, the icon
|
||||
is displayed (in the icon font) in the first section of the
|
||||
facet instead of the ``category``.
|
||||
|
||||
By default, only filters make use of this facility.
|
||||
|
||||
.. js:class:: openerp.web.search.FacetValues
|
||||
|
||||
`Backbone collection`_ of
|
||||
:js:class:`~openerp.web.search.FacetValue` instances.
|
||||
|
||||
.. js:class:: openerp.web.search.FacetValue
|
||||
|
||||
`Backbone model`_ representing a single value within a facet,
|
||||
represents a pair of (displayed name, logical value).
|
||||
|
||||
.. js:attribute:: label
|
||||
|
||||
Backbone model attribute storing the "displayable"
|
||||
representation of the value, visually output to the
|
||||
user. Must be a string.
|
||||
|
||||
.. js:attribute:: value
|
||||
|
||||
Backbone model attribute storing the logical/internal value
|
||||
(of itself), will be used by
|
||||
:js:class:`~openerp.web.search.Input` to serialize to domains
|
||||
and contexts.
|
||||
|
||||
Can be of any type.
|
||||
|
||||
Field services
|
||||
--------------
|
||||
|
||||
:js:class:`~openerp.web.search.Field` provides a default
|
||||
implementation of :js:func:`~openerp.web.search.Input.get_domain` and
|
||||
:js:func:`~openerp.web.search.Input.get_context` taking care of most
|
||||
of the peculiarities pertaining to OpenERP's handling of fields in
|
||||
search views. It also provides finer hooks to let developers of new
|
||||
fields and widgets customize the behavior they want without
|
||||
necessarily having to reimplement all of
|
||||
:js:func:`~openerp.web.search.Input.get_domain` or
|
||||
:js:func:`~openerp.web.search.Input.get_context`:
|
||||
|
||||
.. js:function:: openerp.web.search.Field.get_context(facet)
|
||||
|
||||
If the field has no ``@context``, simply returns
|
||||
``null``. Otherwise, calls
|
||||
:js:func:`~openerp.web.search.Field.value_from` once for each
|
||||
:js:class:`~openerp.web.search.FacetValue` of the current
|
||||
:js:class:`~openerp.web.search.Facet` (in order to extract the
|
||||
basic javascript object from the
|
||||
:js:class:`~openerp.web.search.FacetValue` then evaluates
|
||||
``@context`` with each of these values set as ``self``, and
|
||||
returns the union of all these contexts.
|
||||
|
||||
:param facet:
|
||||
:type facet: openerp.web.search.Facet
|
||||
:returns: a context (literal or compound)
|
||||
|
||||
.. js:function:: openerp.web.search.Field.get_domain(facet)
|
||||
|
||||
If the field has no ``@filter_domain``, calls
|
||||
:js:func:`~openerp.web.search.Field.make_domain` once with each
|
||||
:js:class:`~openerp.web.search.FacetValue` of the current
|
||||
:js:class:`~openerp.web.search.Facet` as well as the field's
|
||||
``@name`` and either its ``@operator`` or
|
||||
:js:attr:`~openerp.web.search.Field.default_operator`.
|
||||
|
||||
If the field has an ``@filter_value``, calls
|
||||
:js:func:`~openerp.web.search.Field.value_from` once per
|
||||
:js:class:`~openerp.web.search.FacetValue` and evaluates
|
||||
``@filter_value`` with each of these values set as ``self``.
|
||||
|
||||
In either case, "ors" all of the resulting domains (using ``|``)
|
||||
if there is more than one
|
||||
:js:class:`~openerp.web.search.FacetValue` and returns the union
|
||||
of the result.
|
||||
|
||||
:param facet:
|
||||
:type facet: openerp.web.search.Facet
|
||||
:returns: a domain (literal or compound)
|
||||
|
||||
.. js:function:: openerp.web.search.Field.make_domain(name, operator, facetValue)
|
||||
|
||||
Builds a literal domain from the provided data. Calls
|
||||
:js:func:`~openerp.web.search.Field.value_from` on the
|
||||
:js:class:`~openerp.web.search.FacetValue` and evaluates and sets
|
||||
it as the domain's third value, uses the other two parameters as
|
||||
the first two values.
|
||||
|
||||
Can be overridden to build more complex default domains.
|
||||
|
||||
:param String name: the field's name
|
||||
:param String operator: the operator to use in the field's domain
|
||||
:param facetValue:
|
||||
:type facetValue: openerp.web.search.FacetValue
|
||||
:returns: Array<(String, String, Object)>
|
||||
|
||||
.. js:function:: openerp.web.search.Field.value_from(facetValue)
|
||||
|
||||
Extracts a "bare" javascript value from the provided
|
||||
:js:class:`~openerp.web.search.FacetValue`, and returns it.
|
||||
|
||||
The default implementation will simply return the ``value``
|
||||
backbone property of the argument.
|
||||
|
||||
:param facetValue:
|
||||
:type facetValue: openerp.web.search.FacetValue
|
||||
:returns: Object
|
||||
|
||||
.. js:attribute:: openerp.web.search.Field.default_operator
|
||||
|
||||
Operator used to build a domain when a field has no ``@operator``
|
||||
or ``@filter_domain``. ``"="`` for
|
||||
:js:class:`~openerp.web.search.Field`
|
||||
|
||||
Arbitrary data storage
|
||||
----------------------
|
||||
|
||||
:js:class:`~openerp.web.search.Facet` and
|
||||
:js:class:`~openerp.web.search.FacetValue` objects (and structures)
|
||||
provided by your widgets should never be altered by the search view
|
||||
(or an other widget). This means you are free to add arbitrary fields
|
||||
in these structures if you need to (because you have more complex
|
||||
needs than the attributes described in this document).
|
||||
|
||||
Ideally this should be avoided, but the possibility remains.
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
.. todo:: merge in changelog instead?
|
||||
|
||||
The displaying of the search view was significantly altered from
|
||||
OpenERP Web 6.1 to OpenERP Web 7.
|
||||
|
||||
As a result, while the external API used to interact with the search
|
||||
view does not change many internal details — including the interaction
|
||||
between the search view and its widgets — were significantly altered:
|
||||
|
||||
Internal operations
|
||||
+++++++++++++++++++
|
||||
|
||||
* :js:func:`openerp.web.SearchView.do_clear` has been removed
|
||||
* :js:func:`openerp.web.SearchView.do_toggle_filter` has been removed
|
||||
|
||||
Widgets API
|
||||
+++++++++++
|
||||
|
||||
* :js:func:`openerp.web.search.Widget.render` has been removed
|
||||
|
||||
* :js:func:`openerp.web.search.Widget.make_id` has been removed
|
||||
|
||||
* Search field objects are not openerp widgets anymore, their
|
||||
``start`` is not generally called
|
||||
|
||||
* :js:func:`~openerp.web.search.Input.clear` has been removed since
|
||||
clearing the search view now simply consists of removing all search
|
||||
facets
|
||||
|
||||
* :js:func:`~openerp.web.search.Input.get_domain` and
|
||||
:js:func:`~openerp.web.search.Input.get_context` now take a
|
||||
:js:class:`~openerp.web.search.Facet` as parameter, from which it's
|
||||
their job to get whatever value they want
|
||||
|
||||
* :js:func:`~openerp.web.search.Input.get_groupby` has been added. It returns
|
||||
an :js:class:`Array` of context-like constructs. By default, it does not do
|
||||
anything in :js:class:`~openerp.web.search.Field` and it returns the various
|
||||
contexts of its enabled filters in
|
||||
:js:class:`~openerp.web.search.FilterGroup`.
|
||||
|
||||
Filters
|
||||
+++++++
|
||||
|
||||
* :js:func:`openerp.web.search.Filter.is_enabled` has been removed
|
||||
|
||||
* :js:class:`~openerp.web.search.FilterGroup` instances are still
|
||||
rendered (and started) in the "advanced search" drawer.
|
||||
|
||||
Fields
|
||||
++++++
|
||||
|
||||
* ``get_value`` has been replaced by
|
||||
:js:func:`~openerp.web.search.Field.value_from` as it now takes a
|
||||
:js:class:`~openerp.web.search.FacetValue` argument (instead of no
|
||||
argument). It provides a default implementation returning the
|
||||
``value`` property of its argument.
|
||||
|
||||
* The third argument to
|
||||
:js:func:`~openerp.web.search.Field.make_domain` is now a
|
||||
:js:class:`~openerp.web.search.FacetValue` so child classes have all
|
||||
the information they need to derive the "right" resulting domain.
|
||||
|
||||
Custom filters
|
||||
++++++++++++++
|
||||
|
||||
Instead of being an intrinsic part of the search view, custom filters
|
||||
are now a special case of filter groups. They are treated specially
|
||||
still, but much less so than they used to be.
|
||||
|
||||
Many To One
|
||||
+++++++++++
|
||||
|
||||
* Because the autocompletion service is now provided by the search
|
||||
view itself,
|
||||
:js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
|
||||
been removed.
|
||||
|
||||
Advanced Search
|
||||
+++++++++++++++
|
||||
|
||||
* The advanced search is now a more standard
|
||||
:js:class:`~openerp.web.search.Input` configured to be rendered in
|
||||
the drawer.
|
||||
|
||||
* :js:class:`~openerp.web.search.ExtendedSearchProposition.Field` are
|
||||
now standard widgets, with the "right" behaviors (they don't rebind
|
||||
their ``$element`` in ``start()``)
|
||||
|
||||
* The ad-hoc optional setting of the openerp field descriptor on a
|
||||
:js:class:`~openerp.web.search.ExtendedSearchProposition.Field` has
|
||||
been removed, the field descriptor is now passed as second argument
|
||||
to the
|
||||
:js:class:`~openerp.web.search.ExtendedSearchProposition.Field`'s
|
||||
constructor, and bound to its
|
||||
:js:attr:`~openerp.web.search.ExtendedSearchProposition.Field.field`.
|
||||
|
||||
* Instead of its former domain triplet ``(field, operator, value)``,
|
||||
:js:func:`~openerp.web.search.ExtendedSearchProposition.get_proposition`
|
||||
now returns an object with two fields ``label`` and ``value``,
|
||||
respectively a human-readable version of the proposition and the
|
||||
corresponding domain triplet for the proposition.
|
||||
|
||||
.. [#previous]
|
||||
|
||||
the original view was implemented on top of a monkey-patched
|
||||
VisualSearch, but as our needs diverged from VisualSearch's goal
|
||||
this made less and less sense ultimately leading to a clean-room
|
||||
reimplementation
|
||||
|
||||
.. [#no_impl]
|
||||
|
||||
In case you are extending the search view with a brand new type of
|
||||
input
|
||||
|
||||
.. [#completion]
|
||||
|
||||
Ideally this array should not hold more than about 10 items, but
|
||||
the search view does not put any constraint on this at the
|
||||
moment. Note that this may change.
|
||||
|
||||
.. [#facet-field]
|
||||
|
||||
``field`` does not actually need to be an instance of
|
||||
:js:class:`~openerp.web.search.Input`, nor does it need to be what
|
||||
created the facet, it just needs to provide the three
|
||||
facet-serialization methods
|
||||
:js:func:`~openerp.web.search.Input.get_domain`,
|
||||
:js:func:`~openerp.web.search.Input.get_context` and
|
||||
:js:func:`~openerp.web.search.Input.get_gropuby`, existing
|
||||
:js:class:`~openerp.web.search.Input` subtypes merely provide
|
||||
convenient base implementation for those methods.
|
||||
|
||||
Complex search view inputs (especially those living in the drawer)
|
||||
may prefer using object literals with the right slots returning
|
||||
closed-over values or some other scheme un-bound to an actual
|
||||
:js:class:`~openerp.web.search.Input`, as
|
||||
:js:class:`~openerp.web.search.CustomFilters` and
|
||||
:js:class:`~openerp.web.search.Advanced` do.
|
||||
|
||||
.. [#special]
|
||||
|
||||
search view fields may also bundle context data to add to the
|
||||
search context
|
||||
|
||||
.. _Backbone:
|
||||
http://documentcloud.github.com/backbone/
|
||||
|
||||
.. _Backbone.Collection:
|
||||
.. _Backbone collection:
|
||||
http://documentcloud.github.com/backbone/#Collection
|
||||
|
||||
.. _Backbone model:
|
||||
http://documentcloud.github.com/backbone/#Model
|
||||
|
||||
.. _commit 3fca87101d:
|
||||
https://github.com/documentcloud/visualsearch/commit/3fca87101d
|
||||