From fd1df3ee22cfe8558a7d6b1fb661c36fc97b084b Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 14:15:14 +0200 Subject: [PATCH 01/16] [IMP] Removed openerp's ineffective JavaScript module loader and added retro-compatibility layer bzr revid: nicolas.vanhoren@openerp.com-20130726121514-ss0omrhed76ybv5a --- addons/web/__openerp__.py | 1 + addons/web/static/src/js/boot.js | 75 +++++++++++--------- addons/web/static/src/js/coresetup.js | 11 ++- addons/web/static/src/js/openerpframework.js | 18 +++++ 4 files changed, 68 insertions(+), 37 deletions(-) create mode 100644 addons/web/static/src/js/openerpframework.js diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index e8c2dd903d0..7a00b4c5a47 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -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", diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index 4565d220dc8..c255db36ff8 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -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. will map to // openerp.instances.sessionname. 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 (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 Date: Fri, 26 Jul 2013 14:40:57 +0200 Subject: [PATCH 02/16] [IMP] Put Class and Widget in a openerpframework.js bzr revid: nicolas.vanhoren@openerp.com-20130726124057-qwmslhhheso5xy1x --- addons/web/static/src/js/corelib.js | 757 +------------------ addons/web/static/src/js/coresetup.js | 1 - addons/web/static/src/js/openerpframework.js | 739 +++++++++++++++++- 3 files changed, 753 insertions(+), 744 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 6ab7a30c642..1b93ced09aa 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -25,460 +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); - }, +var ControllerMixin = { /** * Proxies a method of the object, in order to keep the right ``this`` on * method invocations. @@ -530,301 +77,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 diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index d4e1c6bc8a5..424782d5e24 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -564,7 +564,6 @@ 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 = { '_' : _, diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js index f481f0ce6b1..85c454d595d 100644 --- a/addons/web/static/src/js/openerpframework.js +++ b/addons/web/static/src/js/openerpframework.js @@ -1,16 +1,745 @@ (function() { -function declare($, _, QWeb) { - var openerp = {}; +function declare($, _, QWeb2) { +var openerp = {}; +openerp.web = {}; - 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(){}; - return openerp; + /** + * 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 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 = 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. + */ +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); + } +}); + +openerp.web.qweb = new QWeb2.Engine(); + +return openerp; }; if (typeof(define) !== "undefined") { // amd - define(["jquery", "underscore", "qweb"], declare); + define(["jquery", "underscore", "qweb2"], declare); } else { window.openerp = declare($, _, QWeb2); } From 99e07f1e3e99c633702b9e10dabce5334098d47e Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 14:44:46 +0200 Subject: [PATCH 03/16] Put some qweb globals in the framework bzr revid: nicolas.vanhoren@openerp.com-20130726124446-6z3xpm1mu22mgep3 --- addons/web/static/src/js/coresetup.js | 6 ++---- addons/web/static/src/js/openerpframework.js | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 424782d5e24..7ff082edcce 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -565,12 +565,10 @@ instance.web._lt = function (s) { return {toString: function () { return instance.web._t(s); }}; }; 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) { diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js index 85c454d595d..7774b23eed4 100644 --- a/addons/web/static/src/js/openerpframework.js +++ b/addons/web/static/src/js/openerpframework.js @@ -735,6 +735,11 @@ openerp.web.Widget = openerp.web.Class.extend(openerp.web.PropertiesMixin, { openerp.web.qweb = new QWeb2.Engine(); +openerp.web.qweb.default_dict = { + '_' : _, + 'JSON': JSON, +}; + return openerp; }; From c94c4599b2a1e76664db8558bcd973ac6250e18f Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 14:48:37 +0200 Subject: [PATCH 04/16] Moved proxy() bzr revid: nicolas.vanhoren@openerp.com-20130726124837-yze64evp7r70uv5y --- addons/web/static/src/js/corelib.js | 26 ------------------ addons/web/static/src/js/openerpframework.js | 28 +++++++++++++++++++- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 1b93ced09aa..c419685c222 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -26,32 +26,6 @@ openerp.web.corelib = function(instance) { var ControllerMixin = { - /** - * 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); - }; - }, /** * Informs the action manager to do an action. This supposes that * the action manager can be found amongst the ancestors of the current widget. diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js index 7774b23eed4..c67d9a34f23 100644 --- a/addons/web/static/src/js/openerpframework.js +++ b/addons/web/static/src/js/openerpframework.js @@ -730,7 +730,33 @@ openerp.web.Widget = openerp.web.Class.extend(openerp.web.PropertiesMixin, { */ $: 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(); From 5c7b6cffdecbdc632bcb47a3c420a173e797c53c Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 15:18:05 +0200 Subject: [PATCH 05/16] Fixed some problems about testing framework bzr revid: nicolas.vanhoren@openerp.com-20130726131805-wstt1de2qqq7w7hx --- addons/web/static/src/js/testing.js | 36 +++++------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index ed26238028e..d3a9a538dd9 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -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,36 +242,10 @@ 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); - }); + var instance = openerp; + if (!openerp_inited) { + openerp.init(module_deps); + openerp_inited = true; } if (instance.session) { instance.session.uid = 42; From 38bc6817d6c411124c2d7f1ed456f01cad2cfc33 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 15:35:50 +0200 Subject: [PATCH 06/16] Fixed problem with parsing functions tests that created side effects bzr revid: nicolas.vanhoren@openerp.com-20130726133550-96zyro1znep8ip6x --- addons/web/static/test/formats.js | 46 +++++++++++++++++++------------ 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/addons/web/static/test/formats.js b/addons/web/static/test/formats.js index fe8388c8929..b9ff5e96371 100644 --- a/addons/web/static/test/formats.js +++ b/addons/web/static/test/formats.js @@ -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; From 0c79e1b07968e982ab012c4834f38a62cbe2321d Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 16:27:07 +0200 Subject: [PATCH 07/16] Fixed multiple side effects related to the session bzr revid: nicolas.vanhoren@openerp.com-20130726142707-eyfq5hmubm38slk1 --- addons/web/static/src/js/testing.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index d3a9a538dd9..af434c4c0f4 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -247,9 +247,8 @@ openerp.testing = {}; openerp.init(module_deps); openerp_inited = true; } - if (instance.session) { - instance.session.uid = 42; - } + instance.session = new instance.web.Session(); + instance.session.uid = 42; if (_.isNumber(opts.asserts)) { expect(opts.asserts); } From a02da32a083b356099f183b0852e98d84876d621 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 16:36:47 +0200 Subject: [PATCH 08/16] Changed some testing code creating side effects and making other tests crash bzr revid: nicolas.vanhoren@openerp.com-20130726143647-rz01o0cnwm2a1gyo --- addons/web/static/test/search.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index 268fe4d1c0c..3941c3ccdfe 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -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 []; }); From 861186d136460a174e7b4e6182b939cdf3d560de Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 17:44:54 +0200 Subject: [PATCH 09/16] Fixed potential problem in web_test_demo bzr revid: nicolas.vanhoren@openerp.com-20130726154454-9hu2gpxcz4ynhmkj --- addons/web_tests_demo/static/src/js/demo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web_tests_demo/static/src/js/demo.js b/addons/web_tests_demo/static/src/js/demo.js index b35b44b80f8..506808027cc 100644 --- a/addons/web_tests_demo/static/src/js/demo.js +++ b/addons/web_tests_demo/static/src/js/demo.js @@ -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; } }) - }; + }); }; From 89a9ab8cd055befb2e379b9f9366e5a8cdc8b385 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 18:03:15 +0200 Subject: [PATCH 10/16] Now uses only openerpframework in widget's tests bzr revid: nicolas.vanhoren@openerp.com-20130726160315-l60r6z706urv2hga --- addons/web/static/src/js/openerpframework.js | 2 + addons/web/static/test/Widget.js | 98 ++++++++++---------- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js index c67d9a34f23..11bc6497e8a 100644 --- a/addons/web/static/src/js/openerpframework.js +++ b/addons/web/static/src/js/openerpframework.js @@ -766,6 +766,8 @@ openerp.web.qweb.default_dict = { 'JSON': JSON, }; +openerp.declare = declare; + return openerp; }; diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index 858044f8cb0..ce48fd529b2 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -1,8 +1,13 @@ -openerp.testing.section('Widget.proxy', { - dependencies: ['web.corelib'] +(function() { + +var ropenerp = window.openerp; + +var openerp = ropenerp.declare($, _, QWeb2); + +ropenerp.testing.section('Widget.proxy', { }, function (test) { - test('(String)', function (instance) { - var W = instance.web.Widget.extend({ + test('(String)', function () { + var W = openerp.web.Widget.extend({ exec: function () { this.executed = true; } @@ -12,8 +17,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 +29,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 +47,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( '' + '' + '
    ' + @@ -78,8 +82,8 @@ openerp.testing.section('Widget.renderElement', { ''); } }, 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 +97,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 +115,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 +124,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 +151,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 +161,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 +177,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( '' + '' + '
      ' + @@ -191,8 +194,8 @@ openerp.testing.section('Widget.$', { ''); } }, 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 +204,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( '' + '' + '
        ' + @@ -219,9 +221,9 @@ openerp.testing.section('Widget.events', { ''); } }, 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 +245,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 +265,5 @@ openerp.testing.section('Widget.events', { ok(newclicked, "undelegate should only unbind events it created"); }); }); + +})(); From e01b96e76290ba6e0689bc143deeb79bcef9701e Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 18:07:15 +0200 Subject: [PATCH 11/16] Put class and widgets tests in framework.js bzr revid: nicolas.vanhoren@openerp.com-20130726160715-adjyalkubyismvao --- addons/web/__openerp__.py | 3 +- addons/web/static/test/class.js | 133 ----------------- .../static/test/{Widget.js => framework.js} | 135 ++++++++++++++++++ 3 files changed, 136 insertions(+), 135 deletions(-) delete mode 100644 addons/web/static/test/class.js rename addons/web/static/test/{Widget.js => framework.js} (70%) diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index 7a00b4c5a47..08c6d0c6148 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -75,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", @@ -84,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" diff --git a/addons/web/static/test/class.js b/addons/web/static/test/class.js deleted file mode 100644 index bb3fb7e12b9..00000000000 --- a/addons/web/static/test/class.js +++ /dev/null @@ -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); - }); -}); diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/framework.js similarity index 70% rename from addons/web/static/test/Widget.js rename to addons/web/static/test/framework.js index ce48fd529b2..5848c5cde7f 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/framework.js @@ -4,6 +4,141 @@ var ropenerp = window.openerp; var openerp = ropenerp.declare($, _, QWeb2); +ropenerp.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); + }); +}); + + ropenerp.testing.section('Widget.proxy', { }, function (test) { test('(String)', function () { From 95599aefd445f2a19c28d2d12bed8807ca896872 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 18:08:45 +0200 Subject: [PATCH 12/16] Corrected class tests bzr revid: nicolas.vanhoren@openerp.com-20130726160845-z2ug9cwp41y1h9yj --- addons/web/static/test/framework.js | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/addons/web/static/test/framework.js b/addons/web/static/test/framework.js index 5848c5cde7f..8fb066bbce9 100644 --- a/addons/web/static/test/framework.js +++ b/addons/web/static/test/framework.js @@ -7,8 +7,8 @@ var openerp = ropenerp.declare($, _, QWeb2); ropenerp.testing.section('class', { dependencies: ['web.corelib'] }, function (test) { - test('Basic class creation', function (instance) { - var C = instance.web.Class.extend({ + test('Basic class creation', function () { + var C = openerp.web.Class.extend({ foo: function () { return this.somevar; } @@ -19,13 +19,13 @@ ropenerp.testing.section('class', { ok(i instanceof C); strictEqual(i.foo(), 3); }); - test('Class initialization', function (instance) { - var C1 = instance.web.Class.extend({ + test('Class initialization', function () { + var C1 = openerp.web.Class.extend({ init: function () { this.foo = 3; } }); - var C2 = instance.web.Class.extend({ + var C2 = openerp.web.Class.extend({ init: function (arg) { this.foo = arg; } @@ -37,8 +37,8 @@ ropenerp.testing.section('class', { strictEqual(i1.foo, 3); strictEqual(i2.foo, 42); }); - test('Inheritance', function (instance) { - var C0 = instance.web.Class.extend({ + test('Inheritance', function () { + var C0 = openerp.web.Class.extend({ foo: function () { return 1; } @@ -58,8 +58,8 @@ ropenerp.testing.section('class', { strictEqual(new C1().foo(), 2); strictEqual(new C2().foo(), 3); }); - test('In-place extension', function (instance) { - var C0 = instance.web.Class.extend({ + test('In-place extension', function () { + var C0 = openerp.web.Class.extend({ foo: function () { return 3; }, @@ -84,8 +84,8 @@ ropenerp.testing.section('class', { strictEqual(new C0().foo(), 5); strictEqual(new C0().qux(), 5); }); - test('In-place extension and inheritance', function (instance) { - var C0 = instance.web.Class.extend({ + test('In-place extension and inheritance', function () { + var C0 = openerp.web.Class.extend({ foo: function () { return 1; }, bar: function () { return 1; } }); @@ -102,8 +102,8 @@ ropenerp.testing.section('class', { 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({ + test('In-place extensions alter existing instances', function () { + var C0 = openerp.web.Class.extend({ foo: function () { return 1; }, bar: function () { return 1; } }); @@ -118,8 +118,8 @@ ropenerp.testing.section('class', { strictEqual(i.foo(), 2); strictEqual(i.bar(), 3); }); - test('In-place extension of subclassed types', function (instance) { - var C0 = instance.web.Class.extend({ + test('In-place extension of subclassed types', function () { + var C0 = openerp.web.Class.extend({ foo: function () { return 1; }, bar: function () { return 1; } }); From 4157b860bc11aa4a882539761863fd67cb7929ee Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 26 Jul 2013 18:11:43 +0200 Subject: [PATCH 13/16] Desactivated some sample tests bzr revid: nicolas.vanhoren@openerp.com-20130726161143-z77m3ez5vm9kcehn --- addons/web_tests_demo/static/test/demo.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/web_tests_demo/static/test/demo.js b/addons/web_tests_demo/static/test/demo.js index 02e3b404d90..0ca10ba642c 100644 --- a/addons/web_tests_demo/static/test/demo.js +++ b/addons/web_tests_demo/static/test/demo.js @@ -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'); // }); // }); -}); +});*/ From 22ac3304e1e29699db3b9068d1a417a269ec944a Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Mon, 29 Jul 2013 11:25:12 +0200 Subject: [PATCH 14/16] Added strict mode and es3 mode to framework and corrected some problems bzr revid: nicolas.vanhoren@openerp.com-20130729092512-1ip4abjrypf74qr2 --- addons/web/package.json | 6 +++--- addons/web/static/src/js/boot.js | 4 ++-- addons/web/static/src/js/coresetup.js | 2 +- addons/web/static/src/js/openerpframework.js | 13 ++++++++----- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/addons/web/package.json b/addons/web/package.json index f8dfc2879d6..3ce6500a077 100644 --- a/addons/web/package.json +++ b/addons/web/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "grunt": "~0.4.1", - "grunt-contrib-jshint": "~0.6.0" + "grunt": "*", + "grunt-contrib-jshint": "*" } -} \ No newline at end of file +} diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index c255db36ff8..87115fd30ea 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -40,7 +40,7 @@ var fct = openerp[modules[i]]; if (typeof(fct) === "function") { openerp[modules[i]] = {}; - for (k in fct) { + for (var k in fct) { openerp[modules[i]][k] = fct[k]; } fct(openerp, openerp[modules[i]]); @@ -60,7 +60,7 @@ var fct = openerp.web[files[i]]; if(typeof(fct) === "function") { openerp.web[files[i]] = {}; - for (k in fct) { + for (var k in fct) { openerp.web[files[i]][k] = fct[k]; } fct(openerp, openerp.web[files[i]]); diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 7ff082edcce..c8deca1c9ac 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -253,7 +253,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess var fct = instance._openerp[mod]; if(typeof(fct) === "function") { instance._openerp[mod] = {}; - for (k in fct) { + for (var k in fct) { instance._openerp[mod][k] = fct[k]; } fct(instance, instance._openerp[mod]); diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js index 11bc6497e8a..470baf89b2d 100644 --- a/addons/web/static/src/js/openerpframework.js +++ b/addons/web/static/src/js/openerpframework.js @@ -1,5 +1,7 @@ (function() { +/* jshint es3: true */ +"use strict"; function declare($, _, QWeb2) { var openerp = {}; @@ -72,7 +74,8 @@ openerp.web = {}; // Instantiate a web class (but only create the instance, // don't run the init constructor) initializing = true; - var prototype = new this(); + var This = this; + var prototype = new This(); initializing = false; // Copy the properties over onto the new prototype @@ -148,7 +151,7 @@ openerp.web = {}; Class.constructor = Class; // And make this class extendable - Class.extend = arguments.callee; + Class.extend = this.extend; return Class; }; @@ -756,20 +759,20 @@ openerp.web.Widget = openerp.web.Class.extend(openerp.web.PropertiesMixin, { 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, + 'JSON': JSON }; openerp.declare = declare; return openerp; -}; +} if (typeof(define) !== "undefined") { // amd define(["jquery", "underscore", "qweb2"], declare); From 80df99fa184f2a8878f7842a3f4be80cea90e8a7 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Mon, 29 Jul 2013 11:36:57 +0200 Subject: [PATCH 15/16] cosmetic bzr revid: nicolas.vanhoren@openerp.com-20130729093657-p0xdm0nyhg4jn3i7 --- addons/web/static/src/js/openerpframework.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js index 470baf89b2d..374493b1f7e 100644 --- a/addons/web/static/src/js/openerpframework.js +++ b/addons/web/static/src/js/openerpframework.js @@ -7,7 +7,7 @@ function declare($, _, QWeb2) { var openerp = {}; openerp.web = {}; - /** +/** * Improved John Resig's inheritance, based on: * * Simple JavaScript Inheritance From cf4c185f8c9694b7298c88c04582be026135d0cd Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Mon, 29 Jul 2013 18:49:52 +0200 Subject: [PATCH 16/16] Removed jshint in the "test" task of grunt bzr revid: nicolas.vanhoren@openerp.com-20130729164952-srjsdg8pmq1x2ahd --- addons/web/Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/Gruntfile.js b/addons/web/Gruntfile.js index cbc650492b6..81c41c86001 100644 --- a/addons/web/Gruntfile.js +++ b/addons/web/Gruntfile.js @@ -13,7 +13,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.registerTask('test', ['jshint']); + grunt.registerTask('test', []); grunt.registerTask('default', ['jshint']);