From 2ba74a4c8a0841347578c1038e1374318ba118f5 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 28 Jul 2011 17:24:56 +0200 Subject: [PATCH 1/2] [imp] Mainly documentation and small modifications to Widget. bzr revid: nicolas.vanhoren@openerp.com-20110728152456-2mgkbcqhm7d0zz3m --- addons/base/static/src/js/controller.js | 74 ++++++++++++++++++------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/addons/base/static/src/js/controller.js b/addons/base/static/src/js/controller.js index 2ed00f29ff9..7abf35c10ed 100644 --- a/addons/base/static/src/js/controller.js +++ b/addons/base/static/src/js/controller.js @@ -149,8 +149,10 @@ instance.base.generate_null_object_class = function(claz, add) { // OLD // -------------------------------------------------------- /** - * Class for OpenERP session aware classes to extend. Also provides callback mechanism and logging - * facility. + * Utility class that any class is allowed to extend to easy common manipulations. + * + * It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a + * logging facility. */ instance.base.SessionAware = instance.base.Class.extend({ init: function(session) { @@ -214,6 +216,18 @@ instance.base.SessionAware = instance.base.Class.extend({ } }); +/** + * Base class for all visual components. Provides a lot of functionalities helpful + * for the management of a part of the DOM. + * + * Widget handles: + * - Rendering with QWeb. + * - Life-cycle management and parenting (when a parent is destroyed, all its children are + * destroyed too). + * - Insertion in DOM. + * + * Widget also extends SessionAware for ease of use. + */ instance.base.Widget = instance.base.SessionAware.extend({ /** * The name of the QWeb template that will be used for rendering. Must be @@ -231,16 +245,24 @@ instance.base.Widget = instance.base.SessionAware.extend({ identifier_prefix: 'generic-identifier', /** * @constructs - * rpc operations, event binding and callback calling should be done in - * start() instead of init so that events can be hooked in between. + * Construct the widget and set its parent if a parent is given. + * + * @param {Widget} parent Binds the current instance to the given Widget instance. + * When that widget is destroyed by calling stop(), 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, element_id) { this._super((parent || {}).session); - //TODO niv: get away the possibility to specify an id this.element_id = element_id; this.element_id = this.element_id || _.uniqueId(this.identifier_prefix); - this.$element = $('#' + element_id); + var tmp = document.getElementById(this.element_id); + this.$element = tmp ? $(tmp) : undefined; + // TODO niv: wtf?? if (element_id) { instance.screen[element_id] = this; } @@ -252,8 +274,8 @@ instance.base.Widget = instance.base.SessionAware.extend({ } }, /** - * Render the widget. This.template must be defined. - * The content of the current object is passed as context to the template. + * Renders the widget using QWeb, `this.template` must be defined. + * The context given to QWeb contains the "widget" key that references `this`. * * @param {object} additional Additional context arguments to pass to the template. */ @@ -261,19 +283,24 @@ instance.base.Widget = instance.base.SessionAware.extend({ return QWeb.render(this.template, _.extend({widget: this}, additional || {})); }, /** - * Event binding, rpc and callback calling required to initialize the - * object should happen here - * - * Returns a promise object letting callers (subclasses and direct callers) - * know when this component is done starting + * Method called after rendering. Mostly used to bind actions, perform asynchronous + * calls, etc... + * + * By convention, the method should return a promise to inform the caller when + * this widget has been initialized. * * @returns {jQuery.Deferred} */ start: function() { - var tmp = document.getElementById(this.element_id); - this.$element = tmp ? $(tmp) : null; + if (!this.$element) { + var tmp = document.getElementById(this.element_id); + this.$element = tmp ? $(tmp) : undefined; + } return $.Deferred().done().promise(); }, + /** + * Destroys the current widget, also destory all its children before destroying itself. + */ stop: function() { _.each(_.clone(this.widget_children), function(el) { el.stop(); @@ -286,14 +313,23 @@ instance.base.Widget = instance.base.SessionAware.extend({ } this.widget_parent = null; }, + /** + * Inform the action manager to do an action. Of course, this suppose that + * the action manager can be found amongst the ancestors of the current widget. + * If that's not the case this method will simply return `false`. + */ do_action: function(action, on_finished) { - return this.widget_parent.do_action(action, on_finished); + if (this.widget_parent) { + return this.widget_parent.do_action(action, on_finished); + } + return false; } }); -/* - * For retro compatibility only, the only difference with is that render takes - * directly this instead of +/** + * @deprecated + * For retro compatibility only, the only difference with is that render() uses + * directly this instead of a {widget: this}. */ instance.base.OldWidget = instance.base.Widget.extend({ render: function (additional) { From a64f68bd2027ea6bce02156c5eeafa9e8625e638 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 28 Jul 2011 18:59:14 +0200 Subject: [PATCH 2/2] [imp] DOM insertion methods for Widget + doc + rpc stop mechanism bzr revid: nicolas.vanhoren@openerp.com-20110728165914-6nadrs5sj00jd91j --- addons/base/static/src/js/controller.js | 122 ++++++++++++++++++++++-- addons/base/static/src/js/search.js | 4 +- 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/addons/base/static/src/js/controller.js b/addons/base/static/src/js/controller.js index 7abf35c10ed..0684f7407d2 100644 --- a/addons/base/static/src/js/controller.js +++ b/addons/base/static/src/js/controller.js @@ -227,6 +227,46 @@ instance.base.SessionAware = instance.base.Class.extend({ * - Insertion in DOM. * * Widget also extends SessionAware for ease of use. + * + * Guide to create implementations of this class: + * ============================================== + * + * Here is a sample child class: + * + * MyWidget = openerp.base.Widget.extend({ + * // the name of the QWeb template to use for rendering + * template: "MyQWebTemplate", + * // identifier prefix, useful to put an obvious one for debugging + * identifier_prefix: 'my-id-prefix-', + * + * init: function(parent) { + * this._super(parent); + * // stuff that you want to init before the rendering + * }, + * start: function() { + * // stuff you want to make after the rendering, `this.$element` holds a value + * this.$element.find(".my_button").click(/* an example of event binding * /); + * + * // if you have some asynchronous operations, it's a good idea to return + * // a promise in start() + * var promise = this.rpc(...); + * return promise; + * } + * }); + * + * Now this class can simply be used with the following syntax: + * + * var my_widget = new MyWidget(this); + * my_widget.appendTo($(".some-div")); + * + * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the + * DOM inside the ".some-div" div and its events were binded. + * + * And of course, when you don't need that widget anymore, just do: + * + * my_widget.stop(); + * + * That will kill the widget in a clean way and erase its content from the dom. */ instance.base.Widget = instance.base.SessionAware.extend({ /** @@ -242,7 +282,7 @@ instance.base.Widget = instance.base.SessionAware.extend({ * * @type string */ - identifier_prefix: 'generic-identifier', + identifier_prefix: 'generic-identifier-', /** * @constructs * Construct the widget and set its parent if a parent is given. @@ -255,23 +295,74 @@ instance.base.Widget = instance.base.SessionAware.extend({ * 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, element_id) { + init: function(parent, /** @deprecated */ element_id) { this._super((parent || {}).session); + // if given an element_id, try to get the associated DOM element and save + // a reference in this.$element. Else just generate a unique identifier. this.element_id = element_id; this.element_id = this.element_id || _.uniqueId(this.identifier_prefix); - var tmp = document.getElementById(this.element_id); this.$element = tmp ? $(tmp) : undefined; - // TODO niv: wtf?? - if (element_id) { - instance.screen[element_id] = this; - } - // save the parent children relationship + this.widget_parent = parent; this.widget_children = []; if(parent && parent.widget_children) { parent.widget_children.push(this); } + // useful to know if the widget was destroyed and should not be used anymore + this.widget_is_stopped = false; + }, + /** + * Render the current widget and appends it to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + appendTo: function(target) { + var self = this; + return this._render_and_insert(function(t) { + self.$element.appendTo(t); + }, target); + }, + /** + * Render the current widget and prepends it to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + prependTo: function(target) { + var self = this; + return this._render_and_insert(function(t) { + self.$element.prependTo(t); + }, target); + }, + /** + * Render the current widget and inserts it after to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + insertAfter: function(target) { + var self = this; + return this._render_and_insert(function(t) { + self.$element.insertAfter(t); + }, target); + }, + /** + * Render the current widget and inserts it before to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + insertBefore: function(target) { + var self = this; + return this._render_and_insert(function(t) { + self.$element.insertBefore(t); + }, target); + }, + _render_and_insert: function(insertion, target) { + var rendered = this.render(); + this.$element = $(rendered); + if (target instanceof instance.base.Widget) + target = target.$element; + insertion(target); + return this.start(); }, /** * Renders the widget using QWeb, `this.template` must be defined. @@ -312,6 +403,7 @@ instance.base.Widget = instance.base.SessionAware.extend({ this.widget_parent.widget_children = _.without(this.widget_parent.widget_children, this); } this.widget_parent = null; + this.widget_is_stopped = true; }, /** * Inform the action manager to do an action. Of course, this suppose that @@ -323,13 +415,25 @@ instance.base.Widget = instance.base.SessionAware.extend({ return this.widget_parent.do_action(action, on_finished); } return false; + }, + rpc: function(url, data, success, error) { + var def = $.Deferred().then(success, error); + var self = this; + this._super(url, data). then(function() { + if (!self.widget_is_stopped) + def.resolve.apply(def, arguments); + }, function() { + if (!self.widget_is_stopped) + def.reject.apply(def, arguments); + }); + return def.promise(); } }); /** * @deprecated * For retro compatibility only, the only difference with is that render() uses - * directly this instead of a {widget: this}. + * directly `this` instead of context with a "widget" key. */ instance.base.OldWidget = instance.base.Widget.extend({ render: function (additional) { diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index a63ba79355c..763746bb0bf 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -860,9 +860,7 @@ openerp.base.search.ExtendedSearch = openerp.base.OldWidget.extend({ }, add_group: function() { var group = new openerp.base.search.ExtendedSearchGroup(this, this.fields); - var render = group.render(); - this.$element.find('.searchview_extended_groups_list').append(render); - group.start(); + group.appendTo(this.$element.find('.searchview_extended_groups_list')); this.check_last_element(); }, start: function () {