[MERGE] Merged trunk.
bzr revid: jra@tinyerp.com-20121112055941-y8tc8ga2rtwoau3q
This commit is contained in:
@@ -38,7 +38,7 @@ This module provides the core of the OpenERP Web Client.
|
||||
"static/lib/underscore/underscore.string.js",
|
||||
"static/lib/backbone/backbone.js",
|
||||
"static/lib/cleditor/jquery.cleditor.js",
|
||||
"static/lib/py.js/lib/py.js",
|
||||
"static/lib/py.js/lib/py.js",
|
||||
"static/src/js/boot.js",
|
||||
"static/src/js/corelib.js",
|
||||
"static/src/js/coresetup.js",
|
||||
@@ -67,4 +67,5 @@ This module provides the core of the OpenERP Web Client.
|
||||
'qweb' : [
|
||||
"static/src/xml/*.xml",
|
||||
],
|
||||
'bootstrap': True,
|
||||
}
|
||||
|
||||
@@ -81,57 +81,6 @@ def rjsmin(script):
|
||||
).strip()
|
||||
return result
|
||||
|
||||
def sass2scss(src):
|
||||
# Validated by diff -u of sass2scss against:
|
||||
# sass-convert -F sass -T scss openerp.sass openerp.scss
|
||||
block = []
|
||||
sass = ('', block)
|
||||
reComment = re.compile(r'//.*$')
|
||||
reIndent = re.compile(r'^\s+')
|
||||
reIgnore = re.compile(r'^\s*(//.*)?$')
|
||||
reFixes = { re.compile(r'\(\((.*)\)\)') : r'(\1)', }
|
||||
lastLevel = 0
|
||||
prevBlocks = {}
|
||||
for l in src.split('\n'):
|
||||
l = l.rstrip()
|
||||
if reIgnore.search(l): continue
|
||||
l = reComment.sub('', l)
|
||||
l = l.rstrip()
|
||||
indent = reIndent.match(l)
|
||||
level = indent.end() if indent else 0
|
||||
l = l[level:]
|
||||
if level>lastLevel:
|
||||
prevBlocks[lastLevel] = block
|
||||
newBlock = []
|
||||
block[-1] = (block[-1], newBlock)
|
||||
block = newBlock
|
||||
elif level<lastLevel:
|
||||
block = prevBlocks[level]
|
||||
lastLevel = level
|
||||
if not l: continue
|
||||
# Fixes
|
||||
for ereg, repl in reFixes.items():
|
||||
l = ereg.sub(repl if type(repl)==str else repl(), l)
|
||||
block.append(l)
|
||||
|
||||
def write(sass, level=-1):
|
||||
out = ""
|
||||
indent = ' '*level
|
||||
if type(sass)==tuple:
|
||||
if level>=0:
|
||||
out += indent+sass[0]+" {\n"
|
||||
for e in sass[1]:
|
||||
out += write(e, level+1)
|
||||
if level>=0:
|
||||
out = out.rstrip(" \n")
|
||||
out += ' }\n'
|
||||
if level==0:
|
||||
out += "\n"
|
||||
else:
|
||||
out += indent+sass+";\n"
|
||||
return out
|
||||
return write(sass)
|
||||
|
||||
def db_list(req):
|
||||
dbs = []
|
||||
proxy = req.session.proxy("db")
|
||||
@@ -142,6 +91,17 @@ def db_list(req):
|
||||
dbs = [i for i in dbs if re.match(r, i)]
|
||||
return dbs
|
||||
|
||||
def db_monodb(req):
|
||||
# if only one db is listed returns it else return False
|
||||
try:
|
||||
dbs = db_list(req)
|
||||
if len(dbs) == 1:
|
||||
return dbs[0]
|
||||
except xmlrpclib.Fault:
|
||||
# ignore access denied
|
||||
pass
|
||||
return False
|
||||
|
||||
def module_topological_sort(modules):
|
||||
""" Return a list of module names sorted so that their dependencies of the
|
||||
modules are listed before the module itself
|
||||
@@ -231,22 +191,14 @@ def module_installed_bypass_session(dbname):
|
||||
|
||||
def module_boot(req):
|
||||
server_wide_modules = openerp.conf.server_wide_modules or ['web']
|
||||
return [m for m in server_wide_modules if m in openerpweb.addons_manifest]
|
||||
# TODO the following will be enabled once we separate the module code and translation loading
|
||||
serverside = []
|
||||
dbside = []
|
||||
for i in server_wide_modules:
|
||||
if i in openerpweb.addons_manifest:
|
||||
serverside.append(i)
|
||||
# if only one db load every module at boot
|
||||
dbs = []
|
||||
try:
|
||||
dbs = db_list(req)
|
||||
except xmlrpclib.Fault:
|
||||
# ignore access denied
|
||||
pass
|
||||
if len(dbs) == 1:
|
||||
dbside = module_installed_bypass_session(dbs[0])
|
||||
monodb = db_monodb(req)
|
||||
if monodb:
|
||||
dbside = module_installed_bypass_session(monodb)
|
||||
dbside = [i for i in dbside if i not in serverside]
|
||||
addons = serverside + dbside
|
||||
return addons
|
||||
@@ -549,7 +501,7 @@ def _local_web_translations(trans_file):
|
||||
messages.append({'id': x.id, 'string': x.string})
|
||||
return messages
|
||||
|
||||
def from_elementtree(el, preserve_whitespaces=False):
|
||||
def xml2json_from_elementtree(el, preserve_whitespaces=False):
|
||||
""" xml2json-direct
|
||||
Simple and straightforward XML-to-JSON converter in Python
|
||||
New BSD Licensed
|
||||
@@ -569,13 +521,12 @@ def from_elementtree(el, preserve_whitespaces=False):
|
||||
if el.text and (preserve_whitespaces or el.text.strip() != ''):
|
||||
kids.append(el.text)
|
||||
for kid in el:
|
||||
kids.append(from_elementtree(kid, preserve_whitespaces))
|
||||
kids.append(xml2json_from_elementtree(kid, preserve_whitespaces))
|
||||
if kid.tail and (preserve_whitespaces or kid.tail.strip() != ''):
|
||||
kids.append(kid.tail)
|
||||
res["children"] = kids
|
||||
return res
|
||||
|
||||
|
||||
def content_disposition(filename, req):
|
||||
filename = filename.encode('utf8')
|
||||
escaped = urllib2.quote(filename)
|
||||
@@ -612,8 +563,7 @@ html_template = """<!DOCTYPE html>
|
||||
</head>
|
||||
<body>
|
||||
<!--[if lte IE 8]>
|
||||
<script type="text/javascript"
|
||||
src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
|
||||
<script>
|
||||
var test = function() {
|
||||
CFInstall.check({
|
||||
@@ -750,12 +700,13 @@ class WebClient(openerpweb.Controller):
|
||||
|
||||
translations_per_module = {}
|
||||
for addon_name in mods:
|
||||
addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
|
||||
f_name = os.path.join(addons_path, addon_name, "i18n", lang + ".po")
|
||||
if not os.path.exists(f_name):
|
||||
continue
|
||||
translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)}
|
||||
|
||||
if openerpweb.addons_manifest[addon_name].get('bootstrap'):
|
||||
addons_path = openerpweb.addons_manifest[addon_name]['addons_path']
|
||||
f_name = os.path.join(addons_path, addon_name, "i18n", lang + ".po")
|
||||
if not os.path.exists(f_name):
|
||||
continue
|
||||
translations_per_module[addon_name] = {'messages': _local_web_translations(f_name)}
|
||||
|
||||
return {"modules": translations_per_module,
|
||||
"lang_parameters": None}
|
||||
|
||||
@@ -814,8 +765,7 @@ class Database(openerpweb.Controller):
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def get_list(self, req):
|
||||
dbs = db_list(req)
|
||||
return {"db_list": dbs}
|
||||
return db_list(req)
|
||||
|
||||
@openerpweb.jsonrequest
|
||||
def create(self, req, fields):
|
||||
@@ -1298,17 +1248,6 @@ class DataSet(openerpweb.Controller):
|
||||
m.write(id, { field: i + offset })
|
||||
return True
|
||||
|
||||
class DataGroup(openerpweb.Controller):
|
||||
_cp_path = "/web/group"
|
||||
@openerpweb.jsonrequest
|
||||
def read(self, req, model, fields, group_by_fields, domain=None, sort=None):
|
||||
Model = req.session.model(model)
|
||||
context, domain = eval_context_and_domain(req.session, req.context, domain)
|
||||
|
||||
return Model.read_group(
|
||||
domain or [], fields, group_by_fields, 0, False,
|
||||
dict(context, group_by=group_by_fields), sort or False)
|
||||
|
||||
class View(openerpweb.Controller):
|
||||
_cp_path = "/web/view"
|
||||
|
||||
@@ -1342,7 +1281,7 @@ class View(openerpweb.Controller):
|
||||
xml = self.transform_view(arch, session, evaluation_context)
|
||||
else:
|
||||
xml = ElementTree.fromstring(arch)
|
||||
fvg['arch'] = from_elementtree(xml, preserve_whitespaces)
|
||||
fvg['arch'] = xml2json_from_elementtree(xml, preserve_whitespaces)
|
||||
|
||||
if 'id' in fvg['fields']:
|
||||
# Special case for id's
|
||||
|
||||
+6
-102
@@ -1,6 +1,9 @@
|
||||
Asynchronous Operations
|
||||
=======================
|
||||
|
||||
This documentation is outdated as then() is now pipe. Please never copy api
|
||||
from other libraries just link to it.
|
||||
|
||||
As a language (and runtime), javascript is fundamentally
|
||||
single-threaded. This means any blocking request or computation will
|
||||
blocks the whole page (and, in older browsers, the software itself
|
||||
@@ -26,8 +29,7 @@ Deferreds
|
||||
---------
|
||||
|
||||
Deferreds are a form of `promises`_. OpenERP Web currently uses
|
||||
`jQuery's deferred`_, but any `CommonJS Promises/A`_ implementation
|
||||
should work.
|
||||
`jQuery's deferred`_.
|
||||
|
||||
The core idea of deferreds is that potentially asynchronous methods
|
||||
will return a :js:class:`Deferred` object instead of an arbitrary
|
||||
@@ -44,9 +46,8 @@ directly to asynchronous methods is the ability to :ref:`compose them
|
||||
Using deferreds
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
`CommonJS Promises/A`_ deferreds have only one method of importance:
|
||||
:js:func:`Deferred.then`. This method is used to attach new callbacks
|
||||
to the deferred object.
|
||||
deferreds have only one method of importance: :js:func:`Deferred.then`. This
|
||||
method is used to attach new callbacks to the deferred object.
|
||||
|
||||
* the first parameter attaches a success callback, called when the
|
||||
deferred object is successfully resolved and provided with the
|
||||
@@ -248,103 +249,6 @@ promise-based APIs, in order to filter their resolution value counts
|
||||
for instance (to take advantage of :js:func:`when` 's special treatment
|
||||
of single-value promises).
|
||||
|
||||
jQuery.Deferred API
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. js:function:: when(deferreds…)
|
||||
|
||||
:param deferreds: deferred objects to multiplex
|
||||
:returns: a multiplexed deferred
|
||||
:rtype: :js:class:`Deferred`
|
||||
|
||||
.. js:class:: Deferred
|
||||
|
||||
.. js:function:: Deferred.then(doneCallback[, failCallback])
|
||||
|
||||
Attaches new callbacks to the resolution or rejection of the
|
||||
deferred object. Callbacks are executed in the order they are
|
||||
attached to the deferred.
|
||||
|
||||
To provide only a failure callback, pass ``null`` as the
|
||||
``doneCallback``, to provide only a success callback the
|
||||
second argument can just be ignored (and not passed at all).
|
||||
|
||||
:param doneCallback: function called when the deferred is resolved
|
||||
:type doneCallback: Function
|
||||
:param failCallback: function called when the deferred is rejected
|
||||
:type failCallback: Function
|
||||
:returns: the deferred object on which it was called
|
||||
:rtype: :js:class:`Deferred`
|
||||
|
||||
.. js:function:: Deferred.done(doneCallback)
|
||||
|
||||
Attaches a new success callback to the deferred, shortcut for
|
||||
``deferred.then(doneCallback)``.
|
||||
|
||||
This is a jQuery extension to `CommonJS Promises/A`_ providing
|
||||
little value over calling :js:func:`~Deferred.then` directly,
|
||||
it should be avoided.
|
||||
|
||||
:param doneCallback: function called when the deferred is resolved
|
||||
:type doneCallback: Function
|
||||
:returns: the deferred object on which it was called
|
||||
:rtype: :js:class:`Deferred`
|
||||
|
||||
.. js:function:: Deferred.fail(failCallback)
|
||||
|
||||
Attaches a new failure callback to the deferred, shortcut for
|
||||
``deferred.then(null, failCallback)``.
|
||||
|
||||
A second jQuery extension to `Promises/A <CommonJS
|
||||
Promises/A>`_. Although it provides more value than
|
||||
:js:func:`~Deferred.done`, it still is not much and should be
|
||||
avoided as well.
|
||||
|
||||
:param failCallback: function called when the deferred is rejected
|
||||
:type failCallback: Function
|
||||
:returns: the deferred object on which it was called
|
||||
:rtype: :js:class:`Deferred`
|
||||
|
||||
.. js:function:: Deferred.promise()
|
||||
|
||||
Returns a read-only view of the deferred object, with all
|
||||
mutators (resolve and reject) methods removed.
|
||||
|
||||
.. js:function:: Deferred.resolve(value…)
|
||||
|
||||
Called to resolve a deferred, any value provided will be
|
||||
passed onto the success handlers of the deferred object.
|
||||
|
||||
Resolving a deferred which has already been resolved or
|
||||
rejected has no effect.
|
||||
|
||||
.. js:function:: Deferred.reject(value…)
|
||||
|
||||
Called to reject (fail) a deferred, any value provided will be
|
||||
passed onto the failure handler of the deferred object.
|
||||
|
||||
Rejecting a deferred which has already been resolved or
|
||||
rejected has no effect.
|
||||
|
||||
.. js:function:: Deferred.pipe(doneFilter[, failFilter])
|
||||
|
||||
Filters the result of a deferred, able to transform a success
|
||||
into failure and a failure into success, or to delay
|
||||
resolution further.
|
||||
|
||||
.. [#] or simply calling :js:class:`Deferred` as a function, the
|
||||
result is the same
|
||||
|
||||
.. [#] or not-promises, the `CommonJS Promises/B`_ role of
|
||||
:js:func:`when` is to be able to treat values and promises
|
||||
uniformly: :js:func:`when` will pass promises through directly,
|
||||
but non-promise values and objects will be transformed into a
|
||||
resolved promise (resolving themselves with the value itself).
|
||||
|
||||
jQuery's :js:func:`when` keeps this behavior making deferreds
|
||||
easy to build from "static" values, or allowing defensive code
|
||||
where expected promises are wrapped in :js:func:`when` just in
|
||||
case.
|
||||
|
||||
.. _promises: http://en.wikipedia.org/wiki/Promise_(programming)
|
||||
.. _jQuery's deferred: http://api.jquery.com/category/deferred-object/
|
||||
|
||||
@@ -15,7 +15,7 @@ DataSet -> Model
|
||||
----------------
|
||||
|
||||
The 6.1 ``DataSet`` API has been deprecated in favor of the smaller
|
||||
and more orthogonal :doc:`Model </rpc>` API, which more closely
|
||||
and more orthogonal :doc:`Model </dev_rpc>` API, which more closely
|
||||
matches the API in OpenERP Web's Python side and in OpenObject addons
|
||||
and removes most stateful behavior of DataSet.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.. highlight:: javascript
|
||||
|
||||
Creating a new client action
|
||||
============================
|
||||
Client actions
|
||||
==============
|
||||
|
||||
Client actions are the client-side version of OpenERP's "Server
|
||||
Actions": instead of allowing for semi-arbitrary code to be executed
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
QWeb Cookbook
|
||||
=============
|
||||
QWeb
|
||||
====
|
||||
|
||||
QWeb is the template engine used by the OpenERP Web Client. It is a home made engine create by OpenERP developers. There are a few things to note about it:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
User Interaction: Widget
|
||||
========================
|
||||
Widget
|
||||
======
|
||||
|
||||
This is the base class for all visual components. It corresponds to an MVC
|
||||
view. It provides a number of services to handle a section of a page:
|
||||
+10
-11
@@ -12,20 +12,19 @@ Contents:
|
||||
:maxdepth: 1
|
||||
|
||||
presentation
|
||||
async
|
||||
|
||||
dev_rpc
|
||||
dev_widget
|
||||
dev_qweb
|
||||
dev_client_action
|
||||
|
||||
internal_form
|
||||
internal_list
|
||||
internal_search
|
||||
|
||||
changelog-7.0
|
||||
|
||||
async
|
||||
rpc
|
||||
|
||||
widget
|
||||
qweb
|
||||
search-view
|
||||
|
||||
list-view
|
||||
form-notes
|
||||
|
||||
guides/client-action
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
@@ -451,7 +451,7 @@ formview, delegating instead to its
|
||||
created.
|
||||
|
||||
The result should be a valid form view, see :doc:`Form Notes
|
||||
<form-notes>` for various peculiarities of the form view
|
||||
<internal_form>` for various peculiarities of the form view
|
||||
format.
|
||||
|
||||
:param editor: editor object asking for the view
|
||||
@@ -7,17 +7,17 @@ Prerequisites
|
||||
|
||||
Prerequisites to code addons for the OpenERP Web Client:
|
||||
|
||||
- Html
|
||||
- Css
|
||||
- Javascript
|
||||
- HTML5
|
||||
- CSS
|
||||
- Javascript (Ecmascript 5)
|
||||
- jQuery
|
||||
|
||||
Once you know all this, that's only the beginning. Most usages of Javascript/jQuery are small hacks to make a web page nicer. The OpenERP Client Web is different: it's a web application, not a web site. It doesn't have multiple pages generated by a server side code. Only one unique empty page is loaded and all the html is generated by Javascript code, the page is never reloaded. If you never developed that kind of application you still have lot of good practices to learn.
|
||||
|
||||
Recommendations
|
||||
---------------
|
||||
Generic Guidelines
|
||||
------------------
|
||||
|
||||
First, here are the 5 golden rules when you create web 2.0 applications:
|
||||
First, here are some generic recommandations about web 2.0 applications:
|
||||
|
||||
* **Do not use ids**. Html ids are evil, the key anti-feature that makes your components non-reusable. You want to identify a dom part? Save a jQuery object over that dom element. If you really need to use ids, use _.uniqueId(), but 99% of the time you don't need to, classes are sufficient.
|
||||
* **Do not use predictable css class names** like "content" or "navigation", ten other developers will have the same idea than you and there will be clashes. A simple way to avoid this is to use prefixes. For example, if you need a css class for the button named "new" that is contained in the form view which is itself contained in the OpenERP application, name it "oe-form-view-new-button".
|
||||
@@ -25,12 +25,18 @@ First, here are the 5 golden rules when you create web 2.0 applications:
|
||||
* As a general advice, **Never assume you own the page**. When you create a component, it is never unique and is always surrounded by a bunch of crazy html. You have to do with it.
|
||||
* **Learn how to use jQuery's deferreds** [1]_. That concept may seem over-complicated, but the experience tell us that it is nearly impossible to create big-size javascript applications without that.
|
||||
|
||||
OpenERP guidelines
|
||||
------------------
|
||||
More recommendations related to the specific case of the OpenERP Web Client:
|
||||
|
||||
* Your components should inherit from the *Widget* class.
|
||||
* The code should conform to EcmasScript 5 without the "use strict" mode as we support IE9.
|
||||
|
||||
* Your components should inherit from the *Widget* class. And use *Widget* 's methods (*appendTo*, *replace*,...) to insert your component and its content into the dom.
|
||||
|
||||
* Use QWeb templates for html rendering.
|
||||
* Use *Widget* 's methods (*appendTo*, *replace*,...) to insert your component and its content into the dom.
|
||||
|
||||
* All css classes should have the prefix *oe-* .
|
||||
|
||||
* Functions that call rpc() should return a deferred, even if it calls it indirectly. So a function that calls a function that calls a function that calls rpc() should return a deferred too.
|
||||
|
||||
.. [1] http://api.jquery.com/category/deferred-object/
|
||||
|
||||
@@ -189,7 +189,7 @@ instance.web.Dialog = instance.web.Widget.extend({
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.CrashManager = instance.web.CallbackEnabled.extend({
|
||||
instance.web.CrashManager = instance.web.Class.extend({
|
||||
rpc_error: function(error) {
|
||||
if (error.data.fault_code) {
|
||||
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
|
||||
@@ -403,7 +403,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
|
||||
$('.oe_secondary_menus_container,.oe_user_menu_placeholder').empty();
|
||||
var fetch_db = this.rpc("/web/database/get_list", {}).then(
|
||||
function(result) {
|
||||
self.db_list = result.db_list;
|
||||
self.db_list = result;
|
||||
},
|
||||
function (_, ev) {
|
||||
ev.preventDefault();
|
||||
@@ -648,7 +648,7 @@ instance.web.Login = instance.web.Widget.extend({
|
||||
return d;
|
||||
},
|
||||
on_db_loaded: function (result) {
|
||||
this.db_list = result.db_list;
|
||||
this.db_list = result;
|
||||
this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db}));
|
||||
if(this.db_list.length === 0) {
|
||||
this.do_action("database_manager");
|
||||
|
||||
+107
-142
@@ -243,15 +243,17 @@ instance.web.ParentedMixin = {
|
||||
|
||||
/**
|
||||
* Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead.
|
||||
*
|
||||
* This class just handle the dispatching of events, it is not meant to be extended,
|
||||
* nor used directly. All integration with parenting and automatic unregistration of
|
||||
* events is done in EventDispatcherMixin.
|
||||
*
|
||||
* Copyright notice for the following Class:
|
||||
*
|
||||
* (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
||||
* Backbone may be freely distributed under the MIT license.
|
||||
* For all details and documentation:
|
||||
* http://backbonejs.org
|
||||
*
|
||||
* This class just handle the dispatching of events, it is not meant to be extended,
|
||||
* nor used directly. All integration with parenting and automatic unregistration of
|
||||
* events is done in EventDispatcherMixin.
|
||||
*
|
||||
*/
|
||||
var Events = instance.web.Class.extend({
|
||||
@@ -334,7 +336,6 @@ var Events = instance.web.Class.extend({
|
||||
return this;
|
||||
}
|
||||
});
|
||||
// end of Jeremy Ashkenas' code
|
||||
|
||||
instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
|
||||
__eventDispatcherMixin: true,
|
||||
@@ -426,66 +427,94 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, {
|
||||
init: function() {
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
var self = this;
|
||||
// Transform on_/do_* methods into callbacks
|
||||
var callback_maker = function(fn) {
|
||||
return function() {
|
||||
return fn.apply(self, arguments);
|
||||
}
|
||||
};
|
||||
for (var name in this) {
|
||||
if(typeof(this[name]) == "function") {
|
||||
if((/^on_|^do_/).test(name)) {
|
||||
this[name] = callback_maker(this[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Proxies a method of the object, in order to keep the right ``this`` on
|
||||
* method invocations.
|
||||
*
|
||||
* This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
|
||||
* even more so to ``jQuery.proxy`` with a fundamental difference: its
|
||||
* resolution of the method being called is lazy, meaning it will use the
|
||||
* method as it is when the proxy is called, not when the proxy is created.
|
||||
*
|
||||
* Other methods will fix the bound method to what it is when creating the
|
||||
* binding/proxy, which is fine in most javascript code but problematic in
|
||||
* OpenERP Web where developers may want to replace existing callbacks with
|
||||
* theirs.
|
||||
*
|
||||
* The semantics of this precisely replace closing over the method call.
|
||||
*
|
||||
* @param {String|Function} method function or name of the method to invoke
|
||||
* @returns {Function} proxied method
|
||||
*/
|
||||
proxy: function (method) {
|
||||
var self = this;
|
||||
return function () {
|
||||
var fn = (typeof method === 'string') ? self[method] : method;
|
||||
return fn.apply(self, arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Classes
|
||||
|
||||
instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
||||
/**
|
||||
* Base class for all visual components. Provides a lot of functionalities helpful
|
||||
* for the management of a part of the DOM.
|
||||
*
|
||||
* Widget handles:
|
||||
* - Rendering with QWeb.
|
||||
* - Life-cycle management and parenting (when a parent is destroyed, all its children are
|
||||
* destroyed too).
|
||||
* - Insertion in DOM.
|
||||
*
|
||||
* Guide to create implementations of the Widget class:
|
||||
* ==============================================
|
||||
*
|
||||
* Here is a sample child class:
|
||||
*
|
||||
* MyWidget = instance.base.Widget.extend({
|
||||
* // the name of the QWeb template to use for rendering
|
||||
* template: "MyQWebTemplate",
|
||||
*
|
||||
* init: function(parent) {
|
||||
* this._super(parent);
|
||||
* // stuff that you want to init before the rendering
|
||||
* },
|
||||
* start: function() {
|
||||
* // stuff you want to make after the rendering, `this.$el` holds a correct value
|
||||
* this.$el.find(".my_button").click(/* an example of event binding * /);
|
||||
*
|
||||
* // if you have some asynchronous operations, it's a good idea to return
|
||||
* // a promise in start()
|
||||
* var promise = this.rpc(...);
|
||||
* return promise;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* Now this class can simply be used with the following syntax:
|
||||
*
|
||||
* var my_widget = new MyWidget(this);
|
||||
* my_widget.appendTo($(".some-div"));
|
||||
*
|
||||
* With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
|
||||
* DOM inside the ".some-div" div and its events were binded.
|
||||
*
|
||||
* And of course, when you don't need that widget anymore, just do:
|
||||
*
|
||||
* my_widget.destroy();
|
||||
*
|
||||
* That will kill the widget in a clean way and erase its content from the dom.
|
||||
*/
|
||||
instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, {
|
||||
// Backbone-ish API
|
||||
tagName: 'div',
|
||||
id: null,
|
||||
className: null,
|
||||
attributes: {},
|
||||
events: {},
|
||||
/**
|
||||
* The name of the QWeb template that will be used for rendering. Must be
|
||||
* redefined in subclasses or the default render() method can not be used.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
template: null,
|
||||
/**
|
||||
* Constructs the widget and sets its parent if a parent is given.
|
||||
*
|
||||
* @constructs instance.web.Widget
|
||||
* @extends instance.web.CallbackEnabled
|
||||
*
|
||||
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
|
||||
* When that widget is destroyed by calling destroy(), the current instance will be
|
||||
* destroyed too. Can be null.
|
||||
*/
|
||||
init: function(parent) {
|
||||
instance.web.CallbackEnabledMixin.init.call(this);
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
this.setParent(parent);
|
||||
// Bind on_/do_* methods to this
|
||||
// We might remove this automatic binding in the future
|
||||
for (var name in this) {
|
||||
if(typeof(this[name]) == "function") {
|
||||
if((/^on_|^do_/).test(name)) {
|
||||
this[name] = this[name].bind(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// FIXME: this should not be
|
||||
this.setElement(this._make_descriptive());
|
||||
this.session = instance.session;
|
||||
},
|
||||
/**
|
||||
* Destroys the current widget, also destroys all its children before destroying itself.
|
||||
@@ -574,94 +603,32 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
|
||||
*/
|
||||
start: function() {
|
||||
return $.when();
|
||||
}
|
||||
});
|
||||
|
||||
// Classes
|
||||
|
||||
instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEnabledMixin, {
|
||||
init: function() {
|
||||
instance.web.CallbackEnabledMixin.init.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Base class for all visual components. Provides a lot of functionalities helpful
|
||||
* for the management of a part of the DOM.
|
||||
*
|
||||
* Widget handles:
|
||||
* - Rendering with QWeb.
|
||||
* - Life-cycle management and parenting (when a parent is destroyed, all its children are
|
||||
* destroyed too).
|
||||
* - Insertion in DOM.
|
||||
*
|
||||
* Guide to create implementations of the Widget class:
|
||||
* ==============================================
|
||||
*
|
||||
* Here is a sample child class:
|
||||
*
|
||||
* MyWidget = instance.base.Widget.extend({
|
||||
* // the name of the QWeb template to use for rendering
|
||||
* template: "MyQWebTemplate",
|
||||
*
|
||||
* init: function(parent) {
|
||||
* this._super(parent);
|
||||
* // stuff that you want to init before the rendering
|
||||
* },
|
||||
* start: function() {
|
||||
* // stuff you want to make after the rendering, `this.$el` holds a correct value
|
||||
* this.$el.find(".my_button").click(/* an example of event binding * /);
|
||||
*
|
||||
* // if you have some asynchronous operations, it's a good idea to return
|
||||
* // a promise in start()
|
||||
* var promise = this.rpc(...);
|
||||
* return promise;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* Now this class can simply be used with the following syntax:
|
||||
*
|
||||
* var my_widget = new MyWidget(this);
|
||||
* my_widget.appendTo($(".some-div"));
|
||||
*
|
||||
* With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
|
||||
* DOM inside the ".some-div" div and its events were binded.
|
||||
*
|
||||
* And of course, when you don't need that widget anymore, just do:
|
||||
*
|
||||
* my_widget.destroy();
|
||||
*
|
||||
* That will kill the widget in a clean way and erase its content from the dom.
|
||||
*/
|
||||
instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
||||
// Backbone-ish API
|
||||
tagName: 'div',
|
||||
id: null,
|
||||
className: null,
|
||||
attributes: {},
|
||||
events: {},
|
||||
},
|
||||
/**
|
||||
* The name of the QWeb template that will be used for rendering. Must be
|
||||
* redefined in subclasses or the default render() method can not be used.
|
||||
* Proxies a method of the object, in order to keep the right ``this`` on
|
||||
* method invocations.
|
||||
*
|
||||
* @type string
|
||||
* This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
|
||||
* even more so to ``jQuery.proxy`` with a fundamental difference: its
|
||||
* resolution of the method being called is lazy, meaning it will use the
|
||||
* method as it is when the proxy is called, not when the proxy is created.
|
||||
*
|
||||
* Other methods will fix the bound method to what it is when creating the
|
||||
* binding/proxy, which is fine in most javascript code but problematic in
|
||||
* OpenERP Web where developers may want to replace existing callbacks with
|
||||
* theirs.
|
||||
*
|
||||
* The semantics of this precisely replace closing over the method call.
|
||||
*
|
||||
* @param {String|Function} method function or name of the method to invoke
|
||||
* @returns {Function} proxied method
|
||||
*/
|
||||
template: null,
|
||||
/**
|
||||
* Constructs the widget and sets its parent if a parent is given.
|
||||
*
|
||||
* @constructs instance.web.Widget
|
||||
* @extends instance.web.CallbackEnabled
|
||||
*
|
||||
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
|
||||
* When that widget is destroyed by calling destroy(), the current instance will be
|
||||
* destroyed too. Can be null.
|
||||
*/
|
||||
init: function(parent) {
|
||||
instance.web.WidgetMixin.init.call(this,parent);
|
||||
// FIXME: this should not be
|
||||
this.setElement(this._make_descriptive());
|
||||
this.session = instance.session;
|
||||
proxy: function (method) {
|
||||
var self = this;
|
||||
return function () {
|
||||
var fn = (typeof method === 'string') ? self[method] : method;
|
||||
return fn.apply(self, arguments);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Renders the element. The default implementation renders the widget using QWeb,
|
||||
@@ -678,7 +645,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
|
||||
}
|
||||
this.replaceElement($el);
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-sets the widget's root element and replaces the old root element
|
||||
* (if any) by the new one in the DOM.
|
||||
@@ -941,7 +907,7 @@ instance.web.Registry = instance.web.Class.extend({
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
|
||||
triggers: {
|
||||
'request': 'Request sent',
|
||||
'response': 'Response received',
|
||||
@@ -950,13 +916,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
|
||||
},
|
||||
/**
|
||||
* @constructs instance.web.JsonRPC
|
||||
* @extends instance.web.CallbackEnabled
|
||||
*
|
||||
* @param {String} [server] JSON-RPC endpoint hostname
|
||||
* @param {String} [port] JSON-RPC endpoint port
|
||||
*/
|
||||
init: function() {
|
||||
this._super();
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
this.server = null;
|
||||
this.debug = ($.deparam($.param.querystring()).debug != undefined);
|
||||
},
|
||||
|
||||
@@ -53,7 +53,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
this.session_id = this.get_cookie('session_id');
|
||||
return this.session_reload().then(function(result) {
|
||||
var modules = instance._modules.join(',');
|
||||
var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.do_load_qweb);
|
||||
var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.load_qweb.bind(self));
|
||||
if(self.session_is_valid()) {
|
||||
return deferred.then(function() { return self.load_modules(); });
|
||||
}
|
||||
@@ -155,28 +155,26 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
load_modules: function() {
|
||||
var self = this;
|
||||
return this.rpc('/web/session/modules', {}).then(function(result) {
|
||||
var lang = self.user_context.lang,
|
||||
all_modules = _.uniq(self.module_list.concat(result));
|
||||
var params = { mods: all_modules, lang: lang};
|
||||
var all_modules = _.uniq(self.module_list.concat(result));
|
||||
var to_load = _.difference(result, self.module_list).join(',');
|
||||
self.module_list = all_modules;
|
||||
|
||||
var loaded = self.rpc('/web/webclient/translations', params).done(function(trans) {
|
||||
instance.web._t.database.set_bundle(trans);
|
||||
});
|
||||
var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"];
|
||||
var loaded = self.load_translations();
|
||||
var datejs_locale = "/web/static/lib/datejs/globalization/" + self.user_context.lang.replace("_", "-") + ".js";
|
||||
|
||||
var file_list = [ datejs_locale ];
|
||||
if(to_load.length) {
|
||||
loaded = $.when(
|
||||
loaded,
|
||||
self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.do_load_css),
|
||||
self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.do_load_qweb),
|
||||
self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)),
|
||||
self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.load_qweb.bind(self)),
|
||||
self.rpc('/web/webclient/jslist', {mods: to_load}).done(function(files) {
|
||||
file_list = file_list.concat(files);
|
||||
})
|
||||
);
|
||||
}
|
||||
return loaded.then(function () {
|
||||
return self.do_load_js(file_list);
|
||||
return self.load_js(file_list);
|
||||
}).done(function() {
|
||||
self.on_modules_loaded();
|
||||
self.trigger('module_loaded');
|
||||
@@ -190,7 +188,13 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
});
|
||||
});
|
||||
},
|
||||
do_load_css: function (files) {
|
||||
load_translations: function() {
|
||||
var params = { mods: this.module_list, lang: this.user_context.lang };
|
||||
return this.rpc('/web/webclient/translations', params).done(function(trans) {
|
||||
instance.web._t.database.set_bundle(trans);
|
||||
});
|
||||
},
|
||||
load_css: function (files) {
|
||||
var self = this;
|
||||
_.each(files, function (file) {
|
||||
$('head').append($('<link>', {
|
||||
@@ -200,7 +204,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
}));
|
||||
});
|
||||
},
|
||||
do_load_js: function(files) {
|
||||
load_js: function(files) {
|
||||
var self = this;
|
||||
var d = $.Deferred();
|
||||
if(files.length != 0) {
|
||||
@@ -212,7 +216,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
|
||||
return;
|
||||
tag.onload_done = true;
|
||||
self.do_load_js(files).done(function () {
|
||||
self.load_js(files).done(function () {
|
||||
d.resolve();
|
||||
});
|
||||
};
|
||||
@@ -223,7 +227,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
||||
}
|
||||
return d;
|
||||
},
|
||||
do_load_qweb: function(files) {
|
||||
load_qweb: function(files) {
|
||||
var self = this;
|
||||
_.each(files, function(file) {
|
||||
self.qweb_mutex.exec(function() {
|
||||
|
||||
@@ -358,17 +358,16 @@ instance.web.Model = instance.web.Class.extend({
|
||||
},
|
||||
});
|
||||
|
||||
instance.web.DataSet = instance.web.CallbackEnabled.extend({
|
||||
instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, {
|
||||
/**
|
||||
* Collection of OpenERP records, used to share records and the current selection between views.
|
||||
*
|
||||
* @constructs instance.web.DataSet
|
||||
* @extends instance.web.CallbackEnabled
|
||||
*
|
||||
* @param {String} model the OpenERP model this dataset will manage
|
||||
*/
|
||||
init: function(parent, model, context) {
|
||||
this._super(parent);
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
this.model = model;
|
||||
this.context = context || {};
|
||||
this.index = null;
|
||||
|
||||
@@ -2655,22 +2655,18 @@ instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
The progressbar field expect a float from 0 to 100.
|
||||
*/
|
||||
instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
|
||||
template: 'FieldProgressBar',
|
||||
start: function() {
|
||||
this._super.apply(this, arguments);
|
||||
render_value: function() {
|
||||
this.$el.progressbar({
|
||||
value: this.get('value'),
|
||||
value: this.get('value') || 0,
|
||||
disabled: this.get("effective_readonly")
|
||||
});
|
||||
},
|
||||
render_value: function() {
|
||||
var show_value = Number(this.get('value'));
|
||||
if (isNaN(show_value)) {
|
||||
show_value = 0;
|
||||
}
|
||||
var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0');
|
||||
this.$el.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
|
||||
var formatted_value = instance.web.format_value(this.get('value') || 0, { type : 'float' });
|
||||
this.$('span').html(formatted_value + '%');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1557,9 +1557,8 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
|
||||
}
|
||||
});
|
||||
|
||||
var DataGroup = instance.web.CallbackEnabled.extend({
|
||||
var DataGroup = instance.web.Class.extend({
|
||||
init: function(parent, model, domain, context, group_by, level) {
|
||||
this._super(parent, null);
|
||||
this.model = new instance.web.Model(model, context, domain);
|
||||
this.group_by = group_by;
|
||||
this.context = context;
|
||||
@@ -2217,7 +2216,7 @@ instance.web.list.Many2OneButton = instance.web.list.Column.extend({
|
||||
});
|
||||
instance.web.list.Many2Many = instance.web.list.Column.extend({
|
||||
_format: function (row_data, options) {
|
||||
if (row_data[this.id].value) {
|
||||
if (!_.isEmpty(row_data[this.id].value)) {
|
||||
// If value, use __display version for printing
|
||||
row_data[this.id] = row_data[this.id + '__display'];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user