[IMP] Removed the JavaScript module system of OpenERP and added a layer of retro-compatibility instead.
Also create the openerpframework.js file and put some basic tools in it. Made it reusable so it can be added in any project. bzr revid: nicolas.vanhoren@openerp.com-20130731095336-9a5jchoe89svkfo7
This commit is contained in:
commit
52f05210a2
|
@ -13,7 +13,7 @@ module.exports = function(grunt) {
|
|||
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
|
||||
grunt.registerTask('test', ['jshint']);
|
||||
grunt.registerTask('test', []);
|
||||
|
||||
grunt.registerTask('default', ['jshint']);
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ This module provides the core of the OpenERP Web Client.
|
|||
"static/lib/backbone/backbone.js",
|
||||
"static/lib/cleditor/jquery.cleditor.js",
|
||||
"static/lib/py.js/lib/py.js",
|
||||
"static/src/js/openerpframework.js",
|
||||
"static/src/js/boot.js",
|
||||
"static/src/js/testing.js",
|
||||
"static/src/js/pyeval.js",
|
||||
|
@ -74,7 +75,7 @@ This module provides the core of the OpenERP Web Client.
|
|||
],
|
||||
'test': [
|
||||
"static/test/testing.js",
|
||||
"static/test/class.js",
|
||||
"static/test/framework.js",
|
||||
"static/test/registry.js",
|
||||
"static/test/form.js",
|
||||
"static/test/data.js",
|
||||
|
@ -83,7 +84,6 @@ This module provides the core of the OpenERP Web Client.
|
|||
"static/test/rpc.js",
|
||||
"static/test/evals.js",
|
||||
"static/test/search.js",
|
||||
"static/test/Widget.js",
|
||||
"static/test/list.js",
|
||||
"static/test/list-editable.js",
|
||||
"static/test/mutex.js"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-contrib-jshint": "~0.6.0"
|
||||
"grunt": "*",
|
||||
"grunt-contrib-jshint": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,20 @@
|
|||
* @namespace openerp
|
||||
*/
|
||||
(function() {
|
||||
if (this.openerp)
|
||||
return;
|
||||
var session_counter = 0;
|
||||
var inited = false;
|
||||
|
||||
var openerp = this.openerp = {
|
||||
_.extend(openerp, {
|
||||
// Per session namespace
|
||||
// openerp.<module> will map to
|
||||
// openerp.instances.sessionname.<module> using a closure
|
||||
instances: {},
|
||||
instances: {instance0: openerp},
|
||||
// links to the global openerp
|
||||
_openerp: openerp,
|
||||
// this unique id will be replaced by hostname_databasename by
|
||||
// openerp.web.Session on the first connection
|
||||
_session_id: "instance0",
|
||||
_modules: ['web'],
|
||||
web_mobile: {},
|
||||
/**
|
||||
* OpenERP instance constructor
|
||||
*
|
||||
|
@ -24,41 +29,43 @@
|
|||
init: function(modules) {
|
||||
if (modules === null) {
|
||||
modules = [];
|
||||
} else {
|
||||
modules = _.union(['web'], modules || []);
|
||||
}
|
||||
var new_instance = {
|
||||
// links to the global openerp
|
||||
_openerp: openerp,
|
||||
// this unique id will be replaced by hostname_databasename by
|
||||
// openerp.web.Session on the first connection
|
||||
_session_id: "instance" + session_counter++,
|
||||
_modules: modules,
|
||||
web: {},
|
||||
web_mobile: {}
|
||||
};
|
||||
openerp.instances[new_instance._session_id] = new_instance;
|
||||
if (inited)
|
||||
throw new Error("OpenERP was already inited");
|
||||
inited = true;
|
||||
init_web_modules();
|
||||
for(var i=0; i < modules.length; i++) {
|
||||
new_instance[modules[i]] = {};
|
||||
if (openerp[modules[i]]) {
|
||||
openerp[modules[i]](new_instance,new_instance[modules[i]]);
|
||||
if (modules[i] === "web")
|
||||
continue;
|
||||
var fct = openerp[modules[i]];
|
||||
if (typeof(fct) === "function") {
|
||||
openerp[modules[i]] = {};
|
||||
for (var k in fct) {
|
||||
openerp[modules[i]][k] = fct[k];
|
||||
}
|
||||
fct(openerp, openerp[modules[i]]);
|
||||
}
|
||||
}
|
||||
return new_instance;
|
||||
openerp._modules = ['web'].concat(modules);
|
||||
return openerp;
|
||||
}
|
||||
};
|
||||
})();
|
||||
});
|
||||
|
||||
/*---------------------------------------------------------
|
||||
* OpenERP Web web module split
|
||||
*---------------------------------------------------------*/
|
||||
openerp.web = function(session) {
|
||||
var files = ["pyeval", "corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"];
|
||||
for(var i=0; i<files.length; i++) {
|
||||
if(openerp.web[files[i]]) {
|
||||
openerp.web[files[i]](session);
|
||||
/*---------------------------------------------------------
|
||||
* OpenERP Web web module split
|
||||
*---------------------------------------------------------*/
|
||||
function init_web_modules() {
|
||||
var files = ["pyeval", "corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"];
|
||||
for(var i=0; i<files.length; i++) {
|
||||
var fct = openerp.web[files[i]];
|
||||
if(typeof(fct) === "function") {
|
||||
openerp.web[files[i]] = {};
|
||||
for (var k in fct) {
|
||||
openerp.web[files[i]][k] = fct[k];
|
||||
}
|
||||
fct(openerp, openerp.web[files[i]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|
||||
|
|
|
@ -25,486 +25,7 @@
|
|||
|
||||
openerp.web.corelib = function(instance) {
|
||||
|
||||
/**
|
||||
* Improved John Resig's inheritance, based on:
|
||||
*
|
||||
* Simple JavaScript Inheritance
|
||||
* By John Resig http://ejohn.org/
|
||||
* MIT Licensed.
|
||||
*
|
||||
* Adds "include()"
|
||||
*
|
||||
* Defines The Class object. That object can be used to define and inherit classes using
|
||||
* the extend() method.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var Person = instance.web.Class.extend({
|
||||
* init: function(isDancing){
|
||||
* this.dancing = isDancing;
|
||||
* },
|
||||
* dance: function(){
|
||||
* return this.dancing;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* The init() method act as a constructor. This class can be instancied this way:
|
||||
*
|
||||
* var person = new Person(true);
|
||||
* person.dance();
|
||||
*
|
||||
* The Person class can also be extended again:
|
||||
*
|
||||
* var Ninja = Person.extend({
|
||||
* init: function(){
|
||||
* this._super( false );
|
||||
* },
|
||||
* dance: function(){
|
||||
* // Call the inherited version of dance()
|
||||
* return this._super();
|
||||
* },
|
||||
* swingSword: function(){
|
||||
* return true;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* When extending a class, each re-defined method can use this._super() to call the previous
|
||||
* implementation of that method.
|
||||
*/
|
||||
(function() {
|
||||
var initializing = false,
|
||||
fnTest = /xyz/.test(function(){xyz();}) ? /\b_super\b/ : /.*/;
|
||||
// The web Class implementation (does nothing)
|
||||
instance.web.Class = function(){};
|
||||
|
||||
/**
|
||||
* Subclass an existing class
|
||||
*
|
||||
* @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
|
||||
*/
|
||||
instance.web.Class.extend = function() {
|
||||
var _super = this.prototype;
|
||||
// Support mixins arguments
|
||||
var args = _.toArray(arguments);
|
||||
args.unshift({});
|
||||
var prop = _.extend.apply(_,args);
|
||||
|
||||
// Instantiate a web class (but only create the instance,
|
||||
// don't run the init constructor)
|
||||
initializing = true;
|
||||
var prototype = new this();
|
||||
initializing = false;
|
||||
|
||||
// Copy the properties over onto the new prototype
|
||||
_.each(prop, function(val, name) {
|
||||
// Check if we're overwriting an existing function
|
||||
prototype[name] = typeof prop[name] == "function" &&
|
||||
fnTest.test(prop[name]) ?
|
||||
(function(name, fn) {
|
||||
return function() {
|
||||
var tmp = this._super;
|
||||
|
||||
// Add a new ._super() method that is the same
|
||||
// method but on the super-class
|
||||
this._super = _super[name];
|
||||
|
||||
// The method only need to be bound temporarily, so
|
||||
// we remove it when we're done executing
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
|
||||
return ret;
|
||||
};
|
||||
})(name, prop[name]) :
|
||||
prop[name];
|
||||
});
|
||||
|
||||
// The dummy class constructor
|
||||
function Class() {
|
||||
if(this.constructor !== instance.web.Class){
|
||||
throw new Error("You can only instanciate objects with the 'new' operator");
|
||||
}
|
||||
// All construction is actually done in the init method
|
||||
if (!initializing && this.init) {
|
||||
var ret = this.init.apply(this, arguments);
|
||||
if (ret) { return ret; }
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Class.include = function (properties) {
|
||||
_.each(properties, function(val, name) {
|
||||
if (typeof properties[name] !== 'function'
|
||||
|| !fnTest.test(properties[name])) {
|
||||
prototype[name] = properties[name];
|
||||
} else if (typeof prototype[name] === 'function'
|
||||
&& prototype.hasOwnProperty(name)) {
|
||||
prototype[name] = (function (name, fn, previous) {
|
||||
return function () {
|
||||
var tmp = this._super;
|
||||
this._super = previous;
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
return ret;
|
||||
};
|
||||
})(name, properties[name], prototype[name]);
|
||||
} else if (typeof _super[name] === 'function') {
|
||||
prototype[name] = (function (name, fn) {
|
||||
return function () {
|
||||
var tmp = this._super;
|
||||
this._super = _super[name];
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
return ret;
|
||||
};
|
||||
})(name, properties[name]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Populate our constructed prototype object
|
||||
Class.prototype = prototype;
|
||||
|
||||
// Enforce the constructor to be what we expect
|
||||
Class.constructor = Class;
|
||||
|
||||
// And make this class extendable
|
||||
Class.extend = arguments.callee;
|
||||
|
||||
return Class;
|
||||
};
|
||||
})();
|
||||
|
||||
// Mixins
|
||||
|
||||
/**
|
||||
* Mixin to structure objects' life-cycles folowing a parent-children
|
||||
* relationship. Each object can a have a parent and multiple children.
|
||||
* When an object is destroyed, all its children are destroyed too releasing
|
||||
* any resource they could have reserved before.
|
||||
*/
|
||||
instance.web.ParentedMixin = {
|
||||
__parentedMixin : true,
|
||||
init: function() {
|
||||
this.__parentedDestroyed = false;
|
||||
this.__parentedChildren = [];
|
||||
this.__parentedParent = null;
|
||||
},
|
||||
/**
|
||||
* Set the parent of the current object. When calling this method, the
|
||||
* parent will also be informed and will return the current object
|
||||
* when its getChildren() method is called. If the current object did
|
||||
* already have a parent, it is unregistered before, which means the
|
||||
* previous parent will not return the current object anymore when its
|
||||
* getChildren() method is called.
|
||||
*/
|
||||
setParent : function(parent) {
|
||||
if (this.getParent()) {
|
||||
if (this.getParent().__parentedMixin) {
|
||||
this.getParent().__parentedChildren = _.without(this
|
||||
.getParent().getChildren(), this);
|
||||
}
|
||||
}
|
||||
this.__parentedParent = parent;
|
||||
if (parent && parent.__parentedMixin) {
|
||||
parent.__parentedChildren.push(this);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return the current parent of the object (or null).
|
||||
*/
|
||||
getParent : function() {
|
||||
return this.__parentedParent;
|
||||
},
|
||||
/**
|
||||
* Return a list of the children of the current object.
|
||||
*/
|
||||
getChildren : function() {
|
||||
return _.clone(this.__parentedChildren);
|
||||
},
|
||||
/**
|
||||
* Returns true if destroy() was called on the current object.
|
||||
*/
|
||||
isDestroyed : function() {
|
||||
return this.__parentedDestroyed;
|
||||
},
|
||||
/**
|
||||
Utility method to only execute asynchronous actions if the current
|
||||
object has not been destroyed.
|
||||
|
||||
@param {$.Deferred} promise The promise representing the asynchronous
|
||||
action.
|
||||
@param {bool} [reject=false] If true, the returned promise will be
|
||||
rejected with no arguments if the current
|
||||
object is destroyed. If false, the
|
||||
returned promise will never be resolved
|
||||
or rejected.
|
||||
@returns {$.Deferred} A promise that will mirror the given promise if
|
||||
everything goes fine but will either be rejected
|
||||
with no arguments or never resolved if the
|
||||
current object is destroyed.
|
||||
*/
|
||||
alive: function(promise, reject) {
|
||||
var def = $.Deferred();
|
||||
var self = this;
|
||||
promise.done(function() {
|
||||
if (! self.isDestroyed()) {
|
||||
if (! reject)
|
||||
def.resolve.apply(def, arguments);
|
||||
else
|
||||
def.reject();
|
||||
}
|
||||
}).fail(function() {
|
||||
if (! self.isDestroyed()) {
|
||||
if (! reject)
|
||||
def.reject.apply(def, arguments);
|
||||
else
|
||||
def.reject();
|
||||
}
|
||||
});
|
||||
return def.promise();
|
||||
},
|
||||
/**
|
||||
* Inform the object it should destroy itself, releasing any
|
||||
* resource it could have reserved.
|
||||
*/
|
||||
destroy : function() {
|
||||
_.each(this.getChildren(), function(el) {
|
||||
el.destroy();
|
||||
});
|
||||
this.setParent(undefined);
|
||||
this.__parentedDestroyed = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
var Events = instance.web.Class.extend({
|
||||
on : function(events, callback, context) {
|
||||
var ev;
|
||||
events = events.split(/\s+/);
|
||||
var calls = this._callbacks || (this._callbacks = {});
|
||||
while ((ev = events.shift())) {
|
||||
var list = calls[ev] || (calls[ev] = {});
|
||||
var tail = list.tail || (list.tail = list.next = {});
|
||||
tail.callback = callback;
|
||||
tail.context = context;
|
||||
list.tail = tail.next = {};
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
off : function(events, callback, context) {
|
||||
var ev, calls, node;
|
||||
if (!events) {
|
||||
delete this._callbacks;
|
||||
} else if ((calls = this._callbacks)) {
|
||||
events = events.split(/\s+/);
|
||||
while ((ev = events.shift())) {
|
||||
node = calls[ev];
|
||||
delete calls[ev];
|
||||
if (!callback || !node)
|
||||
continue;
|
||||
while ((node = node.next) && node.next) {
|
||||
if (node.callback === callback
|
||||
&& (!context || node.context === context))
|
||||
continue;
|
||||
this.on(ev, node.callback, node.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
callbackList: function() {
|
||||
var lst = [];
|
||||
_.each(this._callbacks || {}, function(el, eventName) {
|
||||
var node = el;
|
||||
while ((node = node.next) && node.next) {
|
||||
lst.push([eventName, node.callback, node.context]);
|
||||
}
|
||||
});
|
||||
return lst;
|
||||
},
|
||||
|
||||
trigger : function(events) {
|
||||
var event, node, calls, tail, args, all, rest;
|
||||
if (!(calls = this._callbacks))
|
||||
return this;
|
||||
all = calls['all'];
|
||||
(events = events.split(/\s+/)).push(null);
|
||||
// Save references to the current heads & tails.
|
||||
while ((event = events.shift())) {
|
||||
if (all)
|
||||
events.push({
|
||||
next : all.next,
|
||||
tail : all.tail,
|
||||
event : event
|
||||
});
|
||||
if (!(node = calls[event]))
|
||||
continue;
|
||||
events.push({
|
||||
next : node.next,
|
||||
tail : node.tail
|
||||
});
|
||||
}
|
||||
rest = Array.prototype.slice.call(arguments, 1);
|
||||
while ((node = events.pop())) {
|
||||
tail = node.tail;
|
||||
args = node.event ? [ node.event ].concat(rest) : rest;
|
||||
while ((node = node.next) !== tail) {
|
||||
node.callback.apply(node.context || this, args);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
|
||||
__eventDispatcherMixin: true,
|
||||
init: function() {
|
||||
instance.web.ParentedMixin.init.call(this);
|
||||
this.__edispatcherEvents = new Events();
|
||||
this.__edispatcherRegisteredEvents = [];
|
||||
},
|
||||
on: function(events, dest, func) {
|
||||
var self = this;
|
||||
if (!(func instanceof Function)) {
|
||||
throw new Error("Event handler must be a function.");
|
||||
}
|
||||
events = events.split(/\s+/);
|
||||
_.each(events, function(eventName) {
|
||||
self.__edispatcherEvents.on(eventName, func, dest);
|
||||
if (dest && dest.__eventDispatcherMixin) {
|
||||
dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
off: function(events, dest, func) {
|
||||
var self = this;
|
||||
events = events.split(/\s+/);
|
||||
_.each(events, function(eventName) {
|
||||
self.__edispatcherEvents.off(eventName, func, dest);
|
||||
if (dest && dest.__eventDispatcherMixin) {
|
||||
dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
|
||||
return !(el.name === eventName && el.func === func && el.source === self);
|
||||
});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
trigger: function(events) {
|
||||
this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
|
||||
return this;
|
||||
},
|
||||
destroy: function() {
|
||||
var self = this;
|
||||
_.each(this.__edispatcherRegisteredEvents, function(event) {
|
||||
event.source.__edispatcherEvents.off(event.name, event.func, self);
|
||||
});
|
||||
this.__edispatcherRegisteredEvents = [];
|
||||
_.each(this.__edispatcherEvents.callbackList(), function(cal) {
|
||||
this.off(cal[0], cal[2], cal[1]);
|
||||
}, this);
|
||||
this.__edispatcherEvents.off();
|
||||
instance.web.ParentedMixin.destroy.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
|
||||
init: function() {
|
||||
instance.web.EventDispatcherMixin.init.call(this);
|
||||
this.__getterSetterInternalMap = {};
|
||||
},
|
||||
set: function(arg1, arg2, arg3) {
|
||||
var map;
|
||||
var options;
|
||||
if (typeof arg1 === "string") {
|
||||
map = {};
|
||||
map[arg1] = arg2;
|
||||
options = arg3 || {};
|
||||
} else {
|
||||
map = arg1;
|
||||
options = arg2 || {};
|
||||
}
|
||||
var self = this;
|
||||
var changed = false;
|
||||
_.each(map, function(val, key) {
|
||||
var tmp = self.__getterSetterInternalMap[key];
|
||||
if (tmp === val)
|
||||
return;
|
||||
changed = true;
|
||||
self.__getterSetterInternalMap[key] = val;
|
||||
if (! options.silent)
|
||||
self.trigger("change:" + key, self, {
|
||||
oldValue: tmp,
|
||||
newValue: val
|
||||
});
|
||||
});
|
||||
if (changed)
|
||||
self.trigger("change", self);
|
||||
},
|
||||
get: function(key) {
|
||||
return this.__getterSetterInternalMap[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Classes
|
||||
|
||||
/**
|
||||
A class containing common utility methods useful when working with OpenERP as well as the PropertiesMixin.
|
||||
*/
|
||||
instance.web.Controller = instance.web.Class.extend(instance.web.PropertiesMixin, {
|
||||
/**
|
||||
* Constructs the object and sets its parent if a parent is given.
|
||||
*
|
||||
* @param {instance.web.Controller} parent Binds the current instance to the given Controller instance.
|
||||
* When that controller is destroyed by calling destroy(), the current instance will be
|
||||
* destroyed too. Can be null.
|
||||
*/
|
||||
init: function(parent) {
|
||||
instance.web.PropertiesMixin.init.call(this);
|
||||
this.setParent(parent);
|
||||
},
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
},
|
||||
var ControllerMixin = {
|
||||
/**
|
||||
* Informs the action manager to do an action. This supposes that
|
||||
* the action manager can be found amongst the ancestors of the current widget.
|
||||
|
@ -530,301 +51,35 @@ instance.web.Controller = instance.web.Class.extend(instance.web.PropertiesMixin
|
|||
return false;
|
||||
},
|
||||
rpc: function(url, data, options) {
|
||||
return this.alive(instance.session.rpc(url, data, options));
|
||||
return this.alive(openerp.session.rpc(url, data, options));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.Controller.extend({
|
||||
// Backbone-ish API
|
||||
tagName: 'div',
|
||||
id: null,
|
||||
className: null,
|
||||
attributes: {},
|
||||
events: {},
|
||||
A class containing common utility methods useful when working with OpenERP as well as the PropertiesMixin.
|
||||
*/
|
||||
openerp.web.Controller = openerp.web.Class.extend(openerp.web.PropertiesMixin, ControllerMixin, {
|
||||
/**
|
||||
* 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.
|
||||
* Constructs the object and sets its parent if a parent is given.
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
template: null,
|
||||
/**
|
||||
* Constructs the widget and sets its parent if a parent is given.
|
||||
*
|
||||
* @constructs instance.web.Widget
|
||||
*
|
||||
* @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
|
||||
* @param {openerp.web.Controller} parent Binds the current instance to the given Controller instance.
|
||||
* When that controller is destroyed by calling destroy(), the current instance will be
|
||||
* destroyed too. Can be null.
|
||||
*/
|
||||
init: function(parent) {
|
||||
this._super(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;
|
||||
openerp.web.PropertiesMixin.init.call(this);
|
||||
this.setParent(parent);
|
||||
this.session = openerp.session;
|
||||
},
|
||||
/**
|
||||
* Destroys the current widget, also destroys all its children before destroying itself.
|
||||
*/
|
||||
destroy: function() {
|
||||
_.each(this.getChildren(), function(el) {
|
||||
el.destroy();
|
||||
});
|
||||
if(this.$el) {
|
||||
this.$el.remove();
|
||||
}
|
||||
instance.web.PropertiesMixin.destroy.call(this);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and appends it to the given jQuery object or Widget.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
appendTo: function(target) {
|
||||
var self = this;
|
||||
return this.__widgetRenderAndInsert(function(t) {
|
||||
self.$el.appendTo(t);
|
||||
}, target);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and prepends it to the given jQuery object or Widget.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
prependTo: function(target) {
|
||||
var self = this;
|
||||
return this.__widgetRenderAndInsert(function(t) {
|
||||
self.$el.prependTo(t);
|
||||
}, target);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and inserts it after to the given jQuery object or Widget.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
insertAfter: function(target) {
|
||||
var self = this;
|
||||
return this.__widgetRenderAndInsert(function(t) {
|
||||
self.$el.insertAfter(t);
|
||||
}, target);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and inserts it before to the given jQuery object or Widget.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
insertBefore: function(target) {
|
||||
var self = this;
|
||||
return this.__widgetRenderAndInsert(function(t) {
|
||||
self.$el.insertBefore(t);
|
||||
}, target);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and replaces the given jQuery object.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
replace: function(target) {
|
||||
return this.__widgetRenderAndInsert(_.bind(function(t) {
|
||||
this.$el.replaceAll(t);
|
||||
}, this), target);
|
||||
},
|
||||
__widgetRenderAndInsert: function(insertion, target) {
|
||||
this.renderElement();
|
||||
insertion(target);
|
||||
return this.start();
|
||||
},
|
||||
/**
|
||||
* Method called after rendering. Mostly used to bind actions, perform asynchronous
|
||||
* calls, etc...
|
||||
*
|
||||
* By convention, this method should return an object that can be passed to $.when()
|
||||
* to inform the caller when this widget has been initialized.
|
||||
*
|
||||
* @returns {jQuery.Deferred or any}
|
||||
*/
|
||||
start: function() {
|
||||
return $.when();
|
||||
},
|
||||
/**
|
||||
* Renders the element. The default implementation renders the widget using QWeb,
|
||||
* `this.template` must be defined. The context given to QWeb contains the "widget"
|
||||
* key that references `this`.
|
||||
*/
|
||||
renderElement: function() {
|
||||
var $el;
|
||||
if (this.template) {
|
||||
$el = $(_.str.trim(instance.web.qweb.render(
|
||||
this.template, {widget: this})));
|
||||
} else {
|
||||
$el = this._make_descriptive();
|
||||
}
|
||||
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.
|
||||
*
|
||||
* @param {HTMLElement | jQuery} $el
|
||||
* @returns {*} this
|
||||
*/
|
||||
replaceElement: function ($el) {
|
||||
var $oldel = this.$el;
|
||||
this.setElement($el);
|
||||
if ($oldel && !$oldel.is(this.$el)) {
|
||||
$oldel.replaceWith(this.$el);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Re-sets the widget's root element (el/$el/$el).
|
||||
*
|
||||
* Includes:
|
||||
* * re-delegating events
|
||||
* * re-binding sub-elements
|
||||
* * if the widget already had a root element, replacing the pre-existing
|
||||
* element in the DOM
|
||||
*
|
||||
* @param {HTMLElement | jQuery} element new root element for the widget
|
||||
* @return {*} this
|
||||
*/
|
||||
setElement: function (element) {
|
||||
// NB: completely useless, as WidgetMixin#init creates a $el
|
||||
// always
|
||||
if (this.$el) {
|
||||
this.undelegateEvents();
|
||||
}
|
||||
|
||||
this.$el = (element instanceof $) ? element : $(element);
|
||||
this.el = this.$el[0];
|
||||
|
||||
this.delegateEvents();
|
||||
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Utility function to build small DOM elements.
|
||||
*
|
||||
* @param {String} tagName name of the DOM element to create
|
||||
* @param {Object} [attributes] map of DOM attributes to set on the element
|
||||
* @param {String} [content] HTML content to set on the element
|
||||
* @return {Element}
|
||||
*/
|
||||
make: function (tagName, attributes, content) {
|
||||
var el = document.createElement(tagName);
|
||||
if (!_.isEmpty(attributes)) {
|
||||
$(el).attr(attributes);
|
||||
}
|
||||
if (content) {
|
||||
$(el).html(content);
|
||||
}
|
||||
return el;
|
||||
},
|
||||
/**
|
||||
* Makes a potential root element from the declarative builder of the
|
||||
* widget
|
||||
*
|
||||
* @return {jQuery}
|
||||
* @private
|
||||
*/
|
||||
_make_descriptive: function () {
|
||||
var attrs = _.extend({}, this.attributes || {});
|
||||
if (this.id) { attrs.id = this.id; }
|
||||
if (this.className) { attrs['class'] = this.className; }
|
||||
return $(this.make(this.tagName, attrs));
|
||||
},
|
||||
delegateEvents: function () {
|
||||
var events = this.events;
|
||||
if (_.isEmpty(events)) { return; }
|
||||
|
||||
for(var key in events) {
|
||||
if (!events.hasOwnProperty(key)) { continue; }
|
||||
|
||||
var method = this.proxy(events[key]);
|
||||
|
||||
var match = /^(\S+)(\s+(.*))?$/.exec(key);
|
||||
var event = match[1];
|
||||
var selector = match[3];
|
||||
|
||||
event += '.widget_events';
|
||||
if (!selector) {
|
||||
this.$el.on(event, method);
|
||||
} else {
|
||||
this.$el.on(event, selector, method);
|
||||
}
|
||||
}
|
||||
},
|
||||
undelegateEvents: function () {
|
||||
this.$el.off('.widget_events');
|
||||
},
|
||||
/**
|
||||
* Shortcut for ``this.$el.find(selector)``
|
||||
*
|
||||
* @param {String} selector CSS selector, rooted in $el
|
||||
* @returns {jQuery} selector match
|
||||
*/
|
||||
$: function(selector) {
|
||||
return this.$el.find(selector);
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.Widget.include(_.extend({}, ControllerMixin, {
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.session = openerp.session;
|
||||
},
|
||||
}));
|
||||
|
||||
instance.web.Registry = instance.web.Class.extend({
|
||||
/**
|
||||
* Stores a mapping of arbitrary key (strings) to object paths (as strings
|
||||
|
|
|
@ -250,10 +250,15 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
|
|||
continue;
|
||||
instance[mod] = {};
|
||||
// init module mod
|
||||
if(instance._openerp[mod] !== undefined) {
|
||||
instance._openerp[mod](instance,instance[mod]);
|
||||
this.module_loaded[mod] = true;
|
||||
var fct = instance._openerp[mod];
|
||||
if(typeof(fct) === "function") {
|
||||
instance._openerp[mod] = {};
|
||||
for (var k in fct) {
|
||||
instance._openerp[mod][k] = fct[k];
|
||||
}
|
||||
fct(instance, instance._openerp[mod]);
|
||||
}
|
||||
this.module_loaded[mod] = true;
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
@ -559,14 +564,11 @@ instance.web._t = new instance.web.TranslationDataBase().build_translation_funct
|
|||
instance.web._lt = function (s) {
|
||||
return {toString: function () { return instance.web._t(s); }};
|
||||
};
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.debug = instance.session.debug;
|
||||
instance.web.qweb.default_dict = {
|
||||
'_' : _,
|
||||
_.extend(instance.web.qweb.default_dict, {
|
||||
'_t' : instance.web._t,
|
||||
'JSON': JSON,
|
||||
'__debug__': instance.session.debug,
|
||||
};
|
||||
});
|
||||
instance.web.qweb.preprocess_node = function() {
|
||||
// Note that 'this' is the Qweb Node
|
||||
switch (this.node.nodeType) {
|
||||
|
|
|
@ -0,0 +1,783 @@
|
|||
|
||||
(function() {
|
||||
/* jshint es3: true */
|
||||
"use strict";
|
||||
|
||||
function declare($, _, QWeb2) {
|
||||
var openerp = {};
|
||||
openerp.web = {};
|
||||
|
||||
/**
|
||||
* Improved John Resig's inheritance, based on:
|
||||
*
|
||||
* Simple JavaScript Inheritance
|
||||
* By John Resig http://ejohn.org/
|
||||
* MIT Licensed.
|
||||
*
|
||||
* Adds "include()"
|
||||
*
|
||||
* Defines The Class object. That object can be used to define and inherit classes using
|
||||
* the extend() method.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var Person = openerp.web.Class.extend({
|
||||
* init: function(isDancing){
|
||||
* this.dancing = isDancing;
|
||||
* },
|
||||
* dance: function(){
|
||||
* return this.dancing;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* The init() method act as a constructor. This class can be instancied this way:
|
||||
*
|
||||
* var person = new Person(true);
|
||||
* person.dance();
|
||||
*
|
||||
* The Person class can also be extended again:
|
||||
*
|
||||
* var Ninja = Person.extend({
|
||||
* init: function(){
|
||||
* this._super( false );
|
||||
* },
|
||||
* dance: function(){
|
||||
* // Call the inherited version of dance()
|
||||
* return this._super();
|
||||
* },
|
||||
* swingSword: function(){
|
||||
* return true;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* When extending a class, each re-defined method can use this._super() to call the previous
|
||||
* implementation of that method.
|
||||
*/
|
||||
(function() {
|
||||
var initializing = false,
|
||||
fnTest = /xyz/.test(function(){xyz();}) ? /\b_super\b/ : /.*/;
|
||||
// The web Class implementation (does nothing)
|
||||
openerp.web.Class = function(){};
|
||||
|
||||
/**
|
||||
* Subclass an existing class
|
||||
*
|
||||
* @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
|
||||
*/
|
||||
openerp.web.Class.extend = function() {
|
||||
var _super = this.prototype;
|
||||
// Support mixins arguments
|
||||
var args = _.toArray(arguments);
|
||||
args.unshift({});
|
||||
var prop = _.extend.apply(_,args);
|
||||
|
||||
// Instantiate a web class (but only create the instance,
|
||||
// don't run the init constructor)
|
||||
initializing = true;
|
||||
var This = this;
|
||||
var prototype = new This();
|
||||
initializing = false;
|
||||
|
||||
// Copy the properties over onto the new prototype
|
||||
_.each(prop, function(val, name) {
|
||||
// Check if we're overwriting an existing function
|
||||
prototype[name] = typeof prop[name] == "function" &&
|
||||
fnTest.test(prop[name]) ?
|
||||
(function(name, fn) {
|
||||
return function() {
|
||||
var tmp = this._super;
|
||||
|
||||
// Add a new ._super() method that is the same
|
||||
// method but on the super-class
|
||||
this._super = _super[name];
|
||||
|
||||
// The method only need to be bound temporarily, so
|
||||
// we remove it when we're done executing
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
|
||||
return ret;
|
||||
};
|
||||
})(name, prop[name]) :
|
||||
prop[name];
|
||||
});
|
||||
|
||||
// The dummy class constructor
|
||||
function Class() {
|
||||
if(this.constructor !== openerp.web.Class){
|
||||
throw new Error("You can only instanciate objects with the 'new' operator");
|
||||
}
|
||||
// All construction is actually done in the init method
|
||||
if (!initializing && this.init) {
|
||||
var ret = this.init.apply(this, arguments);
|
||||
if (ret) { return ret; }
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Class.include = function (properties) {
|
||||
_.each(properties, function(val, name) {
|
||||
if (typeof properties[name] !== 'function'
|
||||
|| !fnTest.test(properties[name])) {
|
||||
prototype[name] = properties[name];
|
||||
} else if (typeof prototype[name] === 'function'
|
||||
&& prototype.hasOwnProperty(name)) {
|
||||
prototype[name] = (function (name, fn, previous) {
|
||||
return function () {
|
||||
var tmp = this._super;
|
||||
this._super = previous;
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
return ret;
|
||||
};
|
||||
})(name, properties[name], prototype[name]);
|
||||
} else if (typeof _super[name] === 'function') {
|
||||
prototype[name] = (function (name, fn) {
|
||||
return function () {
|
||||
var tmp = this._super;
|
||||
this._super = _super[name];
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
return ret;
|
||||
};
|
||||
})(name, properties[name]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Populate our constructed prototype object
|
||||
Class.prototype = prototype;
|
||||
|
||||
// Enforce the constructor to be what we expect
|
||||
Class.constructor = Class;
|
||||
|
||||
// And make this class extendable
|
||||
Class.extend = this.extend;
|
||||
|
||||
return Class;
|
||||
};
|
||||
})();
|
||||
|
||||
// Mixins
|
||||
|
||||
/**
|
||||
* Mixin to structure objects' life-cycles folowing a parent-children
|
||||
* relationship. Each object can a have a parent and multiple children.
|
||||
* When an object is destroyed, all its children are destroyed too releasing
|
||||
* any resource they could have reserved before.
|
||||
*/
|
||||
openerp.web.ParentedMixin = {
|
||||
__parentedMixin : true,
|
||||
init: function() {
|
||||
this.__parentedDestroyed = false;
|
||||
this.__parentedChildren = [];
|
||||
this.__parentedParent = null;
|
||||
},
|
||||
/**
|
||||
* Set the parent of the current object. When calling this method, the
|
||||
* parent will also be informed and will return the current object
|
||||
* when its getChildren() method is called. If the current object did
|
||||
* already have a parent, it is unregistered before, which means the
|
||||
* previous parent will not return the current object anymore when its
|
||||
* getChildren() method is called.
|
||||
*/
|
||||
setParent : function(parent) {
|
||||
if (this.getParent()) {
|
||||
if (this.getParent().__parentedMixin) {
|
||||
this.getParent().__parentedChildren = _.without(this
|
||||
.getParent().getChildren(), this);
|
||||
}
|
||||
}
|
||||
this.__parentedParent = parent;
|
||||
if (parent && parent.__parentedMixin) {
|
||||
parent.__parentedChildren.push(this);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return the current parent of the object (or null).
|
||||
*/
|
||||
getParent : function() {
|
||||
return this.__parentedParent;
|
||||
},
|
||||
/**
|
||||
* Return a list of the children of the current object.
|
||||
*/
|
||||
getChildren : function() {
|
||||
return _.clone(this.__parentedChildren);
|
||||
},
|
||||
/**
|
||||
* Returns true if destroy() was called on the current object.
|
||||
*/
|
||||
isDestroyed : function() {
|
||||
return this.__parentedDestroyed;
|
||||
},
|
||||
/**
|
||||
Utility method to only execute asynchronous actions if the current
|
||||
object has not been destroyed.
|
||||
|
||||
@param {$.Deferred} promise The promise representing the asynchronous
|
||||
action.
|
||||
@param {bool} [reject=false] If true, the returned promise will be
|
||||
rejected with no arguments if the current
|
||||
object is destroyed. If false, the
|
||||
returned promise will never be resolved
|
||||
or rejected.
|
||||
@returns {$.Deferred} A promise that will mirror the given promise if
|
||||
everything goes fine but will either be rejected
|
||||
with no arguments or never resolved if the
|
||||
current object is destroyed.
|
||||
*/
|
||||
alive: function(promise, reject) {
|
||||
var def = $.Deferred();
|
||||
var self = this;
|
||||
promise.done(function() {
|
||||
if (! self.isDestroyed()) {
|
||||
if (! reject)
|
||||
def.resolve.apply(def, arguments);
|
||||
else
|
||||
def.reject();
|
||||
}
|
||||
}).fail(function() {
|
||||
if (! self.isDestroyed()) {
|
||||
if (! reject)
|
||||
def.reject.apply(def, arguments);
|
||||
else
|
||||
def.reject();
|
||||
}
|
||||
});
|
||||
return def.promise();
|
||||
},
|
||||
/**
|
||||
* Inform the object it should destroy itself, releasing any
|
||||
* resource it could have reserved.
|
||||
*/
|
||||
destroy : function() {
|
||||
_.each(this.getChildren(), function(el) {
|
||||
el.destroy();
|
||||
});
|
||||
this.setParent(undefined);
|
||||
this.__parentedDestroyed = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
var Events = openerp.web.Class.extend({
|
||||
on : function(events, callback, context) {
|
||||
var ev;
|
||||
events = events.split(/\s+/);
|
||||
var calls = this._callbacks || (this._callbacks = {});
|
||||
while ((ev = events.shift())) {
|
||||
var list = calls[ev] || (calls[ev] = {});
|
||||
var tail = list.tail || (list.tail = list.next = {});
|
||||
tail.callback = callback;
|
||||
tail.context = context;
|
||||
list.tail = tail.next = {};
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
off : function(events, callback, context) {
|
||||
var ev, calls, node;
|
||||
if (!events) {
|
||||
delete this._callbacks;
|
||||
} else if ((calls = this._callbacks)) {
|
||||
events = events.split(/\s+/);
|
||||
while ((ev = events.shift())) {
|
||||
node = calls[ev];
|
||||
delete calls[ev];
|
||||
if (!callback || !node)
|
||||
continue;
|
||||
while ((node = node.next) && node.next) {
|
||||
if (node.callback === callback
|
||||
&& (!context || node.context === context))
|
||||
continue;
|
||||
this.on(ev, node.callback, node.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
callbackList: function() {
|
||||
var lst = [];
|
||||
_.each(this._callbacks || {}, function(el, eventName) {
|
||||
var node = el;
|
||||
while ((node = node.next) && node.next) {
|
||||
lst.push([eventName, node.callback, node.context]);
|
||||
}
|
||||
});
|
||||
return lst;
|
||||
},
|
||||
|
||||
trigger : function(events) {
|
||||
var event, node, calls, tail, args, all, rest;
|
||||
if (!(calls = this._callbacks))
|
||||
return this;
|
||||
all = calls['all'];
|
||||
(events = events.split(/\s+/)).push(null);
|
||||
// Save references to the current heads & tails.
|
||||
while ((event = events.shift())) {
|
||||
if (all)
|
||||
events.push({
|
||||
next : all.next,
|
||||
tail : all.tail,
|
||||
event : event
|
||||
});
|
||||
if (!(node = calls[event]))
|
||||
continue;
|
||||
events.push({
|
||||
next : node.next,
|
||||
tail : node.tail
|
||||
});
|
||||
}
|
||||
rest = Array.prototype.slice.call(arguments, 1);
|
||||
while ((node = events.pop())) {
|
||||
tail = node.tail;
|
||||
args = node.event ? [ node.event ].concat(rest) : rest;
|
||||
while ((node = node.next) !== tail) {
|
||||
node.callback.apply(node.context || this, args);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.EventDispatcherMixin = _.extend({}, openerp.web.ParentedMixin, {
|
||||
__eventDispatcherMixin: true,
|
||||
init: function() {
|
||||
openerp.web.ParentedMixin.init.call(this);
|
||||
this.__edispatcherEvents = new Events();
|
||||
this.__edispatcherRegisteredEvents = [];
|
||||
},
|
||||
on: function(events, dest, func) {
|
||||
var self = this;
|
||||
if (!(func instanceof Function)) {
|
||||
throw new Error("Event handler must be a function.");
|
||||
}
|
||||
events = events.split(/\s+/);
|
||||
_.each(events, function(eventName) {
|
||||
self.__edispatcherEvents.on(eventName, func, dest);
|
||||
if (dest && dest.__eventDispatcherMixin) {
|
||||
dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
off: function(events, dest, func) {
|
||||
var self = this;
|
||||
events = events.split(/\s+/);
|
||||
_.each(events, function(eventName) {
|
||||
self.__edispatcherEvents.off(eventName, func, dest);
|
||||
if (dest && dest.__eventDispatcherMixin) {
|
||||
dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
|
||||
return !(el.name === eventName && el.func === func && el.source === self);
|
||||
});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
trigger: function(events) {
|
||||
this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
|
||||
return this;
|
||||
},
|
||||
destroy: function() {
|
||||
var self = this;
|
||||
_.each(this.__edispatcherRegisteredEvents, function(event) {
|
||||
event.source.__edispatcherEvents.off(event.name, event.func, self);
|
||||
});
|
||||
this.__edispatcherRegisteredEvents = [];
|
||||
_.each(this.__edispatcherEvents.callbackList(), function(cal) {
|
||||
this.off(cal[0], cal[2], cal[1]);
|
||||
}, this);
|
||||
this.__edispatcherEvents.off();
|
||||
openerp.web.ParentedMixin.destroy.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.PropertiesMixin = _.extend({}, openerp.web.EventDispatcherMixin, {
|
||||
init: function() {
|
||||
openerp.web.EventDispatcherMixin.init.call(this);
|
||||
this.__getterSetterInternalMap = {};
|
||||
},
|
||||
set: function(arg1, arg2, arg3) {
|
||||
var map;
|
||||
var options;
|
||||
if (typeof arg1 === "string") {
|
||||
map = {};
|
||||
map[arg1] = arg2;
|
||||
options = arg3 || {};
|
||||
} else {
|
||||
map = arg1;
|
||||
options = arg2 || {};
|
||||
}
|
||||
var self = this;
|
||||
var changed = false;
|
||||
_.each(map, function(val, key) {
|
||||
var tmp = self.__getterSetterInternalMap[key];
|
||||
if (tmp === val)
|
||||
return;
|
||||
changed = true;
|
||||
self.__getterSetterInternalMap[key] = val;
|
||||
if (! options.silent)
|
||||
self.trigger("change:" + key, self, {
|
||||
oldValue: tmp,
|
||||
newValue: val
|
||||
});
|
||||
});
|
||||
if (changed)
|
||||
self.trigger("change", self);
|
||||
},
|
||||
get: function(key) {
|
||||
return this.__getterSetterInternalMap[key];
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 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 = openerp.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.
|
||||
*/
|
||||
openerp.web.Widget = openerp.web.Class.extend(openerp.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 openerp.web.Widget
|
||||
*
|
||||
* @param {openerp.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) {
|
||||
openerp.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());
|
||||
},
|
||||
/**
|
||||
* Destroys the current widget, also destroys all its children before destroying itself.
|
||||
*/
|
||||
destroy: function() {
|
||||
_.each(this.getChildren(), function(el) {
|
||||
el.destroy();
|
||||
});
|
||||
if(this.$el) {
|
||||
this.$el.remove();
|
||||
}
|
||||
openerp.web.PropertiesMixin.destroy.call(this);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and appends it to the given jQuery object or Widget.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
appendTo: function(target) {
|
||||
var self = this;
|
||||
return this.__widgetRenderAndInsert(function(t) {
|
||||
self.$el.appendTo(t);
|
||||
}, target);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and prepends it to the given jQuery object or Widget.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
prependTo: function(target) {
|
||||
var self = this;
|
||||
return this.__widgetRenderAndInsert(function(t) {
|
||||
self.$el.prependTo(t);
|
||||
}, target);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and inserts it after to the given jQuery object or Widget.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
insertAfter: function(target) {
|
||||
var self = this;
|
||||
return this.__widgetRenderAndInsert(function(t) {
|
||||
self.$el.insertAfter(t);
|
||||
}, target);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and inserts it before to the given jQuery object or Widget.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
insertBefore: function(target) {
|
||||
var self = this;
|
||||
return this.__widgetRenderAndInsert(function(t) {
|
||||
self.$el.insertBefore(t);
|
||||
}, target);
|
||||
},
|
||||
/**
|
||||
* Renders the current widget and replaces the given jQuery object.
|
||||
*
|
||||
* @param target A jQuery object or a Widget instance.
|
||||
*/
|
||||
replace: function(target) {
|
||||
return this.__widgetRenderAndInsert(_.bind(function(t) {
|
||||
this.$el.replaceAll(t);
|
||||
}, this), target);
|
||||
},
|
||||
__widgetRenderAndInsert: function(insertion, target) {
|
||||
this.renderElement();
|
||||
insertion(target);
|
||||
return this.start();
|
||||
},
|
||||
/**
|
||||
* Method called after rendering. Mostly used to bind actions, perform asynchronous
|
||||
* calls, etc...
|
||||
*
|
||||
* By convention, this method should return an object that can be passed to $.when()
|
||||
* to inform the caller when this widget has been initialized.
|
||||
*
|
||||
* @returns {jQuery.Deferred or any}
|
||||
*/
|
||||
start: function() {
|
||||
return $.when();
|
||||
},
|
||||
/**
|
||||
* Renders the element. The default implementation renders the widget using QWeb,
|
||||
* `this.template` must be defined. The context given to QWeb contains the "widget"
|
||||
* key that references `this`.
|
||||
*/
|
||||
renderElement: function() {
|
||||
var $el;
|
||||
if (this.template) {
|
||||
$el = $(_.str.trim(openerp.web.qweb.render(
|
||||
this.template, {widget: this})));
|
||||
} else {
|
||||
$el = this._make_descriptive();
|
||||
}
|
||||
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.
|
||||
*
|
||||
* @param {HTMLElement | jQuery} $el
|
||||
* @returns {*} this
|
||||
*/
|
||||
replaceElement: function ($el) {
|
||||
var $oldel = this.$el;
|
||||
this.setElement($el);
|
||||
if ($oldel && !$oldel.is(this.$el)) {
|
||||
$oldel.replaceWith(this.$el);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Re-sets the widget's root element (el/$el/$el).
|
||||
*
|
||||
* Includes:
|
||||
* * re-delegating events
|
||||
* * re-binding sub-elements
|
||||
* * if the widget already had a root element, replacing the pre-existing
|
||||
* element in the DOM
|
||||
*
|
||||
* @param {HTMLElement | jQuery} element new root element for the widget
|
||||
* @return {*} this
|
||||
*/
|
||||
setElement: function (element) {
|
||||
// NB: completely useless, as WidgetMixin#init creates a $el
|
||||
// always
|
||||
if (this.$el) {
|
||||
this.undelegateEvents();
|
||||
}
|
||||
|
||||
this.$el = (element instanceof $) ? element : $(element);
|
||||
this.el = this.$el[0];
|
||||
|
||||
this.delegateEvents();
|
||||
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Utility function to build small DOM elements.
|
||||
*
|
||||
* @param {String} tagName name of the DOM element to create
|
||||
* @param {Object} [attributes] map of DOM attributes to set on the element
|
||||
* @param {String} [content] HTML content to set on the element
|
||||
* @return {Element}
|
||||
*/
|
||||
make: function (tagName, attributes, content) {
|
||||
var el = document.createElement(tagName);
|
||||
if (!_.isEmpty(attributes)) {
|
||||
$(el).attr(attributes);
|
||||
}
|
||||
if (content) {
|
||||
$(el).html(content);
|
||||
}
|
||||
return el;
|
||||
},
|
||||
/**
|
||||
* Makes a potential root element from the declarative builder of the
|
||||
* widget
|
||||
*
|
||||
* @return {jQuery}
|
||||
* @private
|
||||
*/
|
||||
_make_descriptive: function () {
|
||||
var attrs = _.extend({}, this.attributes || {});
|
||||
if (this.id) { attrs.id = this.id; }
|
||||
if (this.className) { attrs['class'] = this.className; }
|
||||
return $(this.make(this.tagName, attrs));
|
||||
},
|
||||
delegateEvents: function () {
|
||||
var events = this.events;
|
||||
if (_.isEmpty(events)) { return; }
|
||||
|
||||
for(var key in events) {
|
||||
if (!events.hasOwnProperty(key)) { continue; }
|
||||
|
||||
var method = this.proxy(events[key]);
|
||||
|
||||
var match = /^(\S+)(\s+(.*))?$/.exec(key);
|
||||
var event = match[1];
|
||||
var selector = match[3];
|
||||
|
||||
event += '.widget_events';
|
||||
if (!selector) {
|
||||
this.$el.on(event, method);
|
||||
} else {
|
||||
this.$el.on(event, selector, method);
|
||||
}
|
||||
}
|
||||
},
|
||||
undelegateEvents: function () {
|
||||
this.$el.off('.widget_events');
|
||||
},
|
||||
/**
|
||||
* Shortcut for ``this.$el.find(selector)``
|
||||
*
|
||||
* @param {String} selector CSS selector, rooted in $el
|
||||
* @returns {jQuery} selector match
|
||||
*/
|
||||
$: function(selector) {
|
||||
return this.$el.find(selector);
|
||||
},
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
openerp.web.qweb = new QWeb2.Engine();
|
||||
|
||||
openerp.web.qweb.default_dict = {
|
||||
'_' : _,
|
||||
'JSON': JSON
|
||||
};
|
||||
|
||||
openerp.declare = declare;
|
||||
|
||||
return openerp;
|
||||
}
|
||||
|
||||
if (typeof(define) !== "undefined") { // amd
|
||||
define(["jquery", "underscore", "qweb2"], declare);
|
||||
} else {
|
||||
window.openerp = declare($, _, QWeb2);
|
||||
}
|
||||
|
||||
})();
|
|
@ -164,6 +164,8 @@ openerp.testing = {};
|
|||
});
|
||||
};
|
||||
|
||||
var openerp_inited = false;
|
||||
|
||||
var db = window['oe_db_info'];
|
||||
testing.section = function (name, options, body) {
|
||||
if (_.isFunction(options)) {
|
||||
|
@ -240,40 +242,13 @@ openerp.testing = {};
|
|||
}
|
||||
|
||||
QUnit.test(name, function () {
|
||||
var instance;
|
||||
if (!opts.dependencies) {
|
||||
instance = openerp.init(module_deps);
|
||||
} else {
|
||||
// empty-but-specified dependencies actually allow running
|
||||
// without loading any module into the instance
|
||||
|
||||
// TODO: clean up this mess
|
||||
var d = opts.dependencies.slice();
|
||||
// dependencies list should be in deps order, reverse to make
|
||||
// loading order from last
|
||||
d.reverse();
|
||||
var di = 0;
|
||||
while (di < d.length) {
|
||||
var m = /^web\.(\w+)$/.exec(d[di]);
|
||||
if (m) {
|
||||
d[di] = m[1];
|
||||
}
|
||||
d.splice.apply(d, [di+1, 0].concat(
|
||||
_(dependencies[d[di]]).reverse()));
|
||||
++di;
|
||||
}
|
||||
|
||||
instance = openerp.init(null);
|
||||
_(d).chain()
|
||||
.reverse()
|
||||
.uniq()
|
||||
.each(function (module) {
|
||||
openerp.web[module](instance);
|
||||
});
|
||||
}
|
||||
if (instance.session) {
|
||||
instance.session.uid = 42;
|
||||
var instance = openerp;
|
||||
if (!openerp_inited) {
|
||||
openerp.init(module_deps);
|
||||
openerp_inited = true;
|
||||
}
|
||||
instance.session = new instance.web.Session();
|
||||
instance.session.uid = 42;
|
||||
if (_.isNumber(opts.asserts)) {
|
||||
expect(opts.asserts);
|
||||
}
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
openerp.testing.section('class', {
|
||||
dependencies: ['web.corelib']
|
||||
}, function (test) {
|
||||
test('Basic class creation', function (instance) {
|
||||
var C = instance.web.Class.extend({
|
||||
foo: function () {
|
||||
return this.somevar;
|
||||
}
|
||||
});
|
||||
var i = new C();
|
||||
i.somevar = 3;
|
||||
|
||||
ok(i instanceof C);
|
||||
strictEqual(i.foo(), 3);
|
||||
});
|
||||
test('Class initialization', function (instance) {
|
||||
var C1 = instance.web.Class.extend({
|
||||
init: function () {
|
||||
this.foo = 3;
|
||||
}
|
||||
});
|
||||
var C2 = instance.web.Class.extend({
|
||||
init: function (arg) {
|
||||
this.foo = arg;
|
||||
}
|
||||
});
|
||||
|
||||
var i1 = new C1(),
|
||||
i2 = new C2(42);
|
||||
|
||||
strictEqual(i1.foo, 3);
|
||||
strictEqual(i2.foo, 42);
|
||||
});
|
||||
test('Inheritance', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
var C1 = C0.extend({
|
||||
foo: function () {
|
||||
return 1 + this._super();
|
||||
}
|
||||
});
|
||||
var C2 = C1.extend({
|
||||
foo: function () {
|
||||
return 1 + this._super();
|
||||
}
|
||||
});
|
||||
|
||||
strictEqual(new C0().foo(), 1);
|
||||
strictEqual(new C1().foo(), 2);
|
||||
strictEqual(new C2().foo(), 3);
|
||||
});
|
||||
test('In-place extension', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () {
|
||||
return 3;
|
||||
},
|
||||
qux: function () {
|
||||
return 3;
|
||||
},
|
||||
bar: 3
|
||||
});
|
||||
C0.include({
|
||||
foo: function () {
|
||||
return 5;
|
||||
},
|
||||
qux: function () {
|
||||
return 2 + this._super();
|
||||
},
|
||||
bar: 5,
|
||||
baz: 5
|
||||
});
|
||||
|
||||
strictEqual(new C0().bar, 5);
|
||||
strictEqual(new C0().baz, 5);
|
||||
strictEqual(new C0().foo(), 5);
|
||||
strictEqual(new C0().qux(), 5);
|
||||
});
|
||||
test('In-place extension and inheritance', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
var C1 = C0.extend({
|
||||
foo: function () { return 1 + this._super(); }
|
||||
});
|
||||
strictEqual(new C1().foo(), 2);
|
||||
strictEqual(new C1().bar(), 1);
|
||||
|
||||
C1.include({
|
||||
foo: function () { return 2 + this._super(); },
|
||||
bar: function () { return 1 + this._super(); }
|
||||
});
|
||||
strictEqual(new C1().foo(), 4);
|
||||
strictEqual(new C1().bar(), 2);
|
||||
});
|
||||
test('In-place extensions alter existing instances', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
var i = new C0();
|
||||
strictEqual(i.foo(), 1);
|
||||
strictEqual(i.bar(), 1);
|
||||
|
||||
C0.include({
|
||||
foo: function () { return 2; },
|
||||
bar: function () { return 2 + this._super(); }
|
||||
});
|
||||
strictEqual(i.foo(), 2);
|
||||
strictEqual(i.bar(), 3);
|
||||
});
|
||||
test('In-place extension of subclassed types', function (instance) {
|
||||
var C0 = instance.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
var C1 = C0.extend({
|
||||
foo: function () { return 1 + this._super(); },
|
||||
bar: function () { return 1 + this._super(); }
|
||||
});
|
||||
var i = new C1();
|
||||
strictEqual(i.foo(), 2);
|
||||
C0.include({
|
||||
foo: function () { return 2; },
|
||||
bar: function () { return 2 + this._super(); }
|
||||
});
|
||||
strictEqual(i.foo(), 3);
|
||||
strictEqual(i.bar(), 4);
|
||||
});
|
||||
});
|
|
@ -116,25 +116,37 @@ openerp.testing.section('web-formats', {
|
|||
// equal(val.toString("HH:mm:ss"), res.toString("HH:mm:ss"));
|
||||
// });
|
||||
test('parse_integer', function (instance) {
|
||||
var val = instance.web.parse_value('123,456', {type: 'integer'});
|
||||
equal(val, 123456);
|
||||
instance.web._t.database.parameters.thousands_sep = '|';
|
||||
var val2 = instance.web.parse_value('123|456', {type: 'integer'});
|
||||
equal(val2, 123456);
|
||||
var tmp = instance.web._t.database.parameters.thousands_sep;
|
||||
try {
|
||||
var val = instance.web.parse_value('123,456', {type: 'integer'});
|
||||
equal(val, 123456);
|
||||
instance.web._t.database.parameters.thousands_sep = '|';
|
||||
var val2 = instance.web.parse_value('123|456', {type: 'integer'});
|
||||
equal(val2, 123456);
|
||||
} finally {
|
||||
instance.web._t.database.parameters.thousands_sep = tmp;
|
||||
}
|
||||
});
|
||||
test("parse_float", function (instance) {
|
||||
var str = "134,112.1234";
|
||||
var val = instance.web.parse_value(str, {type:"float"});
|
||||
equal(val, 134112.1234);
|
||||
str = "-134,112.1234";
|
||||
val = instance.web.parse_value(str, {type:"float"});
|
||||
equal(val, -134112.1234);
|
||||
_.extend(instance.web._t.database.parameters, {
|
||||
decimal_point: ',',
|
||||
thousands_sep: '.'
|
||||
});
|
||||
var val3 = instance.web.parse_value('123.456,789', {type: 'float'});
|
||||
equal(val3, 123456.789);
|
||||
var tmp1 = instance.web._t.database.parameters.thousands_sep;
|
||||
var tmp2 = instance.web._t.database.parameters.decimal_point;
|
||||
try {
|
||||
var str = "134,112.1234";
|
||||
var val = instance.web.parse_value(str, {type:"float"});
|
||||
equal(val, 134112.1234);
|
||||
str = "-134,112.1234";
|
||||
val = instance.web.parse_value(str, {type:"float"});
|
||||
equal(val, -134112.1234);
|
||||
_.extend(instance.web._t.database.parameters, {
|
||||
decimal_point: ',',
|
||||
thousands_sep: '.'
|
||||
});
|
||||
var val3 = instance.web.parse_value('123.456,789', {type: 'float'});
|
||||
equal(val3, 123456.789);
|
||||
} finally {
|
||||
instance.web._t.database.parameters.thousands_sep = tmp1;
|
||||
instance.web._t.database.parameters.decimal_point = tmp2;
|
||||
}
|
||||
});
|
||||
test('intersperse', function (instance) {
|
||||
var g = instance.web.intersperse;
|
||||
|
|
|
@ -1,8 +1,148 @@
|
|||
openerp.testing.section('Widget.proxy', {
|
||||
(function() {
|
||||
|
||||
var ropenerp = window.openerp;
|
||||
|
||||
var openerp = ropenerp.declare($, _, QWeb2);
|
||||
|
||||
ropenerp.testing.section('class', {
|
||||
dependencies: ['web.corelib']
|
||||
}, function (test) {
|
||||
test('(String)', function (instance) {
|
||||
var W = instance.web.Widget.extend({
|
||||
test('Basic class creation', function () {
|
||||
var C = openerp.web.Class.extend({
|
||||
foo: function () {
|
||||
return this.somevar;
|
||||
}
|
||||
});
|
||||
var i = new C();
|
||||
i.somevar = 3;
|
||||
|
||||
ok(i instanceof C);
|
||||
strictEqual(i.foo(), 3);
|
||||
});
|
||||
test('Class initialization', function () {
|
||||
var C1 = openerp.web.Class.extend({
|
||||
init: function () {
|
||||
this.foo = 3;
|
||||
}
|
||||
});
|
||||
var C2 = openerp.web.Class.extend({
|
||||
init: function (arg) {
|
||||
this.foo = arg;
|
||||
}
|
||||
});
|
||||
|
||||
var i1 = new C1(),
|
||||
i2 = new C2(42);
|
||||
|
||||
strictEqual(i1.foo, 3);
|
||||
strictEqual(i2.foo, 42);
|
||||
});
|
||||
test('Inheritance', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
foo: function () {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
var C1 = C0.extend({
|
||||
foo: function () {
|
||||
return 1 + this._super();
|
||||
}
|
||||
});
|
||||
var C2 = C1.extend({
|
||||
foo: function () {
|
||||
return 1 + this._super();
|
||||
}
|
||||
});
|
||||
|
||||
strictEqual(new C0().foo(), 1);
|
||||
strictEqual(new C1().foo(), 2);
|
||||
strictEqual(new C2().foo(), 3);
|
||||
});
|
||||
test('In-place extension', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
foo: function () {
|
||||
return 3;
|
||||
},
|
||||
qux: function () {
|
||||
return 3;
|
||||
},
|
||||
bar: 3
|
||||
});
|
||||
C0.include({
|
||||
foo: function () {
|
||||
return 5;
|
||||
},
|
||||
qux: function () {
|
||||
return 2 + this._super();
|
||||
},
|
||||
bar: 5,
|
||||
baz: 5
|
||||
});
|
||||
|
||||
strictEqual(new C0().bar, 5);
|
||||
strictEqual(new C0().baz, 5);
|
||||
strictEqual(new C0().foo(), 5);
|
||||
strictEqual(new C0().qux(), 5);
|
||||
});
|
||||
test('In-place extension and inheritance', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
var C1 = C0.extend({
|
||||
foo: function () { return 1 + this._super(); }
|
||||
});
|
||||
strictEqual(new C1().foo(), 2);
|
||||
strictEqual(new C1().bar(), 1);
|
||||
|
||||
C1.include({
|
||||
foo: function () { return 2 + this._super(); },
|
||||
bar: function () { return 1 + this._super(); }
|
||||
});
|
||||
strictEqual(new C1().foo(), 4);
|
||||
strictEqual(new C1().bar(), 2);
|
||||
});
|
||||
test('In-place extensions alter existing instances', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
var i = new C0();
|
||||
strictEqual(i.foo(), 1);
|
||||
strictEqual(i.bar(), 1);
|
||||
|
||||
C0.include({
|
||||
foo: function () { return 2; },
|
||||
bar: function () { return 2 + this._super(); }
|
||||
});
|
||||
strictEqual(i.foo(), 2);
|
||||
strictEqual(i.bar(), 3);
|
||||
});
|
||||
test('In-place extension of subclassed types', function () {
|
||||
var C0 = openerp.web.Class.extend({
|
||||
foo: function () { return 1; },
|
||||
bar: function () { return 1; }
|
||||
});
|
||||
var C1 = C0.extend({
|
||||
foo: function () { return 1 + this._super(); },
|
||||
bar: function () { return 1 + this._super(); }
|
||||
});
|
||||
var i = new C1();
|
||||
strictEqual(i.foo(), 2);
|
||||
C0.include({
|
||||
foo: function () { return 2; },
|
||||
bar: function () { return 2 + this._super(); }
|
||||
});
|
||||
strictEqual(i.foo(), 3);
|
||||
strictEqual(i.bar(), 4);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
ropenerp.testing.section('Widget.proxy', {
|
||||
}, function (test) {
|
||||
test('(String)', function () {
|
||||
var W = openerp.web.Widget.extend({
|
||||
exec: function () {
|
||||
this.executed = true;
|
||||
}
|
||||
|
@ -12,8 +152,8 @@ openerp.testing.section('Widget.proxy', {
|
|||
fn();
|
||||
ok(w.executed, 'should execute the named method in the right context');
|
||||
});
|
||||
test('(String)(*args)', function (instance) {
|
||||
var W = instance.web.Widget.extend({
|
||||
test('(String)(*args)', function () {
|
||||
var W = openerp.web.Widget.extend({
|
||||
exec: function (arg) {
|
||||
this.executed = arg;
|
||||
}
|
||||
|
@ -24,10 +164,10 @@ openerp.testing.section('Widget.proxy', {
|
|||
ok(w.executed, "should execute the named method in the right context");
|
||||
equal(w.executed, 42, "should be passed the proxy's arguments");
|
||||
});
|
||||
test('(String), include', function (instance) {
|
||||
test('(String), include', function () {
|
||||
// the proxy function should handle methods being changed on the class
|
||||
// and should always proxy "by name", to the most recent one
|
||||
var W = instance.web.Widget.extend({
|
||||
var W = openerp.web.Widget.extend({
|
||||
exec: function () {
|
||||
this.executed = 1;
|
||||
}
|
||||
|
@ -42,26 +182,25 @@ openerp.testing.section('Widget.proxy', {
|
|||
equal(w.executed, 2, "should be lazily resolved");
|
||||
});
|
||||
|
||||
test('(Function)', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({ }))();
|
||||
test('(Function)', function () {
|
||||
var w = new (openerp.web.Widget.extend({ }))();
|
||||
|
||||
var fn = w.proxy(function () { this.executed = true; });
|
||||
fn();
|
||||
ok(w.executed, "should set the function's context (like Function#bind)");
|
||||
});
|
||||
test('(Function)(*args)', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({ }))();
|
||||
test('(Function)(*args)', function () {
|
||||
var w = new (openerp.web.Widget.extend({ }))();
|
||||
|
||||
var fn = w.proxy(function (arg) { this.executed = arg; });
|
||||
fn(42);
|
||||
equal(w.executed, 42, "should be passed the proxy's arguments");
|
||||
});
|
||||
});
|
||||
openerp.testing.section('Widget.renderElement', {
|
||||
dependencies: ['web.corelib'],
|
||||
setup: function (instance) {
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.add_template(
|
||||
ropenerp.testing.section('Widget.renderElement', {
|
||||
setup: function () {
|
||||
openerp.web.qweb = new QWeb2.Engine();
|
||||
openerp.web.qweb.add_template(
|
||||
'<no>' +
|
||||
'<t t-name="test.widget.template">' +
|
||||
'<ol>' +
|
||||
|
@ -78,8 +217,8 @@ openerp.testing.section('Widget.renderElement', {
|
|||
'</no>');
|
||||
}
|
||||
}, function (test) {
|
||||
test('no template, default', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({ }))();
|
||||
test('no template, default', function () {
|
||||
var w = new (openerp.web.Widget.extend({ }))();
|
||||
|
||||
var $original = w.$el;
|
||||
ok($original, "should initially have a root element");
|
||||
|
@ -93,16 +232,16 @@ openerp.testing.section('Widget.renderElement', {
|
|||
equal(w.el.attributes.length, 0, "should not have generated any attribute");
|
||||
ok(_.isEmpty(w.$el.html(), "should not have generated any content"));
|
||||
});
|
||||
test('no template, custom tag', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
test('no template, custom tag', function () {
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
tagName: 'ul'
|
||||
}))();
|
||||
w.renderElement();
|
||||
|
||||
equal(w.el.nodeName, 'UL', "should have generated the custom element tag");
|
||||
});
|
||||
test('no template, @id', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
test('no template, @id', function () {
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
id: 'foo'
|
||||
}))();
|
||||
w.renderElement();
|
||||
|
@ -111,8 +250,8 @@ openerp.testing.section('Widget.renderElement', {
|
|||
equal(w.$el.attr('id'), 'foo', "should have generated the id attribute");
|
||||
equal(w.el.id, 'foo', "should also be available via property");
|
||||
});
|
||||
test('no template, @className', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
test('no template, @className', function () {
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
className: 'oe_some_class'
|
||||
}))();
|
||||
w.renderElement();
|
||||
|
@ -120,8 +259,8 @@ openerp.testing.section('Widget.renderElement', {
|
|||
equal(w.el.className, 'oe_some_class', "should have the right property");
|
||||
equal(w.$el.attr('class'), 'oe_some_class', "should have the right attribute");
|
||||
});
|
||||
test('no template, bunch of attributes', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
test('no template, bunch of attributes', function () {
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
attributes: {
|
||||
'id': 'some_id',
|
||||
'class': 'some_class',
|
||||
|
@ -147,8 +286,8 @@ openerp.testing.section('Widget.renderElement', {
|
|||
equal(w.$el.attr('spoiler'), 'snape kills dumbledore');
|
||||
});
|
||||
|
||||
test('template', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
test('template', function () {
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
template: 'test.widget.template'
|
||||
}))();
|
||||
w.renderElement();
|
||||
|
@ -157,8 +296,8 @@ openerp.testing.section('Widget.renderElement', {
|
|||
equal(w.$el.children().length, 5);
|
||||
equal(w.el.textContent, '01234');
|
||||
});
|
||||
test('repeated', { asserts: 4 }, function (instance, $fix) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
test('repeated', { asserts: 4 }, function (_unused, $fix) {
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
template: 'test.widget.template-value'
|
||||
}))();
|
||||
w.value = 42;
|
||||
|
@ -173,11 +312,10 @@ openerp.testing.section('Widget.renderElement', {
|
|||
});
|
||||
});
|
||||
});
|
||||
openerp.testing.section('Widget.$', {
|
||||
dependencies: ['web.corelib'],
|
||||
setup: function (instance) {
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.add_template(
|
||||
ropenerp.testing.section('Widget.$', {
|
||||
setup: function () {
|
||||
openerp.web.qweb = new QWeb2.Engine();
|
||||
openerp.web.qweb.add_template(
|
||||
'<no>' +
|
||||
'<t t-name="test.widget.template">' +
|
||||
'<ol>' +
|
||||
|
@ -191,8 +329,8 @@ openerp.testing.section('Widget.$', {
|
|||
'</no>');
|
||||
}
|
||||
}, function (test) {
|
||||
test('basic-alias', function (instance) {
|
||||
var w = new (instance.web.Widget.extend({
|
||||
test('basic-alias', function () {
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
template: 'test.widget.template'
|
||||
}))();
|
||||
w.renderElement();
|
||||
|
@ -201,11 +339,10 @@ openerp.testing.section('Widget.$', {
|
|||
"should do the same thing as calling find on the widget root");
|
||||
});
|
||||
});
|
||||
openerp.testing.section('Widget.events', {
|
||||
dependencies: ['web.corelib'],
|
||||
setup: function (instance) {
|
||||
instance.web.qweb = new QWeb2.Engine();
|
||||
instance.web.qweb.add_template(
|
||||
ropenerp.testing.section('Widget.events', {
|
||||
setup: function () {
|
||||
openerp.web.qweb = new QWeb2.Engine();
|
||||
openerp.web.qweb.add_template(
|
||||
'<no>' +
|
||||
'<t t-name="test.widget.template">' +
|
||||
'<ol>' +
|
||||
|
@ -219,9 +356,9 @@ openerp.testing.section('Widget.events', {
|
|||
'</no>');
|
||||
}
|
||||
}, function (test) {
|
||||
test('delegate', function (instance) {
|
||||
test('delegate', function () {
|
||||
var a = [];
|
||||
var w = new (instance.web.Widget.extend({
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
template: 'test.widget.template',
|
||||
events: {
|
||||
'click': function () {
|
||||
|
@ -243,9 +380,9 @@ openerp.testing.section('Widget.events', {
|
|||
ok(a[i], "should pass test " + i);
|
||||
}
|
||||
});
|
||||
test('undelegate', function (instance) {
|
||||
test('undelegate', function () {
|
||||
var clicked = false, newclicked = false;
|
||||
var w = new (instance.web.Widget.extend({
|
||||
var w = new (openerp.web.Widget.extend({
|
||||
template: 'test.widget.template',
|
||||
events: { 'click li': function () { clicked = true; } }
|
||||
}))();
|
||||
|
@ -263,3 +400,5 @@ openerp.testing.section('Widget.events', {
|
|||
ok(newclicked, "undelegate should only unbind events it created");
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
|
@ -1387,10 +1387,8 @@ openerp.testing.section('search.invisible', {
|
|||
templates: true,
|
||||
}, function (test) {
|
||||
var registerTestField = function (instance, methods) {
|
||||
instance.testing.TestWidget = instance.web.search.Field.extend(methods);
|
||||
instance.web.search.fields.add('test', 'instance.testing.TestWidget');
|
||||
instance.testing = {
|
||||
TestWidget: instance.web.search.Field.extend(methods),
|
||||
};
|
||||
};
|
||||
var makeView = function (instance, mock, fields, arch, defaults) {
|
||||
mock('ir.filters:get_filters', function () { return []; });
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// static/src/js/demo.js
|
||||
openerp.web_tests_demo = function (instance) {
|
||||
instance.web_tests_demo = {
|
||||
_.extend(instance.web_tests_demo, {
|
||||
value_true: true,
|
||||
SomeType: instance.web.Class.extend({
|
||||
init: function (value) {
|
||||
this.value = value;
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
openerp.testing.section('basic section', function (test) {
|
||||
|
||||
// niv: I desactivate these until the testing framework has been adapted to better use
|
||||
// the new way to declare JavaScript modules
|
||||
/*openerp.testing.section('basic section', function (test) {
|
||||
test('my first test', function () {
|
||||
ok(true, "this test has run");
|
||||
});
|
||||
|
@ -99,4 +102,4 @@ openerp.testing.section('basic section', function (test) {
|
|||
// strictEqual(record.other, 'bob');
|
||||
// });
|
||||
// });
|
||||
});
|
||||
});*/
|
||||
|
|
Loading…
Reference in New Issue