From 5faec34a3c900e26619e4489c29238f671292034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 20 Nov 2017 11:47:02 +0100 Subject: [PATCH] [REF] web: update widget to new guidelines - make delegate/undelegate events private - remove $el before start - make replaceElement private - remove one _ from __render_and_insert... method - update documentation - remove make and improve make_descriptive (also, it is private) - ... --- .../reconciliation/reconciliation_renderer.js | 4 +- addons/point_of_sale/static/src/js/chrome.js | 1 - .../static/src/js/widget_base.js | 4 + .../web/static/src/js/chrome/search_inputs.js | 2 +- addons/web/static/src/js/core/widget.js | 342 ++++++++++-------- .../web/static/src/js/fields/basic_fields.js | 2 +- .../src/js/views/basic/basic_renderer.js | 31 +- .../static/src/js/views/form/form_renderer.js | 10 +- .../src/js/views/graph/graph_renderer.js | 1 - .../src/js/views/kanban/kanban_record.js | 4 +- .../static/src/js/views/list/list_renderer.js | 4 +- .../src/js/views/pivot/pivot_renderer.js | 2 +- addons/web/static/tests/core/widget_tests.js | 8 +- 13 files changed, 224 insertions(+), 191 deletions(-) diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index 9e72fdeb44f..e7cc268044d 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -794,10 +794,10 @@ var ManualLineRenderer = LineRenderer.extend({ return $.when.apply($, defs).then(function () { if (!self.fields.title_account_id) { - self.fields.partner_id.$el.prependTo(self.$('.accounting_view thead td:eq(1) span:first')); + return self.fields.partner_id.prependTo(self.$('.accounting_view thead td:eq(1) span:first')); } else { self.fields.partner_id.destroy(); - self.fields.title_account_id.appendTo(self.$('.accounting_view thead td:eq(1) span:first')); + return self.fields.title_account_id.appendTo(self.$('.accounting_view thead td:eq(1) span:first')); } }); }); diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index 88ca9e8e4fa..5b1f759b319 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -607,7 +607,6 @@ var Chrome = PosBaseWidget.extend({ $(window).off(); $('html').off(); $('body').off(); - this.$el.parent().off(); // The above lines removed the bindings, but we really need them for the barcode BarcodeEvents.start(); }, diff --git a/addons/point_of_sale/static/src/js/widget_base.js b/addons/point_of_sale/static/src/js/widget_base.js index fa981376747..c81d2d13f8a 100644 --- a/addons/point_of_sale/static/src/js/widget_base.js +++ b/addons/point_of_sale/static/src/js/widget_base.js @@ -22,6 +22,10 @@ var PosBaseWidget = Widget.extend({ this.pos = options.pos || (parent ? parent.pos : undefined); this.chrome = options.chrome || (parent ? parent.chrome : undefined); this.gui = options.gui || (parent ? parent.gui : undefined); + + // the widget class does not support anymore using $el/el before the + // 'start' lifecycle method, but point of sale actually needs it. + this.setElement(this._makeDescriptive()); }, format_currency: function(amount,precision){ var currency = (this.pos && this.pos.currency) ? this.pos.currency : {symbol:'$', position: 'after', rounding: 0.01, decimals: 2}; diff --git a/addons/web/static/src/js/chrome/search_inputs.js b/addons/web/static/src/js/chrome/search_inputs.js index d567551873f..e3e5fec6839 100644 --- a/addons/web/static/src/js/chrome/search_inputs.js +++ b/addons/web/static/src/js/chrome/search_inputs.js @@ -540,7 +540,7 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{ */ search_change: function () { var self = this; - var $filters = this.$el.removeClass('selected'); + var $filters = this.$el ? this.$el.removeClass('selected') : $(); var facet = this.searchview.query.find(_.bind(this.match_facet, this)); if (!facet) { return; } facet.values.each(function (v) { diff --git a/addons/web/static/src/js/core/widget.js b/addons/web/static/src/js/core/widget.js index c47deff7330..5e23c3f2c47 100644 --- a/addons/web/static/src/js/core/widget.js +++ b/addons/web/static/src/js/core/widget.js @@ -21,20 +21,26 @@ var ServicesMixin = require('web.ServicesMixin'); * * Here is a sample child class:: * - * var MyWidget = openerp.base.Widget.extend({ + * var MyWidget = Widget.extend({ * // the name of the QWeb template to use for rendering * template: "MyQWebTemplate", * - * init: function(parent) { + * init: function (parent) { * this._super(parent); * // stuff that you want to init before the rendering * }, + * willStart: function () { + * // async work that need to be done before the widget is ready + * // this method should return a deferred + * }, * start: function() { * // stuff you want to make after the rendering, `this.$el` holds a correct value * this.$(".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() + * // a promise in start(). Note that this is quite rare, and if you + * // need to fetch some data, this should probably be done in the + * // willStart method * var promise = this._rpc(...); * return promise; * } @@ -42,8 +48,8 @@ var ServicesMixin = require('web.ServicesMixin'); * * Now this class can simply be used with the following syntax:: * - * var my_widget = new MyWidget(this); - * my_widget.appendTo($(".some-div")); + * var myWidget = new MyWidget(this); + * myWidget.appendTo($(".some-div")); * * With these two lines, the MyWidget instance was initialized, rendered, * inserted into the DOM inside the ``.some-div`` div and its events were @@ -51,12 +57,11 @@ var ServicesMixin = require('web.ServicesMixin'); * * And of course, when you don't need that widget anymore, just do:: * - * my_widget.destroy(); + * myWidget.destroy(); * * That will kill the widget in a clean way and erase its content from the dom. */ - var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { // Backbone-ish API tagName: 'div', @@ -68,7 +73,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * The name of the QWeb template that will be used for rendering. Must be * redefined in subclasses or the default render() method can not be used. * - * @type {String} + * @type {null|string} */ template: null, /** @@ -76,16 +81,16 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * be rendered. This will not induce loading anything that has already been * loaded. * - * @type {string[]} + * @type {null|string[]} */ xmlDependencies: null, /** * Constructs the widget and sets its parent if a parent is given. * - * @param {openerp.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 {Widget|null} parent Binds the current instance to the given Widget + * instance. When that widget is destroyed by calling destroy(), the + * current instance will be destroyed too. Can be null. */ init: function (parent) { mixins.PropertiesMixin.init.call(this); @@ -93,14 +98,12 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { // Bind on_/do_* methods to this // We might remove this automatic binding in the future for (var name in this) { - if(typeof(this[name]) == "function") { + if(typeof(this[name]) === "function") { if((/^on_|^do_/).test(name)) { - this[name] = _.bind(this[name], this); + this[name] = this[name].bind(this); } } } - // FIXME: this should not be - this.setElement(this._make_descriptive()); }, /** * Method called between @see init and @see start. Performs asynchronous @@ -121,65 +124,51 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { return $.when(); }, /** - * Destroys the current widget, also destroys all its children before destroying itself. + * Method called after rendering. Mostly used to bind actions, perform + * asynchronous calls, etc... + * + * By convention, this method should return an object that can be passed to + * $.when() to inform the caller when this widget has been initialized. + * + * Note that, for historic reasons, many widgets still do work in the start + * method that would be more suited to the willStart method. + * + * @returns {Deferred} + */ + start: function () { + return $.when(); + }, + /** + * Destroys the current widget, also destroys all its children before + * destroying itself. */ destroy: function () { - _.each(this.getChildren(), function (el) { - el.destroy(); - }); + _.invoke(this.getChildren(), 'destroy'); if(this.$el) { this.$el.remove(); } mixins.PropertiesMixin.destroy.call(this); }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + /** - * Renders the current widget and appends it to the given jQuery object or Widget. + * Renders the current widget and appends it to the given jQuery object. * - * @param target A jQuery object or a Widget instance. + * @param {jQuery} target */ appendTo: function (target) { var self = this; - return this.__widgetRenderAndInsert(function (t) { + return this._widgetRenderAndInsert(function (t) { self.$el.appendTo(t); }, target); }, - /** - * Renders the current widget and prepends it to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - prependTo: function (target) { - var self = this; - return this.__widgetRenderAndInsert(function (t) { - self.$el.prependTo(t); - }, target); - }, - /** - * Renders the current widget and inserts it after to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - insertAfter: function (target) { - var self = this; - return this.__widgetRenderAndInsert(function (t) { - self.$el.insertAfter(t); - }, target); - }, - /** - * Renders the current widget and inserts it before to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - insertBefore: function (target) { - var self = this; - return this.__widgetRenderAndInsert(function (t) { - self.$el.insertBefore(t); - }, target); - }, /** * Attach the current widget to a dom element * - * @param target A jQuery object or a Widget instance. + * @param {jQuery} target */ attachTo: function (target) { var self = this; @@ -189,68 +178,86 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { }); }, /** - * Renders the current widget and replaces the given jQuery object. - * - * @param target A jQuery object or a Widget instance. + * Hides the widget */ - replace: function (target) { - return this.__widgetRenderAndInsert(_.bind(function (t) { - this.$el.replaceAll(t); - }, this), target); + do_hide: function () { + this.$el.addClass('o_hidden'); }, - __widgetRenderAndInsert: function (insertion, target) { + /** + * Displays the widget + */ + do_show: function () { + this.$el.removeClass('o_hidden'); + }, + /** + * Displays or hides the widget + * @param {boolean} [display] use true to show the widget or false to hide it + */ + do_toggle: function (display) { + if (_.isBoolean(display)) { + display ? this.do_show() : this.do_hide(); + } else { + this.$el.hasClass('o_hidden') ? this.do_show() : this.do_hide(); + } + }, + /** + * Renders the current widget and inserts it after to the given jQuery + * object. + * + * @param {jQuery} target + */ + insertAfter: function (target) { var self = this; - return this.willStart().then(function () { - self.renderElement(); - insertion(target); - return self.start(); - }); + return this._widgetRenderAndInsert(function (t) { + self.$el.insertAfter(t); + }, target); }, /** - * Method called after rendering. Mostly used to bind actions, perform asynchronous - * calls, etc... + * Renders the current widget and inserts it before to the given jQuery + * object. * - * By convention, this method should return an object that can be passed to $.when() - * to inform the caller when this widget has been initialized. - * - * @returns {jQuery.Deferred or any} + * @param {jQuery} target */ - start: function () { - return $.when(); + insertBefore: function (target) { + var self = this; + return this._widgetRenderAndInsert(function (t) { + self.$el.insertBefore(t); + }, target); }, /** - * Renders the element. The default implementation renders the widget using QWeb, - * `this.template` must be defined. The context given to QWeb contains the "widget" - * key that references `this`. + * Renders the current widget and prepends it to the given jQuery object. + * + * @param {jQuery} target + */ + prependTo: function (target) { + var self = this; + return this._widgetRenderAndInsert(function (t) { + self.$el.prependTo(t); + }, target); + }, + /** + * Renders the element. The default implementation renders the widget using + * QWeb, `this.template` must be defined. The context given to QWeb contains + * the "widget" key that references `this`. */ renderElement: function () { var $el; if (this.template) { $el = $(core.qweb.render(this.template, {widget: this}).trim()); } else { - $el = this._make_descriptive(); + $el = this._makeDescriptive(); } - this.replaceElement($el); + 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. + * Renders the current widget and replaces the given jQuery object. * - * @param {HTMLElement | jQuery} $el - * @returns {Widget} this + * @param target A jQuery object or a Widget instance. */ - replaceElement: function ($el) { - var $oldel = this.$el; - this.setElement($el); - if ($oldel && !$oldel.is(this.$el)) { - if ($oldel.length > 1) { - $oldel.wrapAll('
'); - $oldel.parent().replaceWith(this.$el); - } else { - $oldel.replaceWith(this.$el); - } - } - return this; + replace: function (target) { + return this._widgetRenderAndInsert(_.bind(function (t) { + this.$el.replaceAll(t); + }, this), target); }, /** * Re-sets the widget's root element (el/$el/$el). @@ -266,51 +273,41 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * @return {Widget} this */ setElement: function (element) { - // NB: completely useless, as WidgetMixin#init creates a $el - // always if (this.$el) { - this.undelegateEvents(); + this._undelegateEvents(); } this.$el = (element instanceof $) ? element : $(element); this.el = this.$el[0]; - this.delegateEvents(); + this._delegateEvents(); return this; }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** - * Utility function to build small DOM elements. + * Helper method, for ``this.$el.find(selector)`` * - * @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} + * @private + * @param {string} selector CSS selector, rooted in $el + * @returns {jQuery} selector match */ - make: function (tagName, attributes, content) { - var el = document.createElement(tagName); - if (!_.isEmpty(attributes)) { - $(el).attr(attributes); + $: function (selector) { + if (selector === undefined) { + return this.$el; } - if (content) { - $(el).html(content); - } - return el; + return this.$el.find(selector); }, /** - * Makes a potential root element from the declarative builder of the - * widget + * Attach event handlers for events described in the 'events' key * - * @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 () { + _delegateEvents: function () { var events = this.events; if (_.isEmpty(events)) { return; } @@ -331,42 +328,73 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { } } }, - undelegateEvents: function () { + /** + * Makes a potential root element from the declarative builder of the + * widget + * + * @private + * @return {jQuery} + */ + _makeDescriptive: function () { + var attrs = _.extend({}, this.attributes || {}); + if (this.id) { + attrs.id = this.id; + } + if (this.className) { + attrs['class'] = this.className; + } + var $el = $(document.createElement(this.tagName)); + if (!_.isEmpty(attrs)) { + $el.attr(attrs); + } + return $el; + }, + /** + * Re-sets the widget's root element and replaces the old root element + * (if any) by the new one in the DOM. + * + * @private + * @param {HTMLElement | jQuery} $el + * @returns {Widget} this instance, so it can be chained + */ + _replaceElement: function ($el) { + var $oldel = this.$el; + this.setElement($el); + if ($oldel && !$oldel.is(this.$el)) { + if ($oldel.length > 1) { + $oldel.wrapAll('
'); + $oldel.parent().replaceWith(this.$el); + } else { + $oldel.replaceWith(this.$el); + } + } + return this; + }, + /** + * Remove all handlers registered on this.$el + * + * @private + */ + _undelegateEvents: function () { this.$el.off('.widget_events'); }, /** - * Shortcut for ``this.$el.find(selector)`` + * Render the widget. This is a private method, and should really never be + * called by anyone (except this widget). It assumes that the widget was + * not willStarted yet. * - * @param {String} selector CSS selector, rooted in $el - * @returns {jQuery} selector match + * @private + * @param {function: jQuery -> any} insertion + * @param {jQuery} target + * @returns {Deferred} */ - $: function (selector) { - if (selector === undefined) - return this.$el; - return this.$el.find(selector); - }, - /** - * Displays the widget - */ - do_show: function () { - this.$el.removeClass('o_hidden'); - }, - /** - * Hides the widget - */ - do_hide: function () { - this.$el.addClass('o_hidden'); - }, - /** - * Displays or hides the widget - * @param {Boolean} [display] use true to show the widget or false to hide it - */ - do_toggle: function (display) { - if (_.isBoolean(display)) { - display ? this.do_show() : this.do_hide(); - } else { - this.$el.hasClass('o_hidden') ? this.do_show() : this.do_hide(); - } + _widgetRenderAndInsert: function (insertion, target) { + var self = this; + return this.willStart().then(function () { + self.renderElement(); + insertion(target); + return self.start(); + }); }, }); diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 2c8b79e7de8..1b0e3a6c8a3 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -438,7 +438,7 @@ var FieldDate = InputField.extend({ def = this.datewidget.appendTo('
').done(function () { self.datewidget.$el.addClass(self.$el.attr('class')); self._prepareInput(self.datewidget.$input); - self.replaceElement(self.datewidget.$el); + self._replaceElement(self.datewidget.$el); }); } return $.when(def, this._super.apply(this, arguments)); diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index 21fabbc2274..a6d5172bfe1 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -483,15 +483,18 @@ var BasicRenderer = AbstractRenderer.extend({ * rendering of the widget will be started and the associated deferred will * be added to the 'defs' attribute. This is supposed to be created and * deleted by the calling code if necessary. - * Note: for this implementation to work, AbstractField willStart methods - * *must* be synchronous. + * + * Note: we always return a $el. If the field widget is asynchronous, this + * $el will be replaced by the real $el, whenever the widget is ready (start + * method is done). This means that this is not the correct place to make + * changes on the widget $el. For this, @see _postProcessField method * * @private * @param {Object} node * @param {Object} record * @param {Object} [options] * @param {Object} [modifiersOptions] - * @returns {AbstractField} + * @returns {jQueryElement} */ _renderFieldWidget: function (node, record, options, modifiersOptions) { var fieldName = node.attrs.name; @@ -516,8 +519,10 @@ var BasicRenderer = AbstractRenderer.extend({ widget.__node = node; // TODO get rid of this if possible one day // Prepare widget rendering and save the related deferred - var def = widget.__widgetRenderAndInsert(function () {}); - if (def.state() === 'pending') { + var def = widget._widgetRenderAndInsert(function () {}); + var async = def.state() === 'pending'; + var $el = async ? $('
') : widget.$el; + if (async) { this.defs.push(def); } @@ -526,6 +531,9 @@ var BasicRenderer = AbstractRenderer.extend({ // associated to new widget) var self = this; def.then(function () { + if (async) { + $el.replaceWith(widget.$el); + } self._registerModifiers(node, record, widget, _.extend({ callback: function (element, modifiers, record) { element.$el.toggleClass('o_field_empty', !!( @@ -538,7 +546,7 @@ var BasicRenderer = AbstractRenderer.extend({ self._postProcessField(widget, node); }); - return widget; + return $el; }, /** * Renders the nocontent helper. @@ -583,7 +591,7 @@ var BasicRenderer = AbstractRenderer.extend({ var widget = new Widget(this, record); // Prepare widget rendering and save the related deferred - var def = widget.__widgetRenderAndInsert(function () {}); + var def = widget._widgetRenderAndInsert(function () {}); if (def.state() === 'pending') { this.defs.push(def); } @@ -602,20 +610,17 @@ var BasicRenderer = AbstractRenderer.extend({ * @private * @param {Widget} widget * @param {Object} record - * @returns {AbstractField} */ _rerenderFieldWidget: function (widget, record) { // Render the new field widget - var newWidget = this._renderFieldWidget(widget.__node, record); - widget.$el.replaceWith(newWidget.$el); + var $el = this._renderFieldWidget(widget.__node, record); + widget.$el.replaceWith($el); // Destroy the old widget and position the new one at the old one's var oldIndex = this._destroyFieldWidget(record.id, widget); var recordWidgets = this.allFieldWidgets[record.id]; + var newWidget = recordWidgets.pop(); recordWidgets.splice(oldIndex, 0, newWidget); - recordWidgets.pop(); - - return newWidget; }, /** * Unregisters an element of the modifiers data associated to the given diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index 3cc08cc35ea..9409a51b7e0 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -443,8 +443,8 @@ var FormRenderer = BasicRenderer.extend({ * @returns {jQueryElement} */ _renderInnerGroupField: function (node) { - var widget = this._renderFieldWidget(node, this.state); - var $tds = $('').append(widget.$el); + var $el = this._renderFieldWidget(node, this.state); + var $tds = $('').append($el); if (node.attrs.nolabel !== '1') { var $labelTd = this._renderInnerGroupLabel(node); @@ -570,7 +570,7 @@ var FormRenderer = BasicRenderer.extend({ * @returns {jQueryElement} */ _renderTagField: function (node) { - return this._renderFieldWidget(node, this.state).$el; + return this._renderFieldWidget(node, this.state); }, /** * @private @@ -624,8 +624,8 @@ var FormRenderer = BasicRenderer.extend({ $statusbar.append(this._renderHeaderButtons(node)); _.each(node.children, function (child) { if (child.tag === 'field') { - var widget = self._renderFieldWidget(child, self.state); - $statusbar.append(widget.$el); + var $el = self._renderFieldWidget(child, self.state); + $statusbar.append($el); } }); this._handleAttributes($statusbar, node); diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js index 2c3a7a7db19..414e51b31b4 100644 --- a/addons/web/static/src/js/views/graph/graph_renderer.js +++ b/addons/web/static/src/js/views/graph/graph_renderer.js @@ -34,7 +34,6 @@ return AbstractRenderer.extend({ init: function (parent, state, params) { this._super.apply(this, arguments); this.stacked = params.stacked; - this.$el.css({minWidth: '100px', minHeight: '100px'}); }, /** * @override diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js index 3688d6918c5..dfe62e3746e 100644 --- a/addons/web/static/src/js/views/kanban/kanban_record.js +++ b/addons/web/static/src/js/views/kanban/kanban_record.js @@ -270,7 +270,7 @@ var KanbanRecord = Widget.extend({ var Widget = widgetRegistry.get($field.attr('name')); var widget = new Widget(self, self.state); - var def = widget.__widgetRenderAndInsert(function () {}); + var def = widget._widgetRenderAndInsert(function () {}); if (def.state() === 'pending') { self.defs.push(def); } @@ -282,7 +282,7 @@ var KanbanRecord = Widget.extend({ * Renders the record */ _render: function () { - this.replaceElement(this.qweb.render('kanban-box', this.qweb_context)); + this._replaceElement(this.qweb.render('kanban-box', this.qweb_context)); this.$el.addClass('o_kanban_record'); this.$el.data('record', this); if (this.$el.hasClass('oe_kanban_global_click') || diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index 0ee834a0efc..b836da253c3 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -274,8 +274,8 @@ var ListRenderer = BasicRenderer.extend({ return $td.append(this._renderWidget(record, node)); } if (node.attrs.widget || (options && options.renderWidgets)) { - var widget = this._renderFieldWidget(node, record, _.pick(options, 'mode')); - return $td.append(widget.$el); + var $el = this._renderFieldWidget(node, record, _.pick(options, 'mode')); + return $td.append($el); } var name = node.attrs.name; var field = this.state.fields[name]; diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js index d279d59d4b1..83112277bb3 100644 --- a/addons/web/static/src/js/views/pivot/pivot_renderer.js +++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js @@ -35,7 +35,7 @@ var PivotRenderer = AbstractRenderer.extend({ _render: function () { if (!this._hasContent()) { // display the nocontent helper - this.replaceElement(QWeb.render('PivotView.nodata')); + this._replaceElement(QWeb.render('PivotView.nodata')); return this._super.apply(this, arguments); } diff --git a/addons/web/static/tests/core/widget_tests.js b/addons/web/static/tests/core/widget_tests.js index 8988002734b..43b9d0dc334 100644 --- a/addons/web/static/tests/core/widget_tests.js +++ b/addons/web/static/tests/core/widget_tests.js @@ -82,17 +82,15 @@ QUnit.module('core', {}, function () { QUnit.test('renderElement, no template, default', function (assert) { - assert.expect(8); + assert.expect(7); var widget = new (Widget.extend({ }))(); - var $original = widget.$el; - assert.ok($original, "should initially have a root element"); + assert.strictEqual(widget.$el, undefined, "should not have a root element"); widget.renderElement(); assert.ok(widget.$el, "should have generated a root element"); - assert.ok($original !== widget.$el, "should have generated a new root element"); assert.strictEqual(widget.$el, widget.$el, "should provide $el alias"); assert.ok(widget.$el.is(widget.el), "should provide raw DOM alias"); @@ -306,7 +304,7 @@ QUnit.module('core', {}, function () { assert.ok(newclicked, "should trigger bound events"); clicked = newclicked = false; - widget.undelegateEvents(); + widget._undelegateEvents(); widget.$('li').click(); assert.ok(!clicked, "undelegate should unbind events delegated"); assert.ok(newclicked, "undelegate should only unbind events it created");