[MERGE] Merged trunk.

bzr revid: jra@tinyerp.com-20121112055941-y8tc8ga2rtwoau3q
This commit is contained in:
Jiten (OpenERP)
2012-11-12 11:29:41 +05:30
19 changed files with 206 additions and 394 deletions
+2 -1
View File
@@ -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,
}
+26 -87
View File
@@ -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
View File
@@ -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/
+1 -1
View File
@@ -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
View File
@@ -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
+14 -8
View File
@@ -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/
+3 -3
View File
@@ -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
View File
@@ -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);
},
+19 -15
View File
@@ -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() {
+2 -3
View File
@@ -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;
+7 -11
View File
@@ -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 + '%');
}
});
+2 -3
View File
@@ -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'];
}