From 0fee0cea0079a57d362a5752cf3499a28c58bc43 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 08:45:23 +0200 Subject: [PATCH 01/89] [ADD] backbone-ification of Widgets API bzr revid: xmo@openerp.com-20120611064523-gczdqmfes5f1g9kj --- addons/web/static/src/js/corelib.js | 177 ++++++++++++++++++++++------ 1 file changed, 143 insertions(+), 34 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 94c748f839c..8455a183f87 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -238,6 +238,20 @@ instance.web.ParentedMixin = { } }; +/** + * Utility function for APIs which can take either a value or a function which + * returns a value + * + * @param {Object} object + * @param {String} property + * @returns {Object} + */ +var getValue = function (object, property) { + if (object == null) { return null; } + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; +}; + /** * Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead. * @@ -490,23 +504,19 @@ instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, { * * The semantics of this precisely replace closing over the method call. * - * @param {String} method_name name of the method to invoke + * @param {String|Function} method function or name of the method to invoke * @returns {Function} proxied method */ - proxy: function (method_name) { + proxy: function (method) { var self = this; return function () { - return self[method_name].apply(self, arguments); + var fn = (method instanceof String) ? self[method] : method; + return fn.apply(self, arguments); } } }); instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { - /** - * Tag name when creating a default $element. - * @type string - */ - tagName: 'div', /** * Constructs the widget and sets its parent if a parent is given. * @@ -516,14 +526,9 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance. * When that widget is destroyed by calling destroy(), the current instance will be * destroyed too. Can be null. - * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want - * to bind the current Widget to an already existing part of the DOM, which is not compatible - * with the DOM insertion methods provided by the current implementation of Widget. So - * for new components this argument should not be provided any more. */ init: function(parent) { instance.web.CallbackEnabledMixin.init.call(this); - this.$element = $(document.createElement(this.tagName)); this.setParent(parent); }, /** @@ -533,8 +538,8 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { _.each(this.getChildren(), function(el) { el.destroy(); }); - if(this.$element != null) { - this.$element.remove(); + if(this.$el) { + this.$el.remove(); } instance.web.PropertiesMixin.destroy.call(this); }, @@ -546,7 +551,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { appendTo: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.appendTo(t); + self.$el.appendTo(t); }, target); }, /** @@ -557,7 +562,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { prependTo: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.prependTo(t); + self.$el.prependTo(t); }, target); }, /** @@ -568,7 +573,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { insertAfter: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.insertAfter(t); + self.$el.insertAfter(t); }, target); }, /** @@ -579,7 +584,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { insertBefore: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.insertBefore(t); + self.$el.insertBefore(t); }, target); }, /** @@ -589,7 +594,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { */ replace: function(target) { return this.__widgetRenderAndInsert(_.bind(function(t) { - this.$element.replaceAll(t); + this.$el.replaceAll(t); }, this), target); }, __widgetRenderAndInsert: function(insertion, target) { @@ -612,6 +617,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { * @returns {jQuery.Deferred} */ start: function() { + return $.when(); } }); @@ -688,12 +694,9 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance. * When that widget is destroyed by calling destroy(), the current instance will be * destroyed too. Can be null. - * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want - * to bind the current Widget to an already existing part of the DOM, which is not compatible - * with the DOM insertion methods provided by the current implementation of Widget. So - * for new components this argument should not be provided any more. */ init: function(parent) { + this._uid = _.uniqueId('-widget-'); instance.web.WidgetMixin.init.call(this,parent); this.session = instance.connection; }, @@ -703,20 +706,126 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * key that references `this`. */ renderElement: function() { - var rendered = null; - if (this.template) - rendered = instance.web.qweb.render(this.template, {widget: this}); - if (_.str.trim(rendered)) { - var elem = $(rendered); - this.$element.replaceWith(elem); - this.$element = elem; + var $el; + if (this.template) { + $el = $(_.str.trim(instance.web.qweb.render( + this.template, {widget: this}))); + } else { + var attrs = _.extend({}, getValue(this, 'attributes') || {}); + if (this.id) { attrs.id = this.id; } + if (this.className) { attrs['class'] = this.className; } + $el = $(this.make(this.tagName, attrs)) + } + this.setElement($el); + }, + + /** + * Re-sets the widget's root element (el/$el/$element). + * + * 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 {*} + */ + setElement: function (element) { + if (this.$el) { + this.undelegateEvents(); + this.unbindElements(); + this.$el.replaceWith(element); + } + + this.$element = this.$el = (element instanceof $) ? element : $(element); + this.el = this.$el[0]; + + this.bindElements(); + this.delegateEvents(); + + return this; + }, + + // Backbone-ish API + tagName: 'div', + id: null, + className: null, + attributes: {}, + /** + * 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 (attributes) { + $(el).attrs(attributes); + } + if (content) { + $(el).html(content); + } + return el; + }, + events: {}, + delegateEvents: function () { + var events = getValue(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]; + + // add namespace for easy un-delegation + event += '.delegated-events' + this._uid; + if (!selector) { + this.$el.on(event, method); + } else { + this.$el.on(event, selector, method); + } + } + }, + undelegateEvents: function () { + this.$el.off('.delegated-events' + this._uid); + }, + elements: {}, + bindElements: function () { + var elements = getValue(this, 'elements'); + if (_.isEmpty(elements)) { return; } + + for (var selector in elements) { + if (!elements.hasOwnProperty(selector)) { continue; } + + // TODO: ensure at least one element matched? + this[elements[selector]] = this.$(selector); + } + }, + unbindElements: function () { + var elements = getValue(this, 'elements'); + if (_.isEmpty(elements)) { return; } + + for (var selector in elements) { + if (!elements.hasOwnProperty(selector)) { continue; } + + delete this[elements[selector]]; } }, /** - * Shortcut for $element.find() like backbone + * Shortcut for ``this.$el.find(selector)`` + * + * @param {String} selector CSS selector, rooted in $el + * @returns {jQuery} selector match */ - "$": function() { - return this.$element.find.apply(this.$element,arguments); + $: function(selector) { + return this.$el.find(selector); }, /** * Informs the action manager to do an action. This supposes that From 459eb54d77f8fce5cce460d56c74121fe8125275 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 10:50:19 +0200 Subject: [PATCH 02/89] [FIX] broken unit tests bzr revid: xmo@openerp.com-20120611085019-z16aggfnuovgh1p4 --- addons/web/static/src/xml/base.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 3764395dc13..7bbc1884beb 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1409,10 +1409,10 @@ -
+
-
+

M Custom Filters

    From 26ec50e0603561128a5470f871d9ea487ed95560 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 11:18:03 +0200 Subject: [PATCH 03/89] [FIX] bug in new 'proxy' implementation revealed by searchview tests bzr revid: xmo@openerp.com-20120611091803-kzo5j5t8p82gwr6i --- addons/web/static/src/js/corelib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 8455a183f87..61464a58930 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -510,7 +510,7 @@ instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, { proxy: function (method) { var self = this; return function () { - var fn = (method instanceof String) ? self[method] : method; + var fn = (typeof method === 'string') ? self[method] : method; return fn.apply(self, arguments); } } From 5c701e2b1d8070e49f0dc247f1b649f19ebc7751 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 11:18:20 +0200 Subject: [PATCH 04/89] [FIX] behavior of OldWidget for new Widget implementation bzr revid: xmo@openerp.com-20120611091820-m5gm67psqeh1xxx8 --- addons/web/static/src/js/coresetup.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 4defde54a3c..d419c7ac3c1 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -19,15 +19,15 @@ instance.web.OldWidget = instance.web.Widget.extend({ this._super(parent); this.element_id = element_id; this.element_id = this.element_id || _.uniqueId('widget-'); + var tmp = document.getElementById(this.element_id); - this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName)); + this.setElement(tmp || this.make(this.tagName)); }, renderElement: function() { var rendered = this.render(); if (rendered) { - var elem = $(rendered); - this.$element.replaceWith(elem); - this.$element = elem; + var $el = $(rendered); + this.setElement($el); } return this; }, From 3f1757869c51cadb87d57abbb0392c78cb888bda Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 11:18:57 +0200 Subject: [PATCH 05/89] [IMP] make searchview base widget not inherit from OldWidget anymore bzr revid: xmo@openerp.com-20120611091857-wcpc2oqywoc0gg51 --- addons/web/static/src/js/search.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 85521a14651..22649197317 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -886,13 +886,13 @@ instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web ); } }); -instance.web.search.Widget = instance.web.OldWidget.extend( /** @lends instance.web.search.Widget# */{ +instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web.search.Widget# */{ template: null, /** * Root class of all search widgets * * @constructs instance.web.search.Widget - * @extends instance.web.OldWidget + * @extends instance.web.Widget * * @param view the ancestor view of this widget */ From 772bed66f33f16886ac0211cda6bd34ba05e4e3d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 11:48:40 +0200 Subject: [PATCH 06/89] [TEST] Widget#proxy, with both String and Function arguments bzr revid: xmo@openerp.com-20120611094840-mtilgirzwlhkl2qc --- addons/web/static/test/Widget.js | 67 ++++++++++++++++++++++++++++++++ addons/web/static/test/test.html | 1 + 2 files changed, 68 insertions(+) create mode 100644 addons/web/static/test/Widget.js diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js new file mode 100644 index 00000000000..dc86e99b38a --- /dev/null +++ b/addons/web/static/test/Widget.js @@ -0,0 +1,67 @@ +$(document).ready(function () { + var $fix = $('#qunit-fixture'); + var mod = { + setup: function () { + instance = window.openerp.init([]); + window.openerp.web.corelib(instance); + } + }; + var instance; + + module('Widget.proxy', mod); + test('(String)', function () { + var W = instance.web.Widget.extend({ + exec: function () { + this.executed = true; + } + }); + var w = new W; + var fn = w.proxy('exec'); + fn(); + ok(w.executed, 'should execute the named method in the right context'); + }); + test('(String)(*args)', function () { + var W = instance.web.Widget.extend({ + exec: function (arg) { + this.executed = arg; + } + }); + var w = new W; + var fn = w.proxy('exec'); + fn(42); + 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 () { + // 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({ + exec: function () { + this.executed = 1; + } + }); + var w = new W; + var fn = w.proxy('exec'); + W.include({ + exec: function () { this.executed = 2; } + }); + + fn(); + equal(w.executed, 2, "should be lazily resolved"); + }); + + test('(Function)', function () { + var w = new (instance.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 () { + var w = new (instance.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"); + }); +}); diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html index e352f63c17c..dd0d08a4986 100644 --- a/addons/web/static/test/test.html +++ b/addons/web/static/test/test.html @@ -55,4 +55,5 @@ + From a0c5057dbacef8e8f1841245c58ad4f52da57ef9 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 13:32:38 +0200 Subject: [PATCH 07/89] [TEST] Widget#renderElement with no template specified, fix Widget#make's handling of attributes bzr revid: xmo@openerp.com-20120611113238-wz516g9tl0xp1igd --- addons/web/static/src/js/corelib.js | 4 +- addons/web/static/test/Widget.js | 68 +++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 61464a58930..83586ec57d4 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -762,8 +762,8 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { */ make: function (tagName, attributes, content) { var el = document.createElement(tagName); - if (attributes) { - $(el).attrs(attributes); + if (!_.isEmpty(attributes)) { + $(el).attr(attributes); } if (content) { $(el).html(content); diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index dc86e99b38a..51a5182845d 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -64,4 +64,72 @@ $(document).ready(function () { fn(42); equal(w.executed, 42, "should be passed the proxy's arguments"); }); + + module('Widget.renderElement', mod); + test('no template, default', function () { + var w = new (instance.web.Widget.extend({ })); + + ok(!w.$el, "should not initially have a root element"); + w.renderElement(); + ok(w.$el, "should have generated a root element"); + strictEqual(w.$element, w.$el, "should provide $element alias"); + ok(w.$el.is(w.el), "should provide raw DOM alias"); + + equal(w.el.nodeName, 'DIV', "should have generated the default element"); + 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 () { + var w = new (instance.web.Widget.extend({ + tagName: 'ul' + })); + w.renderElement(); + + equal(w.el.nodeName, 'UL', "should have generated the custom element tag"); + }); + test('no template, @id', function () { + var w = new (instance.web.Widget.extend({ + id: 'foo' + })); + w.renderElement(); + + equal(w.el.attributes.length, 1, "should have one attribute"); + 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 () { + var w = new (instance.web.Widget.extend({ + className: 'oe_some_class' + })); + w.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 () { + var w = new (instance.web.Widget.extend({ + attributes: { + 'id': 'some_id', + 'class': 'some_class', + 'data-foo': 'data attribute', + 'clark': 'gable', + 'spoiler': 'snape kills dumbledore' + } + })); + w.renderElement(); + + equal(w.el.attributes.length, 5, "should have all the specified attributes"); + + equal(w.el.id, 'some_id'); + equal(w.$el.attr('id'), 'some_id'); + + equal(w.el.className, 'some_class'); + equal(w.$el.attr('class'), 'some_class'); + + equal(w.$el.attr('data-foo'), 'data attribute'); + equal(w.$el.data('foo'), 'data attribute'); + + equal(w.$el.attr('clark'), 'gable'); + equal(w.$el.attr('spoiler'), 'snape kills dumbledore'); + }); }); From c531cb96ca2b0dc74ec6f9569eec8ea24c6506a1 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 13:48:49 +0200 Subject: [PATCH 08/89] [TEST] Wisget#renderElement with template bzr revid: xmo@openerp.com-20120611114849-dwk4erae5f1tyc6o --- addons/web/static/test/Widget.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index 51a5182845d..3a2301e53f8 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -132,4 +132,24 @@ $(document).ready(function () { equal(w.$el.attr('clark'), 'gable'); equal(w.$el.attr('spoiler'), 'snape kills dumbledore'); }); + + test('template', function () { + instance.web.qweb = new QWeb2.Engine(); + instance.web.qweb.add_template('' + + '
      ' + + '
    1. ' + + '' + + '
    2. ' + + '
    ' + + '
    '); + + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template' + })); + w.renderElement(); + + equal(w.el.nodeName, 'OL'); + equal(w.$el.children().length, 5); + equal(w.el.textContent, '01234'); + }); }); From 2a9bd73a83c27e3dfab7e7b6438ad9988930ae39 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 14:08:18 +0200 Subject: [PATCH 09/89] [TEST] Widget#$ shortcut bzr revid: xmo@openerp.com-20120611120818-apsawz6kypnpc5m3 --- addons/web/static/test/Widget.js | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index 3a2301e53f8..f588d9a258c 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -4,6 +4,15 @@ $(document).ready(function () { setup: function () { instance = window.openerp.init([]); window.openerp.web.corelib(instance); + + instance.web.qweb = new QWeb2.Engine(); + instance.web.qweb.add_template('' + + '
      ' + + '
    1. ' + + '' + + '
    2. ' + + '
    ' + + '
    '); } }; var instance; @@ -134,15 +143,6 @@ $(document).ready(function () { }); test('template', function () { - instance.web.qweb = new QWeb2.Engine(); - instance.web.qweb.add_template('' + - '
      ' + - '
    1. ' + - '' + - '
    2. ' + - '
    ' + - '
    '); - var w = new (instance.web.Widget.extend({ template: 'test.widget.template' })); @@ -152,4 +152,15 @@ $(document).ready(function () { equal(w.$el.children().length, 5); equal(w.el.textContent, '01234'); }); + + module('Widget.$', mod); + test('basic-alias', function () { + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template' + })); + w.renderElement(); + + ok(w.$('li:eq(3)').is(w.$el.find('li:eq(3)')), + "should do the same thing as calling find on the widget root"); + }); }); From c64d8859a01933a89cd5c06df4324d7e11081a4f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 14:48:24 +0200 Subject: [PATCH 10/89] [TEST] declarative events delegation bzr revid: xmo@openerp.com-20120611124824-w83h6betknrajyte --- addons/web/static/test/Widget.js | 49 +++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index f588d9a258c..d797120a341 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -8,7 +8,9 @@ $(document).ready(function () { instance.web.qweb = new QWeb2.Engine(); instance.web.qweb.add_template('' + '
      ' + - '
    1. ' + + '
    2. ' + + '' + '' + '
    3. ' + '
    ' + @@ -163,4 +165,49 @@ $(document).ready(function () { ok(w.$('li:eq(3)').is(w.$el.find('li:eq(3)')), "should do the same thing as calling find on the widget root"); }); + + module('Widget.events', mod); + test('delegate', function () { + var a = []; + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template', + events: { + 'click': function () { + a[0] = true; + strictEqual(this, w, "should trigger events in widget") + }, + 'click li.class-3': 'class3', + 'change input': function () { a[2] = true; } + }, + class3: function () { a[1] = true; } + })); + w.renderElement(); + + w.$el.click(); + w.$('li:eq(3)').click(); + w.$('input:last').val('foo').change(); + + for(var i=0; i<3; ++i) { + ok(a[i], "should pass test " + i); + } + }); + test('undelegate', function () { + var clicked = false, newclicked = false; + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template', + events: { 'click li': function () { clicked = true; } } + })); + w.renderElement(); + w.$el.on('click', 'li', function () { newclicked = true }); + + w.$('li').click(); + ok(clicked, "should trigger bound events"); + ok(newclicked, "should trigger bound events"); + clicked = newclicked = false; + + w.undelegateEvents(); + w.$('li').click(); + ok(!clicked, "undelegate should unbind events delegated"); + ok(newclicked, "undelegate should only unbind events it created"); + }); }); From b19354ae2005e83980a938c8e534fe55b5d1c14e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 14:54:54 +0200 Subject: [PATCH 11/89] [TEST] getValue/events as a callable bzr revid: xmo@openerp.com-20120611125454-nli5gkds9ffksmdm --- addons/web/static/test/Widget.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index d797120a341..36d6795ff7c 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -191,6 +191,17 @@ $(document).ready(function () { ok(a[i], "should pass test " + i); } }); + test('delegate(Function)', function () { + var clicked = false, fn = function () { + return { 'click li': function () { clicked = true; } }; }; + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template', + events: fn + })); + w.renderElement(); + w.$('li:first').click(); + ok(clicked, "should accept #events to be a function"); + }); test('undelegate', function () { var clicked = false, newclicked = false; var w = new (instance.web.Widget.extend({ From d6f376464516709c7b4ad7631ac1e49e4e39fe5e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 16:41:08 +0200 Subject: [PATCH 12/89] [REM] declarative elements binding, not sure there's much point and can add later bzr revid: xmo@openerp.com-20120611144108-pw8pz5p06prsikxe --- addons/web/static/src/js/corelib.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 83586ec57d4..e9fd4e1fedf 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -734,14 +734,12 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { setElement: function (element) { if (this.$el) { this.undelegateEvents(); - this.unbindElements(); this.$el.replaceWith(element); } this.$element = this.$el = (element instanceof $) ? element : $(element); this.el = this.$el[0]; - this.bindElements(); this.delegateEvents(); return this; @@ -796,28 +794,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { undelegateEvents: function () { this.$el.off('.delegated-events' + this._uid); }, - elements: {}, - bindElements: function () { - var elements = getValue(this, 'elements'); - if (_.isEmpty(elements)) { return; } - - for (var selector in elements) { - if (!elements.hasOwnProperty(selector)) { continue; } - - // TODO: ensure at least one element matched? - this[elements[selector]] = this.$(selector); - } - }, - unbindElements: function () { - var elements = getValue(this, 'elements'); - if (_.isEmpty(elements)) { return; } - - for (var selector in elements) { - if (!elements.hasOwnProperty(selector)) { continue; } - - delete this[elements[selector]]; - } - }, /** * Shortcut for ``this.$el.find(selector)`` * From d2f7ddb43eeb53973e7957fa9bccf3153d7f7bc5 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 11 Jun 2012 17:07:02 +0200 Subject: [PATCH 13/89] [ADD] doc for backbone-ish APIs bzr revid: xmo@openerp.com-20120611150702-r4c9kyxmu8m7g4c0 --- doc/addons.rst | 109 -------------------- doc/index.rst | 1 + doc/widget.rst | 270 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+), 109 deletions(-) create mode 100644 doc/widget.rst diff --git a/doc/addons.rst b/doc/addons.rst index da1f1f28769..35878b04430 100644 --- a/doc/addons.rst +++ b/doc/addons.rst @@ -113,103 +113,6 @@ initializing the addon. Creating new standard roles --------------------------- -Widget -++++++ - -This is the base class for all visual components. It provides a number of -services for the management of a DOM subtree: - -* Rendering with QWeb - -* Parenting-child relations - -* Life-cycle management (including facilitating children destruction when a - parent object is removed) - -* DOM insertion, via jQuery-powered insertion methods. Insertion targets can - be anything the corresponding jQuery method accepts (generally selectors, - DOM nodes and jQuery objects): - - :js:func:`~openerp.base.Widget.appendTo` - Renders the widget and inserts it as the last child of the target, uses - `.appendTo()`_ - - :js:func:`~openerp.base.Widget.prependTo` - Renders the widget and inserts it as the first child of the target, uses - `.prependTo()`_ - - :js:func:`~openerp.base.Widget.insertAfter` - Renders the widget and inserts it as the preceding sibling of the target, - uses `.insertAfter()`_ - - :js:func:`~openerp.base.Widget.insertBefore` - Renders the widget and inserts it as the following sibling of the target, - uses `.insertBefore()`_ - -:js:class:`~openerp.base.Widget` inherits from -:js:class:`~openerp.base.SessionAware`, so subclasses can easily access the -RPC layers. - -Subclassing Widget -~~~~~~~~~~~~~~~~~~ - -:js:class:`~openerp.base.Widget` is subclassed in the standard manner (via the -:js:func:`~openerp.base.Class.extend` method), and provides a number of -abstract properties and concrete methods (which you may or may not want to -override). Creating a subclass looks like this: - -.. code-block:: javascript - - var MyWidget = openerp.base.Widget.extend({ - // QWeb template to use when rendering the object - template: "MyQWebTemplate", - - init: function(parent) { - this._super(parent); - // insert code to execute before rendering, for object - // initialization - }, - start: function() { - this._super(); - // post-rendering initialization code, at this point - // ``this.$element`` has been initialized - this.$element.find(".my_button").click(/* an example of event binding * /); - - // if ``start`` is asynchronous, return a promise object so callers - // know when the object is done initializing - return this.rpc(/* … */) - } - }); - -The new class can then be used in the following manner: - -.. code-block:: javascript - - // Create the instance - var my_widget = new MyWidget(this); - // Render and insert into DOM - my_widget.appendTo(".some-div"); - -After these two lines have executed (and any promise returned by ``appendTo`` -has been resolved if needed), the widget is ready to be used. - -.. note:: the insertion methods will start the widget themselves, and will - return the result of :js:func:`~openerp.base.Widget.start()`. - - If for some reason you do not want to call these methods, you will - have to first call :js:func:`~openerp.base.Widget.render()` on the - widget, then insert it into your DOM and start it. - -If the widget is not needed anymore (because it's transient), simply terminate -it: - -.. code-block:: javascript - - my_widget.stop(); - -will unbind all DOM events, remove the widget's content from the DOM and -destroy all widget data. - Views +++++ @@ -541,18 +444,6 @@ Python .. _promise object: http://api.jquery.com/deferred.promise/ -.. _.appendTo(): - http://api.jquery.com/appendTo/ - -.. _.prependTo(): - http://api.jquery.com/prependTo/ - -.. _.insertAfter(): - http://api.jquery.com/insertAfter/ - -.. _.insertBefore(): - http://api.jquery.com/insertBefore/ - .. _Rosetta: .. _Launchpad's own translation tool: https://help.launchpad.net/Translations diff --git a/doc/index.rst b/doc/index.rst index 9f54b879acc..1f660426a88 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -16,6 +16,7 @@ Contents: async rpc + widget search-view Older stuff diff --git a/doc/widget.rst b/doc/widget.rst new file mode 100644 index 00000000000..9868e4030e0 --- /dev/null +++ b/doc/widget.rst @@ -0,0 +1,270 @@ +User Interaction: Widget +======================== + +This is the base class for all visual components. It corresponds to an MVC +view. It provides a number of services to handle a section of a page: + +* Rendering with QWeb + +* Parenting-child relations + +* Life-cycle management (including facilitating children destruction when a + parent object is removed) + +* DOM insertion, via jQuery-powered insertion methods. Insertion targets can + be anything the corresponding jQuery method accepts (generally selectors, + DOM nodes and jQuery objects): + + :js:func:`~openerp.base.Widget.appendTo` + Renders the widget and inserts it as the last child of the target, uses + `.appendTo()`_ + + :js:func:`~openerp.base.Widget.prependTo` + Renders the widget and inserts it as the first child of the target, uses + `.prependTo()`_ + + :js:func:`~openerp.base.Widget.insertAfter` + Renders the widget and inserts it as the preceding sibling of the target, + uses `.insertAfter()`_ + + :js:func:`~openerp.base.Widget.insertBefore` + Renders the widget and inserts it as the following sibling of the target, + uses `.insertBefore()`_ + +* Backbone-compatible shortcuts + +DOM Root +-------- + +A :js:class:`~openerp.web.Widget` is responsible for a section of the +page materialized by the DOM root of the widget. The DOM root is +available via the :js:attr:`~openerp.web.Widget.el` and +:js:attr:`~openerp.web.Widget.$el` attributes, which are respectively +the raw DOM Element and the jQuery wrapper around the DOM element. + +.. note:: + + both attributes are compatible with Backbone's equivalent, there + is also the :js:attr:`~openerp.web.Widget.$element` attribute + which aliases to :js:attr:`~openerp.web.Widget.$el` and remains + for backwards compatiblity reasons. + +There are two main ways to define and generate this DOM root: + +.. js:attribute:: openerp.web.Widget.template + + Should be set to the name of a QWeb template (a + :js:class:`String`). If set, the template will be rendered after + the widget has been initialized but before it has been + started. The root element generated by the template will be set as + the DOM root of the widget. + +.. js:attribute:: openerp.web.Widget.tagName + + Used if the widget has no template defined. Defaults to ``div``, + will be used as the tag name to create the DOM element to set as + the widget's DOM root. It is possible to further customize this + generated DOM root with the following attributes: + + .. js:attribute:: openerp.web.Widget.id + + Used to generate an ``id`` attribute on the generated DOM + root. + + .. js:attribute:: openerp.web.Widget.className + + Used to generate a ``class`` attribute on the generated DOM root. + + .. js:attribute:: openerp.web.Widget.attributes + + Mapping (object literal) of attribute names to attribute + values. Each of these k:v pairs will be set as a DOM attribute + on the generated DOM root. + + None of these is used in case a template is specified on the widget. + +The DOM root can also be defined programmatically by overridding + +.. js:function:: openerp.web.Widget.renderElement + + Renders the widget's DOM root and sets it. The default + implementation will render a set template or generate an element + as described above, and will call + :js:func:`~openerp.web.Widget.setElement` on the result. + + Any override to :js:func:`~openerp.web.Widget.renderElement` which + does not call its ``_super`` **must** call + :js:func:`~openerp.web.Widget.setElement` with whatever it + generated or the widget's behavior is undefined. + +Accessing DOM content +~~~~~~~~~~~~~~~~~~~~~ + +Because a widget is only responsible for the content below its DOM +root, there is a shortcut for selecting sub-sections of a widget's +DOM: + +.. js:function:: openerp.web.Widget.$(selector) + + Applies the CSS selector specified as parameter to the widget's + DOM root. + + .. code-block:: javascript + + this.$(selector); + + is functionally identical to: + + .. code-block:: javascript + + this.$element.find(selector); + + :param String selector: CSS selector + :returns: jQuery object + + .. note:: this helper method is compatible with + ``Backbone.View.$`` + +Resetting the DOM root +~~~~~~~~~~~~~~~~~~~~~~ + +.. js:function:: openerp.web.Widget.setElement(element) + + Re-sets the widget's DOM root to the provided element, also + handles re-setting the various aliases of the DOM root as well as + unsetting and re-setting delegated events. + + :param Element element: a DOM element or jQuery object to set as + the widget's DOM root + + .. note:: should be mostly compatible with `Backbone's + setElement`_ + +DOM events handling +------------------- + +A widget will generally need to respond to user action within its +section of the page. This entails binding events to DOM elements. + +To this end, :js:class:`~openerp.web.Widget` provides an shortcut: + +.. js:attribute:: openerp.web.Widget.events + + Events are a mapping of ``event selector`` (an event name and a + CSS selector separated by a space) to a callback. The callback can + be either a method name in the widget or a function. In either + case, the ``this`` will be set to the widget. + + The selector is used for jQuery's `event delegation`_, the + callback will only be triggered for descendants of the DOM root + matching the selector [0]_. If the selector is left out (only an + event name is specified), the event will be set directly on the + widget's DOM root. + +.. js:function:: openerp.web.Widget.delegateEvents + + This method is in charge of binding + :js:attr:`~openerp.web.Widget.events` to the DOM. It is + automatically called after setting the widget's DOM root. + + It can be overridden to set up more complex events than the + :js:attr:`~openerp.web.Widget.events` map allows, but the parent + should always be called (or :js:attr:`~openerp.web.Widget.events` + won't be handled correctly). + +.. js:function:: openerp.web.Widget.undelegateEvents + + This method is in charge of unbinding + :js:attr:`~openerp.web.Widget.events` from the DOM root when the + widget is destroyed or the DOM root is reset, in order to avoid + leaving "phantom" events. + + It should be overridden to un-set any event set in an override of + :js:func:`~openerp.web.Widget.delegateEvents`. + +.. note:: this behavior should be compatible with `Backbone's + delegateEvents`_, apart from not accepting any argument. + +Subclassing Widget +------------------ + +:js:class:`~openerp.base.Widget` is subclassed in the standard manner (via the +:js:func:`~openerp.base.Class.extend` method), and provides a number of +abstract properties and concrete methods (which you may or may not want to +override). Creating a subclass looks like this: + +.. code-block:: javascript + + var MyWidget = openerp.base.Widget.extend({ + // QWeb template to use when rendering the object + template: "MyQWebTemplate", + + init: function(parent) { + this._super(parent); + // insert code to execute before rendering, for object + // initialization + }, + start: function() { + this._super(); + // post-rendering initialization code, at this point + // ``this.$element`` has been initialized + this.$element.find(".my_button").click(/* an example of event binding * /); + + // if ``start`` is asynchronous, return a promise object so callers + // know when the object is done initializing + return this.rpc(/* … */) + } + }); + +The new class can then be used in the following manner: + +.. code-block:: javascript + + // Create the instance + var my_widget = new MyWidget(this); + // Render and insert into DOM + my_widget.appendTo(".some-div"); + +After these two lines have executed (and any promise returned by ``appendTo`` +has been resolved if needed), the widget is ready to be used. + +.. note:: the insertion methods will start the widget themselves, and will + return the result of :js:func:`~openerp.base.Widget.start()`. + + If for some reason you do not want to call these methods, you will + have to first call :js:func:`~openerp.base.Widget.render()` on the + widget, then insert it into your DOM and start it. + +If the widget is not needed anymore (because it's transient), simply terminate +it: + +.. code-block:: javascript + + my_widget.destroy(); + +will unbind all DOM events, remove the widget's content from the DOM and +destroy all widget data. + +.. [0] not all DOM events are compatible with events delegation + +.. _.appendTo(): + http://api.jquery.com/appendTo/ + +.. _.prependTo(): + http://api.jquery.com/prependTo/ + +.. _.insertAfter(): + http://api.jquery.com/insertAfter/ + +.. _.insertBefore(): + http://api.jquery.com/insertBefore/ + +.. _event delegation: + http://api.jquery.com/delegate/ + +.. _Backbone's setElement: + http://backbonejs.org/#View-setElement + +.. _Backbone's delegateEvents: + http://backbonejs.org/#View-delegateEvents + From f0ad5fedacc2e994ef8ba45b05113278ac72db95 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 12 Jun 2012 15:21:20 +0200 Subject: [PATCH 14/89] [IMP] move .replaceWith() back into renderElement * Better formalizes repeated calls to renderElement * Makes setElement cleaner and less surprising to call bzr revid: xmo@openerp.com-20120612132120-egsg4kr5n2riv4zk --- addons/web/static/src/js/corelib.js | 5 +++- addons/web/static/test/Widget.js | 42 ++++++++++++++++++++++------- doc/widget.rst | 12 ++++++++- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index e9fd4e1fedf..73fa6839607 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -716,7 +716,11 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { if (this.className) { attrs['class'] = this.className; } $el = $(this.make(this.tagName, attrs)) } + var $oldel = this.$el; this.setElement($el); + if ($oldel && !$oldel.is(this.$el)) { + $oldel.replaceWith(this.$el); + } }, /** @@ -734,7 +738,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { setElement: function (element) { if (this.$el) { this.undelegateEvents(); - this.$el.replaceWith(element); } this.$element = this.$el = (element instanceof $) ? element : $(element); diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index 36d6795ff7c..b50dd233a13 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -6,15 +6,21 @@ $(document).ready(function () { window.openerp.web.corelib(instance); instance.web.qweb = new QWeb2.Engine(); - instance.web.qweb.add_template('' + - '
      ' + - '
    1. ' + - '' + - '' + - '
    2. ' + - '
    ' + - '
    '); + instance.web.qweb.add_template( + '' + + '' + + '
      ' + + '
    1. ' + + '' + + '' + + '
    2. ' + + '
    ' + + '
    ' + + '' + + '

    ' + + '
    ' + + '
    '); } }; var instance; @@ -221,4 +227,22 @@ $(document).ready(function () { ok(!clicked, "undelegate should unbind events delegated"); ok(newclicked, "undelegate should only unbind events it created"); }); + + module('Widget.renderElement', mod); + test('repeated', function () { + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template-value' + })); + w.value = 42; + w.appendTo($fix) + .always(start) + .done(function () { + equal($fix.find('p').text(), '42', "DOM fixture should contain initial value"); + equal(w.$el.text(), '42', "should set initial value"); + w.value = 36; + w.renderElement(); + equal($fix.find('p').text(), '36', "DOM fixture should use new value"); + equal(w.$el.text(), '36', "should set new value"); + }); + }); }); diff --git a/doc/widget.rst b/doc/widget.rst index 9868e4030e0..b29260c5882 100644 --- a/doc/widget.rst +++ b/doc/widget.rst @@ -95,7 +95,17 @@ The DOM root can also be defined programmatically by overridding Any override to :js:func:`~openerp.web.Widget.renderElement` which does not call its ``_super`` **must** call :js:func:`~openerp.web.Widget.setElement` with whatever it - generated or the widget's behavior is undefined. + generated or the widget's behavior is undefined.r + + .. note:: + + The default :js:func:`~openerp.web.Widget.renderElement` can + be called repeatedly, it will *replace* the previous DOM root + (using ``replaceWith``). However, this requires that the + widget correctly sets and unsets its events (and children + widgets). Generally, + :js:func:`~openerp.web.Widget.renderElement` should not be + called repeatedly unless the widget advertizes this feature. Accessing DOM content ~~~~~~~~~~~~~~~~~~~~~ From 43fb6b3ee39999fd55a045a961372691b8926e58 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 17:25:58 +0200 Subject: [PATCH 15/89] [CHG] require that Widget#attributes be an object if present, not a function bzr revid: xmo@openerp.com-20120711152558-9jm096g7mowgfvf5 --- addons/web/static/src/js/corelib.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 73fa6839607..d71ff91247d 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -711,10 +711,10 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { $el = $(_.str.trim(instance.web.qweb.render( this.template, {widget: this}))); } else { - var attrs = _.extend({}, getValue(this, 'attributes') || {}); + var attrs = _.extend({}, this.attributes || {}); if (this.id) { attrs.id = this.id; } if (this.className) { attrs['class'] = this.className; } - $el = $(this.make(this.tagName, attrs)) + $el = $(this.make(this.tagName, attrs)); } var $oldel = this.$el; this.setElement($el); From a6e85cfd5a533e5d9ff83121ae901978029bc0b5 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sat, 21 Jul 2012 22:12:53 +0200 Subject: [PATCH 16/89] [REM] setValue, event namespacing bzr revid: al@openerp.com-20120721201253-z35xjhao0r1mv6l1 --- addons/web/static/src/js/corelib.js | 35 ++++++++--------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 5ecd6dbb07d..dcaaa864fc8 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -237,20 +237,6 @@ instance.web.ParentedMixin = { } }; -/** - * Utility function for APIs which can take either a value or a function which - * returns a value - * - * @param {Object} object - * @param {String} property - * @returns {Object} - */ -var getValue = function (object, property) { - if (object == null) { return null; } - var value = object[property]; - return _.isFunction(value) ? value.call(object) : value; -}; - /** * Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead. * @@ -677,6 +663,12 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn * That will kill the widget in a clean way and erase its content from the dom. */ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { + // Backbone-ish API + tagName: 'div', + id: null, + className: null, + attributes: {}, + events: {}, /** * The name of the QWeb template that will be used for rendering. Must be * redefined in subclasses or the default render() method can not be used. @@ -695,7 +687,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * destroyed too. Can be null. */ init: function(parent) { - this._uid = _.uniqueId('-widget-'); instance.web.WidgetMixin.init.call(this,parent); this.session = instance.connection; }, @@ -746,12 +737,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { return this; }, - - // Backbone-ish API - tagName: 'div', - id: null, - className: null, - attributes: {}, /** * Utility function to build small DOM elements. * @@ -770,9 +755,8 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { } return el; }, - events: {}, delegateEvents: function () { - var events = getValue(this, 'events'); + var events = this.events; if (_.isEmpty(events)) { return; } for(var key in events) { @@ -784,8 +768,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { var event = match[1]; var selector = match[3]; - // add namespace for easy un-delegation - event += '.delegated-events' + this._uid; + event += '.delegated-events'; if (!selector) { this.$el.on(event, method); } else { @@ -794,7 +777,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { } }, undelegateEvents: function () { - this.$el.off('.delegated-events' + this._uid); + this.$el.off('.delegated-events'); }, /** * Shortcut for ``this.$el.find(selector)`` From 974d4f4f106762dafb82b6d6f7a7a43fd6cdacdb Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Wed, 25 Jul 2012 10:24:46 +0200 Subject: [PATCH 17/89] rename widget event namespace bzr revid: al@openerp.com-20120725082446-eahrub8qigektert --- addons/web/static/src/js/corelib.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index dcaaa864fc8..6c7fd539b38 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -768,7 +768,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { var event = match[1]; var selector = match[3]; - event += '.delegated-events'; + event += '.widget_events'; if (!selector) { this.$el.on(event, method); } else { @@ -777,7 +777,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { } }, undelegateEvents: function () { - this.$el.off('.delegated-events'); + this.$el.off('.widget_events'); }, /** * Shortcut for ``this.$el.find(selector)`` From 0310d5ae73b34ded9c67c3a4b7423afabccd5630 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 25 Jul 2012 11:13:31 +0200 Subject: [PATCH 18/89] [REM] #events can't be a function anymore bzr revid: xmo@openerp.com-20120725091331-jvhgyf427cuxbwl4 --- addons/web/static/test/Widget.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index b50dd233a13..99bae019ae4 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -197,17 +197,6 @@ $(document).ready(function () { ok(a[i], "should pass test " + i); } }); - test('delegate(Function)', function () { - var clicked = false, fn = function () { - return { 'click li': function () { clicked = true; } }; }; - var w = new (instance.web.Widget.extend({ - template: 'test.widget.template', - events: fn - })); - w.renderElement(); - w.$('li:first').click(); - ok(clicked, "should accept #events to be a function"); - }); test('undelegate', function () { var clicked = false, newclicked = false; var w = new (instance.web.Widget.extend({ From 9cc2f4fb2553e7c327cbe97e49ab693813500c4b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 25 Jul 2012 11:19:16 +0200 Subject: [PATCH 19/89] [IMP] extract 'set element as new widget root and replace old one in DOM' in its own method also fix OldWidget to use it, as it had not been fixed when #replaceWith was extracted out of setElement bzr revid: xmo@openerp.com-20120725091916-g9t5qmaeeqcceuk1 --- addons/web/static/src/js/corelib.js | 15 +++++++++++++-- addons/web/static/src/js/coresetup.js | 3 +-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 6c7fd539b38..42e549b5ca1 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -706,13 +706,24 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { if (this.className) { attrs['class'] = this.className; } $el = $(this.make(this.tagName, attrs)); } + 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/$element). * @@ -723,7 +734,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * element in the DOM * * @param {HTMLElement | jQuery} element new root element for the widget - * @return {*} + * @return {*} this */ setElement: function (element) { if (this.$el) { diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index c9015220ba7..b86b0534e3b 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -26,8 +26,7 @@ instance.web.OldWidget = instance.web.Widget.extend({ renderElement: function() { var rendered = this.render(); if (rendered) { - var $el = $(rendered); - this.setElement($el); + this.replaceElement($(rendered)); } return this; }, From 0924a29e7aa8d673bbeb2b57237187b4a24d63ed Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 25 Jul 2012 11:34:37 +0200 Subject: [PATCH 20/89] [FIX] backbonify Client bzr revid: chs@openerp.com-20120725093437-hig5jtkdol9uagca --- addons/web/static/src/js/chrome.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index b5c69f5c82f..6b3f5eecdb8 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -58,7 +58,7 @@ instance.web.Dialog = instance.web.Widget.extend({ var self = this; this._super(parent); if (content) { - this.$element = content instanceof $ ? content : $(content); + this.setElement(content); } this.dialog_options = { modal: true, @@ -879,8 +879,7 @@ instance.web.Client = instance.web.Widget.extend({ var self = this; return instance.connection.session_bind(this.origin).then(function() { var $e = $(QWeb.render(self._template, {})); - self.$element.replaceWith($e); - self.$element = $e; + self.replaceElement($e); self.bind_events(); self.show_common(); }); From d304f9fbb7765be660ac54d47c5ed2cba6719880 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 09:53:59 +0200 Subject: [PATCH 21/89] [CHG] rename this. to this. because why make things simple when you can make them fucked up bzr revid: xmo@openerp.com-20120726075359-b0uvuuagrfsf7nhb --- addons/web/static/src/js/corelib.js | 36 +++++++++++++-------------- addons/web/static/test/Widget.js | 38 ++++++++++++++--------------- doc/widget.rst | 4 +-- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 42e549b5ca1..b80543a34d8 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -523,8 +523,8 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { _.each(this.getChildren(), function(el) { el.destroy(); }); - if(this.$el) { - this.$el.remove(); + if(this.$element) { + this.$element.remove(); } instance.web.PropertiesMixin.destroy.call(this); }, @@ -536,7 +536,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { appendTo: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$el.appendTo(t); + self.$element.appendTo(t); }, target); }, /** @@ -547,7 +547,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { prependTo: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$el.prependTo(t); + self.$element.prependTo(t); }, target); }, /** @@ -558,7 +558,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { insertAfter: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$el.insertAfter(t); + self.$element.insertAfter(t); }, target); }, /** @@ -569,7 +569,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { insertBefore: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$el.insertBefore(t); + self.$element.insertBefore(t); }, target); }, /** @@ -579,7 +579,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { */ replace: function(target) { return this.__widgetRenderAndInsert(_.bind(function(t) { - this.$el.replaceAll(t); + this.$element.replaceAll(t); }, this), target); }, __widgetRenderAndInsert: function(insertion, target) { @@ -717,10 +717,10 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * @returns {*} this */ replaceElement: function ($el) { - var $oldel = this.$el; + var $oldel = this.$element; this.setElement($el); - if ($oldel && !$oldel.is(this.$el)) { - $oldel.replaceWith(this.$el); + if ($oldel && !$oldel.is(this.$element)) { + $oldel.replaceWith(this.$element); } return this; }, @@ -737,12 +737,12 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * @return {*} this */ setElement: function (element) { - if (this.$el) { + if (this.$element) { this.undelegateEvents(); } - this.$element = this.$el = (element instanceof $) ? element : $(element); - this.el = this.$el[0]; + this.$element = (element instanceof $) ? element : $(element); + this.el = this.$element[0]; this.delegateEvents(); @@ -781,23 +781,23 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { event += '.widget_events'; if (!selector) { - this.$el.on(event, method); + this.$element.on(event, method); } else { - this.$el.on(event, selector, method); + this.$element.on(event, selector, method); } } }, undelegateEvents: function () { - this.$el.off('.widget_events'); + this.$element.off('.widget_events'); }, /** - * Shortcut for ``this.$el.find(selector)`` + * Shortcut for ``this.$element.find(selector)`` * * @param {String} selector CSS selector, rooted in $el * @returns {jQuery} selector match */ $: function(selector) { - return this.$el.find(selector); + return this.$element.find(selector); }, /** * Informs the action manager to do an action. This supposes that diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js index 99bae019ae4..55d9e7e7ad1 100644 --- a/addons/web/static/test/Widget.js +++ b/addons/web/static/test/Widget.js @@ -86,15 +86,15 @@ $(document).ready(function () { test('no template, default', function () { var w = new (instance.web.Widget.extend({ })); - ok(!w.$el, "should not initially have a root element"); + ok(!w.$element, "should not initially have a root element"); w.renderElement(); - ok(w.$el, "should have generated a root element"); - strictEqual(w.$element, w.$el, "should provide $element alias"); - ok(w.$el.is(w.el), "should provide raw DOM alias"); + ok(w.$element, "should have generated a root element"); + strictEqual(w.$element, w.$element, "should provide $element alias"); + ok(w.$element.is(w.el), "should provide raw DOM alias"); equal(w.el.nodeName, 'DIV', "should have generated the default element"); equal(w.el.attributes.length, 0, "should not have generated any attribute"); - ok(_.isEmpty(w.$el.html(), "should not have generated any content")); + ok(_.isEmpty(w.$element.html(), "should not have generated any content")); }); test('no template, custom tag', function () { var w = new (instance.web.Widget.extend({ @@ -111,7 +111,7 @@ $(document).ready(function () { w.renderElement(); equal(w.el.attributes.length, 1, "should have one attribute"); - equal(w.$el.attr('id'), 'foo', "should have generated the id attribute"); + equal(w.$element.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 () { @@ -121,7 +121,7 @@ $(document).ready(function () { w.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"); + equal(w.$element.attr('class'), 'oe_some_class', "should have the right attribute"); }); test('no template, bunch of attributes', function () { var w = new (instance.web.Widget.extend({ @@ -138,16 +138,16 @@ $(document).ready(function () { equal(w.el.attributes.length, 5, "should have all the specified attributes"); equal(w.el.id, 'some_id'); - equal(w.$el.attr('id'), 'some_id'); + equal(w.$element.attr('id'), 'some_id'); equal(w.el.className, 'some_class'); - equal(w.$el.attr('class'), 'some_class'); + equal(w.$element.attr('class'), 'some_class'); - equal(w.$el.attr('data-foo'), 'data attribute'); - equal(w.$el.data('foo'), 'data attribute'); + equal(w.$element.attr('data-foo'), 'data attribute'); + equal(w.$element.data('foo'), 'data attribute'); - equal(w.$el.attr('clark'), 'gable'); - equal(w.$el.attr('spoiler'), 'snape kills dumbledore'); + equal(w.$element.attr('clark'), 'gable'); + equal(w.$element.attr('spoiler'), 'snape kills dumbledore'); }); test('template', function () { @@ -157,7 +157,7 @@ $(document).ready(function () { w.renderElement(); equal(w.el.nodeName, 'OL'); - equal(w.$el.children().length, 5); + equal(w.$element.children().length, 5); equal(w.el.textContent, '01234'); }); @@ -168,7 +168,7 @@ $(document).ready(function () { })); w.renderElement(); - ok(w.$('li:eq(3)').is(w.$el.find('li:eq(3)')), + ok(w.$('li:eq(3)').is(w.$element.find('li:eq(3)')), "should do the same thing as calling find on the widget root"); }); @@ -189,7 +189,7 @@ $(document).ready(function () { })); w.renderElement(); - w.$el.click(); + w.$element.click(); w.$('li:eq(3)').click(); w.$('input:last').val('foo').change(); @@ -204,7 +204,7 @@ $(document).ready(function () { events: { 'click li': function () { clicked = true; } } })); w.renderElement(); - w.$el.on('click', 'li', function () { newclicked = true }); + w.$element.on('click', 'li', function () { newclicked = true }); w.$('li').click(); ok(clicked, "should trigger bound events"); @@ -227,11 +227,11 @@ $(document).ready(function () { .always(start) .done(function () { equal($fix.find('p').text(), '42', "DOM fixture should contain initial value"); - equal(w.$el.text(), '42', "should set initial value"); + equal(w.$element.text(), '42', "should set initial value"); w.value = 36; w.renderElement(); equal($fix.find('p').text(), '36', "DOM fixture should use new value"); - equal(w.$el.text(), '36', "should set new value"); + equal(w.$element.text(), '36', "should set new value"); }); }); }); diff --git a/doc/widget.rst b/doc/widget.rst index b29260c5882..02332382ce9 100644 --- a/doc/widget.rst +++ b/doc/widget.rst @@ -39,14 +39,14 @@ DOM Root A :js:class:`~openerp.web.Widget` is responsible for a section of the page materialized by the DOM root of the widget. The DOM root is available via the :js:attr:`~openerp.web.Widget.el` and -:js:attr:`~openerp.web.Widget.$el` attributes, which are respectively +:js:attr:`~openerp.web.Widget.$element` attributes, which are respectively the raw DOM Element and the jQuery wrapper around the DOM element. .. note:: both attributes are compatible with Backbone's equivalent, there is also the :js:attr:`~openerp.web.Widget.$element` attribute - which aliases to :js:attr:`~openerp.web.Widget.$el` and remains + which aliases to :js:attr:`~openerp.web.Widget.$element` and remains for backwards compatiblity reasons. There are two main ways to define and generate this DOM root: From 239131a71667408dafbae418bd6a95d3d1f26858 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 10:12:34 +0200 Subject: [PATCH 22/89] [FIX] login widget initialization sequence: don't go mucking around with the login's before it's even been started bzr revid: xmo@openerp.com-20120726081234-2lrq3ksrauxc4m3z --- addons/web/static/src/js/chrome.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 6b3f5eecdb8..c2ab78d0caf 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -57,9 +57,7 @@ instance.web.Dialog = instance.web.Widget.extend({ init: function (parent, options, content) { var self = this; this._super(parent); - if (content) { - this.setElement(content); - } + this.setElement(content || this.make(this.tagName)); this.dialog_options = { modal: true, destroy_on_close: true, @@ -587,6 +585,12 @@ instance.web.Login = instance.web.Widget.extend({ self.$(".oe_login_pane").fadeIn("fast"); self.$element.addClass("oe_login_invalid"); }); + }, + show: function () { + this.$element.show(); + }, + hide: function () { + this.$element.hide(); } }); @@ -877,11 +881,11 @@ instance.web.Client = instance.web.Widget.extend({ }, start: function() { var self = this; - return instance.connection.session_bind(this.origin).then(function() { + return instance.connection.session_bind(this.origin).pipe(function() { var $e = $(QWeb.render(self._template, {})); self.replaceElement($e); self.bind_events(); - self.show_common(); + return self.show_common(); }); }, bind_events: function() { @@ -966,16 +970,19 @@ instance.web.WebClient = instance.web.Client.extend({ // TODO: deprecate and use login client action self.login = new instance.web.Login(self); self.login.on("login",self,self.show_application); + return self.login.appendTo(self.$element).then(function () { + self.login.hide(); + }); }, show_login: function() { var self = this; self.$('.oe_topbar').hide(); - self.login.appendTo(self.$element); + self.login.show(); }, show_application: function() { var self = this; self.$('.oe_topbar').show(); - self.login.$element.hide(); + self.login.hide(); self.menu = new instance.web.Menu(self); self.menu.replace(this.$element.find('.oe_menu_placeholder')); self.menu.on('menu_click', this, this.on_menu_action); From a29023b3f2a253140d6eb6c158df9a910eb11979 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:30:26 +0200 Subject: [PATCH 23/89] [FIX] avoid fucking up ViewManager#start in process: correctly return _super() result bzr revid: xmo@openerp.com-20120726143026-ttu6x6zq2vp106s9 --- addons/web_process/static/src/js/process.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web_process/static/src/js/process.js b/addons/web_process/static/src/js/process.js index bb32d300be8..60259f26b64 100644 --- a/addons/web_process/static/src/js/process.js +++ b/addons/web_process/static/src/js/process.js @@ -3,12 +3,13 @@ openerp.web_process = function (instance) { _t = instance.web._t; instance.web.ViewManager.include({ start: function() { - this._super(); + var _super = this._super(); this.process_check(); this.process_help = this.action ? this.action.help : 'Help: Not Defined'; this.model = this.dataset.model; if(this.action) this.process_model = this.action.res_model; else this.process_model = this.model; + return _super; }, process_check: function() { var self = this, From 18afaaa1c13c27145977d2b374814490230d6c09 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:31:05 +0200 Subject: [PATCH 24/89] [FIX] correctly call renderElement _super in dashboard bzr revid: xmo@openerp.com-20120726143105-w817zv5o3ihvmjj5 --- addons/web_dashboard/static/src/js/dashboard.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/web_dashboard/static/src/js/dashboard.js b/addons/web_dashboard/static/src/js/dashboard.js index a19151af33c..fea775d4ee8 100644 --- a/addons/web_dashboard/static/src/js/dashboard.js +++ b/addons/web_dashboard/static/src/js/dashboard.js @@ -226,6 +226,8 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ } }, renderElement: function() { + this._super(); + var check = _.detect(this.node.children, function(column, column_index) { return _.detect(column.children,function(element){ return element.tag === "action"? element: false; From 7ccf2ad2a5779dac1175c0191778ff9ee69eb7fb Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:31:41 +0200 Subject: [PATCH 25/89] [FIX] if value may not be a promise, return the wrapped value if wrapping it anyway bzr revid: xmo@openerp.com-20120726143141-4wbrqq29qqhvwhqw --- addons/web/static/src/js/views.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index ddf4050fa38..5af7e5bf616 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -396,7 +396,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]') .parent().addClass('active'); - $.when(view_promise).then(function () { + return $.when(view_promise).then(function () { _.each(_.keys(self.views), function(view_name) { var controller = self.views[view_name].controller; if (controller) { @@ -418,7 +418,6 @@ instance.web.ViewManager = instance.web.Widget.extend({ } }); }); - return view_promise; }, do_create_view: function(view_type) { // Lazy loading of views @@ -1129,7 +1128,7 @@ instance.web.View = instance.web.Widget.extend({ this.view_id = view_id; this.set_default_options(options); }, - start: function() { + start: function () { return this.load_view(); }, load_view: function() { From de1f6488a7ada9f2a1cd24c8f56d07af7ca53019 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:33:20 +0200 Subject: [PATCH 26/89] [CHG] there always is an editor in the editable listview, destroy just before creating the new one bzr revid: xmo@openerp.com-20120726143320-4cdze45po1g581lu --- addons/web/static/src/js/view_list_editable.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index c7654479864..85e9e5a402e 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -96,9 +96,6 @@ openerp.web.list_editable = function (instance) { }, on_loaded: function (data, grouped) { var self = this; - if (this.editor) { - this.editor.destroy(); - } // tree/@editable takes priority on everything else if present. var result = this._super(data, grouped); if (this.editable()) { @@ -118,6 +115,7 @@ openerp.web.list_editable = function (instance) { self.start_edition(); } }); + this.editor.destroy(); // Editor is not restartable due to formview not being // restartable this.editor = this.make_editor(); From 3e4262266f99af4c927f60984108110a2f7e7f06 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:43:09 +0200 Subject: [PATCH 27/89] [REM] unused and useless line bzr revid: xmo@openerp.com-20120726144309-olaini3pgjvb1j4j --- addons/web/static/src/js/view_form.js | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 835527b3135..d38494112e5 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3016,7 +3016,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.views = views; this.viewmanager = new instance.web.form.One2ManyViewManager(this, this.dataset, views, {}); - this.viewmanager.$element.addClass("oe_view_manager_one2many"); this.viewmanager.o2m = self; var once = $.Deferred().then(function() { self.init_form_last_update.resolve(); From 9cde11f63d3fb3ac7a2c0d782c159302be2f95ed Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:44:20 +0200 Subject: [PATCH 28/89] [FIX] object may or may not have been started when destroy is run bzr revid: xmo@openerp.com-20120726144420-7coh354993kj546a --- addons/web/static/src/js/view_form.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index d38494112e5..ca0f2b77082 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -88,7 +88,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM w.off('focused blurred'); w.destroy(); }); - this.$element.off('.formBlur'); + if (this.$element) { + this.$element.off('.formBlur'); + } this._super(); }, on_loaded: function(data) { From a9ccdfae0305afa988284e59dc1a81694e0a26e3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:45:33 +0200 Subject: [PATCH 29/89] [FIX] use Widget#setElement and Widget#replaceElement rather than direct setting bzr revid: xmo@openerp.com-20120726144533-zev1o8kpugn1mkcz --- addons/web/static/src/js/view_form.js | 4 ++-- addons/web_kanban/static/src/js/kanban.js | 4 +--- addons/web_mobile/static/src/js/chrome_mobile.js | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index ca0f2b77082..5378e74a1a4 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -4216,7 +4216,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan this.selection.view = this.view; this.selection.set({force_readonly: this.get('effective_readonly')}); this.selection.on("change:value", this, this.on_selection_changed); - this.selection.$element = $(".oe_form_view_reference_selection", this.$element); + this.selection.setElement(this.$(".oe_form_view_reference_selection")); this.selection.renderElement(); this.selection.start(); this.selection @@ -4229,7 +4229,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan this.m2o.view = this.view; this.m2o.set({force_readonly: this.get("effective_readonly")}); this.m2o.on("change:value", this, this.data_changed); - this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element); + this.m2o.setElement(this.$(".oe_form_view_reference_m2o")); this.m2o.renderElement(); this.m2o.start(); this.m2o diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 5a22b9e5ba5..50360683c14 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -711,9 +711,7 @@ instance.web_kanban.KanbanRecord = instance.web.OldWidget.extend({ this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).then(function(records) { if (records.length) { self.set_record(records[0]); - var $render = $(self.render()); - self.$element.replaceWith($render); - self.$element = $render; + this.replaceElement($(self.render())); self.$element.data('widget', self); self.bind_events(); self.group.compute_cards_auto_height(); diff --git a/addons/web_mobile/static/src/js/chrome_mobile.js b/addons/web_mobile/static/src/js/chrome_mobile.js index a507e3b5943..029e4bce49b 100644 --- a/addons/web_mobile/static/src/js/chrome_mobile.js +++ b/addons/web_mobile/static/src/js/chrome_mobile.js @@ -48,8 +48,7 @@ instance.web_mobile.Login = instance.web.OldWidget.extend({ jQuery("#oe_header").children().remove(); this.rpc("/web/database/get_list", {}, function(result) { self.db_list = result.db_list; - $('#'+self.element_id).html(self.render(self)); - self.$element = $('#'+self.element_id); + this.setElement($('#'+self.element_id).html(self.render(self))); if(self.session.db!=""){ self.$element.find("#database").val(self.session.db); } From 5c0324f3214d95c3a2d34db7d6d145700501c762 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:48:17 +0200 Subject: [PATCH 30/89] [IMP] Widget#setElement from init to reinstate $element always being there That makes precious little sense, but a bunch of widgets actually expect $element to exist before start() has been called. Also extract creating a node from the widget's node description into a private method, use that where possible. bzr revid: xmo@openerp.com-20120726144817-20caa8jkvg39ycho --- addons/web/static/src/js/corelib.js | 21 +++++++++++++++++---- addons/web/static/src/js/coresetup.js | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index b80543a34d8..3fafcab26fb 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -514,6 +514,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { */ init: function(parent) { instance.web.CallbackEnabledMixin.init.call(this); + this.setElement(this._make_descriptive()); this.setParent(parent); }, /** @@ -701,10 +702,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { $el = $(_.str.trim(instance.web.qweb.render( this.template, {widget: this}))); } else { - var attrs = _.extend({}, this.attributes || {}); - if (this.id) { attrs.id = this.id; } - if (this.className) { attrs['class'] = this.className; } - $el = $(this.make(this.tagName, attrs)); + $el = this._make_descriptive(); } this.replaceElement($el); }, @@ -737,6 +735,8 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * @return {*} this */ setElement: function (element) { + // NB: completely useless, as WidgetMixin#init creates a $element + // always if (this.$element) { this.undelegateEvents(); } @@ -766,6 +766,19 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { } 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; } diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index b86b0534e3b..181364ba533 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -21,7 +21,7 @@ instance.web.OldWidget = instance.web.Widget.extend({ this.element_id = this.element_id || _.uniqueId('widget-'); var tmp = document.getElementById(this.element_id); - this.setElement(tmp || this.make(this.tagName)); + this.setElement(tmp || this._make_descriptive()); }, renderElement: function() { var rendered = this.render(); From 5fe4ff9c49e741c607ae33279552c6ae492f87ab Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:57:04 +0200 Subject: [PATCH 31/89] [FIX] move setElement init call from WidgetMixin to Widget Widget is the one providing both setElement and _make_descriptive... bzr revid: xmo@openerp.com-20120726145704-uzg7ufntvf24gnyd --- addons/web/static/src/js/corelib.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 3fafcab26fb..370fe74e63e 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -514,7 +514,6 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { */ init: function(parent) { instance.web.CallbackEnabledMixin.init.call(this); - this.setElement(this._make_descriptive()); this.setParent(parent); }, /** @@ -689,6 +688,8 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { */ init: function(parent) { instance.web.WidgetMixin.init.call(this,parent); + // FIXME: this should not be + this.setElement(this._make_descriptive()); this.session = instance.connection; }, /** From 7285d434088289f9f24c0836ba7da614528a182d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:57:15 +0200 Subject: [PATCH 32/89] [IMP] APIs usage of calendar bzr revid: xmo@openerp.com-20120726145715-5q1zu2gwmv8uvou5 --- addons/web_calendar/static/src/js/calendar.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/addons/web_calendar/static/src/js/calendar.js b/addons/web_calendar/static/src/js/calendar.js index 59ca58bec86..ae92585f67e 100644 --- a/addons/web_calendar/static/src/js/calendar.js +++ b/addons/web_calendar/static/src/js/calendar.js @@ -492,15 +492,17 @@ instance.web_calendar.Sidebar = instance.web.Widget.extend({ } }); instance.web_calendar.SidebarFilter = instance.web.Widget.extend({ + events: { + 'change input:checkbox': 'on_filter_click' + }, init: function(parent, view) { this._super(parent); this.view = view; - this.$element.delegate('input:checkbox', 'change', this.on_filter_click); }, on_events_loaded: function(filters) { var selected_filters = this.view.selected_filters.slice(0); this.$element.html(QWeb.render('CalendarView.sidebar.responsible', { filters: filters })); - this.$element.find('div.oe_calendar_responsible input').each(function() { + this.$('div.oe_calendar_responsible input').each(function() { if (_.indexOf(selected_filters, $(this).val()) > -1) { $(this).click(); } @@ -511,7 +513,7 @@ instance.web_calendar.SidebarFilter = instance.web.Widget.extend({ responsibles = [], $e = $(e.target); this.view.selected_filters = []; - this.$element.find('div.oe_calendar_responsible input:checked').each(function() { + this.$('div.oe_calendar_responsible input:checked').each(function() { responsibles.push($(this).val()); self.view.selected_filters.push($(this).val()); }); From eafee2f60265a89567cf56d9b9b5161f93fbe663 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Jul 2012 16:59:10 +0200 Subject: [PATCH 33/89] [FUCK] remove note which makes no sense anymore bzr revid: xmo@openerp.com-20120726145910-nn8vvu42n9634ycu --- doc/widget.rst | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/doc/widget.rst b/doc/widget.rst index 02332382ce9..ddfe525227d 100644 --- a/doc/widget.rst +++ b/doc/widget.rst @@ -39,15 +39,9 @@ DOM Root A :js:class:`~openerp.web.Widget` is responsible for a section of the page materialized by the DOM root of the widget. The DOM root is available via the :js:attr:`~openerp.web.Widget.el` and -:js:attr:`~openerp.web.Widget.$element` attributes, which are respectively -the raw DOM Element and the jQuery wrapper around the DOM element. - -.. note:: - - both attributes are compatible with Backbone's equivalent, there - is also the :js:attr:`~openerp.web.Widget.$element` attribute - which aliases to :js:attr:`~openerp.web.Widget.$element` and remains - for backwards compatiblity reasons. +:js:attr:`~openerp.web.Widget.$element` attributes, which are +respectively the raw DOM Element and the jQuery wrapper around the DOM +element. There are two main ways to define and generate this DOM root: From 8e5a0fa9b07396dffbe90acbad8e98f6b1148f69 Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Mon, 30 Jul 2012 14:48:15 +0530 Subject: [PATCH 34/89] [IMP]removed view editor from web to web addons. bzr revid: vme@tinyerp.com-20120730091815-scj8dt3j1tr1jpvx --- addons/web/static/src/css/base.css | 21 ------ addons/web/static/src/css/base.sass | 19 ------ addons/web/static/src/js/boot.js | 2 +- addons/web/static/src/js/views.js | 2 +- addons/web/static/src/xml/base.xml | 64 ------------------ addons/web_view_editor/__init__.py | 1 + addons/web_view_editor/__openerp__.py | 14 ++++ .../web_view_editor/i18n/web_view_editor.pot | 19 ++++++ .../static/src/css/view_editor.css | 21 ++++++ .../static/src/js/view_editor.js | 32 ++++----- .../static/src/xml/view_editor.xml | 66 +++++++++++++++++++ 11 files changed, 139 insertions(+), 122 deletions(-) create mode 100644 addons/web_view_editor/__init__.py create mode 100644 addons/web_view_editor/__openerp__.py create mode 100644 addons/web_view_editor/i18n/web_view_editor.pot create mode 100644 addons/web_view_editor/static/src/css/view_editor.css rename addons/{web => web_view_editor}/static/src/js/view_editor.js (97%) create mode 100644 addons/web_view_editor/static/src/xml/view_editor.xml diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 1e59897cd86..ac04fb4d1e1 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2474,27 +2474,6 @@ .openerp .oe_trad_field.touched { border: 1px solid green !important; } -.openerp .oe_view_editor { - width: 100%; - border-collapse: collapse; - margin-left: -12px; - width: 100%; - background-color: white; - border-spacing: 0; -} -.openerp .oe_view_editor td { - text-align: center; - white-space: nowrap; - border: 1px solid #d8d8d8; - cursor: pointer; - font-size: 90%; -} -.openerp .oe_view_editor_field td { - border: 0px !important; -} -.openerp .oe_view_editor tr:hover { - background-color: #ecebf2; -} .openerp .oe_layout_debugging .oe_form_group { outline: 2px dashed green; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 3bb52497454..2698787c5a3 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1929,25 +1929,6 @@ $sheet-max-width: 860px .oe_trad_field.touched border: 1px solid green !important // }}} - // View Editor {{{ - .oe_view_editor - width: 100% - border-collapse: collapse - margin-left: -12px - width: 100% - background-color: white - border-spacing: 0 - td - text-align: center - white-space: nowrap - border: 1px solid #D8D8D8 - cursor: pointer - font-size: 90% - .oe_view_editor_field td - border: 0px !important - .oe_view_editor tr:hover - background-color: #ecebf2 - // }}} // Debugging stuff {{{ .oe_layout_debugging .oe_form_group diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index c1b1b984abe..844dd88a0ac 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -48,7 +48,7 @@ * OpenERP Web web module split *---------------------------------------------------------*/ openerp.web = function(session) { - var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"]; + var files = ["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
    - - - -
    -
    - - - - - - - - -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Export diff --git a/addons/web_view_editor/__init__.py b/addons/web_view_editor/__init__.py new file mode 100644 index 00000000000..40a96afc6ff --- /dev/null +++ b/addons/web_view_editor/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/addons/web_view_editor/__openerp__.py b/addons/web_view_editor/__openerp__.py new file mode 100644 index 00000000000..317a6cbc367 --- /dev/null +++ b/addons/web_view_editor/__openerp__.py @@ -0,0 +1,14 @@ +{ + "name": "View Editor", + "category": "Hidden", + "description": + """ + OpenERP Web to edit views. + """, + "version": "2.0", + "depends":['web'], + "js": ["static/src/js/view_editor.js"], + "css": ['static/src/css/view_editor.css'], + "qweb": ['static/src/xml/view_editor.xml'], + 'auto_install': True, +} diff --git a/addons/web_view_editor/i18n/web_view_editor.pot b/addons/web_view_editor/i18n/web_view_editor.pot new file mode 100644 index 00000000000..fa48d3e6909 --- /dev/null +++ b/addons/web_view_editor/i18n/web_view_editor.pot @@ -0,0 +1,19 @@ +# Translations template for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-07-30 14:37+0530\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + diff --git a/addons/web_view_editor/static/src/css/view_editor.css b/addons/web_view_editor/static/src/css/view_editor.css new file mode 100644 index 00000000000..e80e15a4c17 --- /dev/null +++ b/addons/web_view_editor/static/src/css/view_editor.css @@ -0,0 +1,21 @@ +.openerp .oe_view_editor { + width: 100%; + border-collapse: collapse; + margin-left: -12px; + width: 100%; + background-color: white; + border-spacing: 0; +} +.openerp .oe_view_editor td { + text-align: center; + white-space: nowrap; + border: 1px solid #d8d8d8; + cursor: pointer; + font-size: 90%; +} +.openerp .oe_view_editor_field td { + border: 0px !important; +} +.openerp .oe_view_editor tr:hover { + background-color: #ecebf2; +} diff --git a/addons/web/static/src/js/view_editor.js b/addons/web_view_editor/static/src/js/view_editor.js similarity index 97% rename from addons/web/static/src/js/view_editor.js rename to addons/web_view_editor/static/src/js/view_editor.js index 6df78679ecb..519f6bce3ee 100644 --- a/addons/web/static/src/js/view_editor.js +++ b/addons/web_view_editor/static/src/js/view_editor.js @@ -1,7 +1,7 @@ -openerp.web.view_editor = function(instance) { +openerp.web_view_editor = function(instance) { var _t = instance.web._t; var QWeb = instance.web.qweb; -instance.web.ViewEditor = instance.web.OldWidget.extend({ +instance.web_view_editor.ViewEditor = instance.web.OldWidget.extend({ init: function(parent, element_id, dataset, view, options) { this._super(parent); this.element_id = element_id; @@ -9,7 +9,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({ this.dataset = new instance.web.DataSetSearch(this, 'ir.ui.view', null, null), this.model = dataset.model; this.xml_element_id = 0; - this.property = instance.web.ViewEditor.property_widget; + this.property = instance.web_view_editor.ViewEditor.property_widget; this.one_object = false; }, start: function() { @@ -434,7 +434,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({ break; } if (view_find.attr('level') < min_level) { - min_level = parseInt(view_find.attr('level')); + min_level = parseInt(view_find.attr('level')); } } var val = _.detect(obj.att_list, function(val) {return val[0] == "name";}); @@ -1013,7 +1013,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({ }); } }); -instance.web.ViewEditor.Field = instance.web.Class.extend({ +instance.web_view_editor.ViewEditor.Field = instance.web.Class.extend({ init: function(view, widget) { this.$element = view.$element; this.dirty = false; @@ -1049,7 +1049,7 @@ instance.web.ViewEditor.Field = instance.web.Class.extend({ return _.str.sprintf("%s", this.name, QWeb.render(this.template, {widget: this})) } }); -instance.web.ViewEditor.FieldBoolean = instance.web.ViewEditor.Field.extend({ +instance.web_view_editor.ViewEditor.FieldBoolean = instance.web_view_editor.ViewEditor.Field.extend({ template : "vieweditor_boolean", start: function() { var self = this; @@ -1067,7 +1067,7 @@ instance.web.ViewEditor.FieldBoolean = instance.web.ViewEditor.Field.extend({ return this.$element.find("input[id=" + this.name + "]").is(':checked')? "1" : null; } }); -instance.web.ViewEditor.FieldChar = instance.web.ViewEditor.Field.extend({ +instance.web_view_editor.ViewEditor.FieldChar = instance.web_view_editor.ViewEditor.Field.extend({ template : "vieweditor_char", start: function () { var self = this; @@ -1083,7 +1083,7 @@ instance.web.ViewEditor.FieldChar = instance.web.ViewEditor.Field.extend({ return this.$element.find("input[id=" + this.name + "]").val(); } }); -instance.web.ViewEditor.FieldSelect = instance.web.ViewEditor.Field.extend({ +instance.web_view_editor.ViewEditor.FieldSelect = instance.web_view_editor.ViewEditor.Field.extend({ template : "vieweditor_selection", start: function () { var self = this; @@ -1113,7 +1113,7 @@ instance.web.ViewEditor.FieldSelect = instance.web.ViewEditor.Field.extend({ return this.$element.find("select[id=" + this.name + "]").val(); } }); -instance.web.ViewEditor.FieldSelectMulti = instance.web.ViewEditor.FieldSelect.extend({ +instance.web_view_editor.ViewEditor.FieldSelectMulti = instance.web_view_editor.ViewEditor.FieldSelect.extend({ start: function () { this._super(); this.$element.find("select[id=" + this.name + "]").css('height', '100px').attr("multiple", true); @@ -1129,7 +1129,7 @@ instance.web.ViewEditor.FieldSelectMulti = instance.web.ViewEditor.FieldSelect.e }); } }); -instance.web.ViewEditor.FieldFloat = instance.web.ViewEditor.FieldChar.extend({ +instance.web_view_editor.ViewEditor.FieldFloat = instance.web_view_editor.ViewEditor.FieldChar.extend({ }); var _PROPERTIES = { @@ -1202,11 +1202,11 @@ var _ICONS = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner', 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph' ]; -instance.web.ViewEditor.property_widget = new instance.web.Registry({ - 'boolean' : 'instance.web.ViewEditor.FieldBoolean', - 'selection_multi' : 'instance.web.ViewEditor.FieldSelectMulti', - 'selection' : 'instance.web.ViewEditor.FieldSelect', - 'char' : 'instance.web.ViewEditor.FieldChar', - 'float' : 'instance.web.ViewEditor.FieldFloat' +instance.web_view_editor.ViewEditor.property_widget = new instance.web.Registry({ + 'boolean' : 'instance.web_view_editor.ViewEditor.FieldBoolean', + 'selection_multi' : 'instance.web_view_editor.ViewEditor.FieldSelectMulti', + 'selection' : 'instance.web_view_editor.ViewEditor.FieldSelect', + 'char' : 'instance.web_view_editor.ViewEditor.FieldChar', + 'float' : 'instance.web_view_editor.ViewEditor.FieldFloat' }); }; diff --git a/addons/web_view_editor/static/src/xml/view_editor.xml b/addons/web_view_editor/static/src/xml/view_editor.xml new file mode 100644 index 00000000000..e93fb3bd3ff --- /dev/null +++ b/addons/web_view_editor/static/src/xml/view_editor.xml @@ -0,0 +1,66 @@ + + From 1fc733b9197fefaa18348a87748f2b3a12841ff8 Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Mon, 30 Jul 2012 15:19:38 +0530 Subject: [PATCH 35/89] [FIX]small issue. bzr revid: vme@tinyerp.com-20120730094938-hr0et2qgm6jd7xjb --- addons/web_view_editor/static/src/js/view_editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_view_editor/static/src/js/view_editor.js b/addons/web_view_editor/static/src/js/view_editor.js index 519f6bce3ee..bd2d7ec406b 100644 --- a/addons/web_view_editor/static/src/js/view_editor.js +++ b/addons/web_view_editor/static/src/js/view_editor.js @@ -350,7 +350,7 @@ instance.web_view_editor.ViewEditor = instance.web.OldWidget.extend({ this.edit_xml_dialog.$element.find("tr[id=viewedit-" + row_id + "]").addClass('ui-selected'); }, do_parent_img_hide_show: function(img) { - if ($(img).attr('src') == '/web/static/src/img/collapse.gif') { + if (_.str.include($(img).attr('src'), '/web/static/src/img/collapse.gif')) { $(img).attr('src', '/web/static/src/img/expand.gif'); this.on_expand(img); } else { From 09fe39c0b61e0ffa5f2a98f913e9770770bbc4db Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 31 Jul 2012 13:08:45 +0200 Subject: [PATCH 36/89] [IMP] rec_name assertion and fallback bzr revid: al@openerp.com-20120731110845-nt11isvr96tuc207 --- openerp/osv/orm.py | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index a62561fc95d..a18c4195dc2 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -657,7 +657,7 @@ class BaseModel(object): _columns = {} _constraints = [] _defaults = {} - _rec_name = 'name' + _rec_name = False _parent_name = 'parent_id' _parent_store = False _parent_order = False @@ -1054,6 +1054,13 @@ class BaseModel(object): assert self._log_access, "TransientModels must have log_access turned on, "\ "in order to implement their access rights policy" + # Validate rec_name + if self._rec_name: + assert self.rec_name in self._columns + else: + self._rec_name = 'name' + + def __export_row(self, cr, uid, row, fields, context=None): if context is None: context = {} @@ -1567,6 +1574,12 @@ class BaseModel(object): res.extend(self.pool.get(parent).fields_get_keys(cr, user, context)) return res + def _rec_name_fallback(self, cr, uid, context=None): + rec_name = self._rec_name + if rec_name not in self._columns: + rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id" + return rec_name + # # Overload this method if you need a window title which depends on the context # @@ -1859,8 +1872,7 @@ class BaseModel(object): return view def _get_default_search_view(self, cr, user, context=None): - """ Generates a single-field tree view, using _rec_name if - it's one of the columns or the first column it finds otherwise + """ Generates a single-field search view, based on _rec_name. :param cr: database cursor :param int user: user id @@ -1868,17 +1880,12 @@ class BaseModel(object): :returns: a tree view as an lxml document :rtype: etree._Element """ - _rec_name = self._rec_name - if _rec_name not in self._columns: - _rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id" - view = etree.Element('search', string=self._description) - etree.SubElement(view, 'field', name=_rec_name) + etree.SubElement(view, 'field', name=self._rec_name_fallback(cr,uid,context)) return view def _get_default_tree_view(self, cr, user, context=None): - """ Generates a single-field tree view, using _rec_name if - it's one of the columns or the first column it finds otherwise + """ Generates a single-field tree view, based on _rec_name. :param cr: database cursor :param int user: user id @@ -1886,12 +1893,8 @@ class BaseModel(object): :returns: a tree view as an lxml document :rtype: etree._Element """ - _rec_name = self._rec_name - if _rec_name not in self._columns: - _rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id" - view = etree.Element('tree', string=self._description) - etree.SubElement(view, 'field', name=_rec_name) + etree.SubElement(view, 'field', name=self._rec_name_fallback(cr,uid,context)) return view def _get_default_calendar_view(self, cr, user, context=None): @@ -1918,7 +1921,7 @@ class BaseModel(object): return False view = etree.Element('calendar', string=self._description) - etree.SubElement(view, 'field', name=self._rec_name) + etree.SubElement(view, 'field', self._rec_name_get(cr,uid,context)) if (self._date_name not in self._columns): date_found = False @@ -2276,8 +2279,13 @@ class BaseModel(object): return [] if isinstance(ids, (int, long)): ids = [ids] - return [(r['id'], tools.ustr(r[self._rec_name])) for r in self.read(cr, user, ids, - [self._rec_name], context, load='_classic_write')] + + rec_name = self._rec_name + if rec_name not in self._columns: + res = [(r['id'], "%s,%s"%(self._name,r['id'])) for r in self.read(cr, user, ids, ['id'], context, load='_classic_write')] + else: + res = [(r['id'], tools.ustr(r[rec_name])) for r in self.read(cr, user, ids, [rec_name], context, load='_classic_write')] + return res def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100): """Search for records that have a display name matching the given ``name`` pattern if compared From d375ee272e55d071920cec9f5092143d67b2ea4d Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 31 Jul 2012 14:06:21 +0200 Subject: [PATCH 37/89] [FIX] rec_name assertion and fallback bzr revid: al@openerp.com-20120731120621-11j7k7965d9sclp2 --- openerp/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index a18c4195dc2..7570cb3d690 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1056,7 +1056,7 @@ class BaseModel(object): # Validate rec_name if self._rec_name: - assert self.rec_name in self._columns + assert self._rec_name in self._columns else: self._rec_name = 'name' From 8540dcf017fd21efca71dbc175127779cdcb78be Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 31 Jul 2012 14:25:57 +0200 Subject: [PATCH 38/89] [FIX] assertion message bzr revid: al@openerp.com-20120731122557-9ka14pw09x2fhmos --- openerp/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 7570cb3d690..c5490a6a99c 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1056,7 +1056,7 @@ class BaseModel(object): # Validate rec_name if self._rec_name: - assert self._rec_name in self._columns + assert self._rec_name in self._columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self.rec_name, self._name) else: self._rec_name = 'name' From ed0d89e65907344a0bd4db708179433d75272615 Mon Sep 17 00:00:00 2001 From: "Vidhin Mehta (OpenERP)" Date: Tue, 31 Jul 2012 19:14:06 +0530 Subject: [PATCH 39/89] [IMP] override on_debug_changed method. bzr revid: vme@tinyerp.com-20120731134406-6pornq9phwnahia3 --- addons/web/static/src/js/views.js | 9 --------- .../static/src/js/view_editor.js | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index c3c697638d8..02f0c9d5812 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -756,15 +756,6 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ width: '95%'}, $root).open(); }); break; - case 'manage_views': - if (current_view.fields_view && current_view.fields_view.arch) { - var view_editor = new instance.web_view_editor.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch); - view_editor.start(); - } else { - this.do_warn(_t("Manage Views"), - _t("Could not find current view declaration")); - } - break; case 'edit_workflow': return this.do_action({ res_model : 'workflow', diff --git a/addons/web_view_editor/static/src/js/view_editor.js b/addons/web_view_editor/static/src/js/view_editor.js index bd2d7ec406b..36e07622dd0 100644 --- a/addons/web_view_editor/static/src/js/view_editor.js +++ b/addons/web_view_editor/static/src/js/view_editor.js @@ -1,6 +1,24 @@ openerp.web_view_editor = function(instance) { var _t = instance.web._t; var QWeb = instance.web.qweb; +instance.web_view_editor.ViewManagerAction = instance.web.ViewManagerAction.include({ + on_debug_changed:function(evt){ + var val = $(evt.currentTarget).find('option:selected').val(), + current_view = this.views[this.active_view].controller; + if(val === "manage_views"){ + if (current_view.fields_view && current_view.fields_view.arch) { + var view_editor = new instance.web_view_editor.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch); + view_editor.start(); + } else { + this.do_warn(_t("Manage Views"), + _t("Could not find current view declaration")); + } + evt.currentTarget.selectedIndex = 0; + }else{ + return this._super.apply(this,arguments); + } + } +}) instance.web_view_editor.ViewEditor = instance.web.OldWidget.extend({ init: function(parent, element_id, dataset, view, options) { this._super(parent); From 4811011c7617101d840bcdcc2c965b45972b0ce1 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 31 Jul 2012 17:17:18 +0200 Subject: [PATCH 40/89] [FIX] typo bzr revid: al@openerp.com-20120731151718-g0fk33ycoapob13j --- openerp/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index c5490a6a99c..5d2399309e0 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1056,7 +1056,7 @@ class BaseModel(object): # Validate rec_name if self._rec_name: - assert self._rec_name in self._columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self.rec_name, self._name) + assert self._rec_name in self._columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name) else: self._rec_name = 'name' From 42859212faae2624cfe8eeb592d4836f50aa5ed2 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 31 Jul 2012 17:26:49 +0200 Subject: [PATCH 41/89] [FIX] typos and vmt review bzr revid: al@openerp.com-20120731152649-4mxph7d0cnlaqtef --- openerp/osv/orm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 5d2399309e0..2c73e811eeb 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -657,7 +657,7 @@ class BaseModel(object): _columns = {} _constraints = [] _defaults = {} - _rec_name = False + _rec_name = None _parent_name = 'parent_id' _parent_store = False _parent_order = False @@ -1055,7 +1055,7 @@ class BaseModel(object): "in order to implement their access rights policy" # Validate rec_name - if self._rec_name: + if self._rec_name is None: assert self._rec_name in self._columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name) else: self._rec_name = 'name' @@ -1921,7 +1921,7 @@ class BaseModel(object): return False view = etree.Element('calendar', string=self._description) - etree.SubElement(view, 'field', self._rec_name_get(cr,uid,context)) + etree.SubElement(view, 'field', self._rec_name_fallback(cr,uid,context)) if (self._date_name not in self._columns): date_found = False From 2e4c6523afb206ac34548a1660f2e4a50a5b3290 Mon Sep 17 00:00:00 2001 From: "Rajesh Prajapati (OpenERP)" Date: Wed, 1 Aug 2012 12:44:46 +0530 Subject: [PATCH 42/89] [IMP] hr : wizard traceback of "all children rules" removed bzr revid: rpr@tinyerp.com-20120801071446-13jar5rxtg0xsy5v --- addons/hr_payroll/hr_payroll_view.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/hr_payroll/hr_payroll_view.xml b/addons/hr_payroll/hr_payroll_view.xml index b08ce6983c6..c4f42fde82d 100644 --- a/addons/hr_payroll/hr_payroll_view.xml +++ b/addons/hr_payroll/hr_payroll_view.xml @@ -659,11 +659,10 @@ From 171500f171eb78524cb2d4977b6b9f98682f266d Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 1 Aug 2012 09:50:24 +0200 Subject: [PATCH 43/89] [MAOW] lolcat ninja fix in the branch, yaoh! bzr revid: vmt@openerp.com-20120801075024-mpc4seluumaa33ph --- openerp/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 2c73e811eeb..4233fc2de2d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1055,7 +1055,7 @@ class BaseModel(object): "in order to implement their access rights policy" # Validate rec_name - if self._rec_name is None: + if self._rec_name is not None: assert self._rec_name in self._columns.keys() + ['id'], "Invalid rec_name %s for model %s" % (self._rec_name, self._name) else: self._rec_name = 'name' From 6aef95b5c28647c4b165ba8dcd0e31908a671a0a Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 1 Aug 2012 10:01:55 +0200 Subject: [PATCH 44/89] [MEOW] lolcat crazy ninja fix in the branch, take two! bzr revid: vmt@openerp.com-20120801080155-8qhg3aqkay2rrmbt --- openerp/osv/orm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 4233fc2de2d..a1a267bd552 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -1881,7 +1881,7 @@ class BaseModel(object): :rtype: etree._Element """ view = etree.Element('search', string=self._description) - etree.SubElement(view, 'field', name=self._rec_name_fallback(cr,uid,context)) + etree.SubElement(view, 'field', name=self._rec_name_fallback(cr, user, context)) return view def _get_default_tree_view(self, cr, user, context=None): @@ -1894,7 +1894,7 @@ class BaseModel(object): :rtype: etree._Element """ view = etree.Element('tree', string=self._description) - etree.SubElement(view, 'field', name=self._rec_name_fallback(cr,uid,context)) + etree.SubElement(view, 'field', name=self._rec_name_fallback(cr, user, context)) return view def _get_default_calendar_view(self, cr, user, context=None): @@ -1921,7 +1921,7 @@ class BaseModel(object): return False view = etree.Element('calendar', string=self._description) - etree.SubElement(view, 'field', self._rec_name_fallback(cr,uid,context)) + etree.SubElement(view, 'field', self._rec_name_fallback(cr, user, context)) if (self._date_name not in self._columns): date_found = False From 74582aea7e36bbe805fb395bbb0f11fa768ead14 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 1 Aug 2012 10:16:30 +0200 Subject: [PATCH 45/89] [FIX] event: removed invalid _rec_name. bzr revid: vmt@openerp.com-20120801081630-2wtg0rnwoa97zacg --- addons/event/report/report_event_registration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/event/report/report_event_registration.py b/addons/event/report/report_event_registration.py index 17472e0cc95..060b1ef7556 100644 --- a/addons/event/report/report_event_registration.py +++ b/addons/event/report/report_event_registration.py @@ -27,7 +27,6 @@ class report_event_registration(osv.osv): _name = "report.event.registration" _description = "Events Analysis" _auto = False - _rec_name = 'date' _columns = { 'event_date': fields.char('Event Start Date', size=64, readonly=True), 'year': fields.char('Year', size=4, readonly=True), From ee1018a24280975e30375361292305b56031af5d Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 1 Aug 2012 10:38:23 +0200 Subject: [PATCH 46/89] [FIX] hr_evaluation: removed invalid _rec_name. bzr revid: vmt@openerp.com-20120801083823-m1mr5n32vuhiofzj --- addons/hr_evaluation/report/hr_evaluation_report.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/hr_evaluation/report/hr_evaluation_report.py b/addons/hr_evaluation/report/hr_evaluation_report.py index ac32133186c..bc2e50629c3 100644 --- a/addons/hr_evaluation/report/hr_evaluation_report.py +++ b/addons/hr_evaluation/report/hr_evaluation_report.py @@ -26,7 +26,6 @@ class hr_evaluation_report(osv.osv): _name = "hr.evaluation.report" _description = "Evaluations Statistics" _auto = False - _rec_name = 'date' _columns = { 'create_date': fields.date('Create Date', readonly=True), 'delay_date':fields.float('Delay to Start', digits=(16,2),readonly=True), From 3edc646322df9ae16385029c9036ad14ff828a50 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 1 Aug 2012 12:10:14 +0200 Subject: [PATCH 47/89] [FIX] l10n_ar,l10n_cl,analytic_contract_hr_expense: remove empty certificate to allow UNIQUE index to be created bzr revid: odo@openerp.com-20120801101014-sw2vla5a5u1bjpvv --- addons/analytic_contract_hr_expense/__openerp__.py | 1 - addons/l10n_ar/__openerp__.py | 1 - addons/l10n_cl/__openerp__.py | 1 - 3 files changed, 3 deletions(-) diff --git a/addons/analytic_contract_hr_expense/__openerp__.py b/addons/analytic_contract_hr_expense/__openerp__.py index b5ab6bea9ab..6d5cca8b574 100644 --- a/addons/analytic_contract_hr_expense/__openerp__.py +++ b/addons/analytic_contract_hr_expense/__openerp__.py @@ -40,7 +40,6 @@ This module is for modifying account analytic view to show some data related to ], 'installable': True, 'auto_install': True, - 'certificate': '', } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/l10n_ar/__openerp__.py b/addons/l10n_ar/__openerp__.py index 8971ce94b22..ef04805d1b0 100644 --- a/addons/l10n_ar/__openerp__.py +++ b/addons/l10n_ar/__openerp__.py @@ -45,7 +45,6 @@ Plan contable argentino e impuestos de acuerdo a disposiciones vigentes ], "active": False, "installable": True, - "certificate" : "", 'images': ['images/config_chart_l10n_ar.jpeg','images/l10n_ar_chart.jpeg'], } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/l10n_cl/__openerp__.py b/addons/l10n_cl/__openerp__.py index 8c1e1e45b50..d10acdae575 100644 --- a/addons/l10n_cl/__openerp__.py +++ b/addons/l10n_cl/__openerp__.py @@ -45,7 +45,6 @@ Plan contable chileno e impuestos de acuerdo a disposiciones vigentes ], "active": False, "installable": True, - "certificate" : "", 'images': ['images/config_chart_l10n_cl.jpeg','images/l10n_cl_chart.jpeg'], } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 9c2de5a3bda6423b937ca4de1046e454251f1c1b Mon Sep 17 00:00:00 2001 From: Hardik Date: Wed, 1 Aug 2012 16:29:01 +0530 Subject: [PATCH 48/89] [IMP]product : product by location wizard improved bzr revid: hsa@tinyerp.com-20120801105901-2s2bqg1hqoxl2pfi --- addons/product/product_view.xml | 1 - addons/stock/wizard/stock_location_product.py | 7 +++++-- addons/stock/wizard/stock_location_product_view.xml | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index e182cb01bed..2aea995b996 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -45,7 +45,6 @@ - diff --git a/addons/stock/wizard/stock_location_product.py b/addons/stock/wizard/stock_location_product.py index 49dc048d0ab..4832788927b 100644 --- a/addons/stock/wizard/stock_location_product.py +++ b/addons/stock/wizard/stock_location_product.py @@ -20,13 +20,16 @@ ############################################################################## from osv import fields, osv +from tools.translate import _ class stock_location_product(osv.osv_memory): _name = "stock.location.product" _description = "Products by Location" _columns = { 'from_date': fields.datetime('From'), - 'to_date': fields.datetime('To'), + 'to_date': fields.datetime('To'), + 'type': fields.selection([('inventory','Analyse Current Inventory'), + ('period','Analyse a Period')], 'Analyse Type', required=True), } def action_open_window(self, cr, uid, ids, context=None): @@ -43,7 +46,7 @@ class stock_location_product(osv.osv_memory): location_products = self.read(cr, uid, ids, ['from_date', 'to_date'], context=context) if location_products: return { - 'name': False, + 'name': _('Current Inventory'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'product.product', diff --git a/addons/stock/wizard/stock_location_product_view.xml b/addons/stock/wizard/stock_location_product_view.xml index f0a52933c8a..756c3782ea1 100644 --- a/addons/stock/wizard/stock_location_product_view.xml +++ b/addons/stock/wizard/stock_location_product_view.xml @@ -7,14 +7,15 @@ form
    - + + + -