[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)
- ...
This commit is contained in:
Géry Debongnie
2017-12-04 10:03:00 +01:00
parent 595a405b76
commit 5faec34a3c
13 changed files with 224 additions and 191 deletions
@@ -794,10 +794,10 @@ var ManualLineRenderer = LineRenderer.extend({
return $.when.apply($, defs).then(function () { return $.when.apply($, defs).then(function () {
if (!self.fields.title_account_id) { 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 { } else {
self.fields.partner_id.destroy(); 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'));
} }
}); });
}); });
@@ -607,7 +607,6 @@ var Chrome = PosBaseWidget.extend({
$(window).off(); $(window).off();
$('html').off(); $('html').off();
$('body').off(); $('body').off();
this.$el.parent().off();
// The above lines removed the bindings, but we really need them for the barcode // The above lines removed the bindings, but we really need them for the barcode
BarcodeEvents.start(); BarcodeEvents.start();
}, },
@@ -22,6 +22,10 @@ var PosBaseWidget = Widget.extend({
this.pos = options.pos || (parent ? parent.pos : undefined); this.pos = options.pos || (parent ? parent.pos : undefined);
this.chrome = options.chrome || (parent ? parent.chrome : undefined); this.chrome = options.chrome || (parent ? parent.chrome : undefined);
this.gui = options.gui || (parent ? parent.gui : 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){ format_currency: function(amount,precision){
var currency = (this.pos && this.pos.currency) ? this.pos.currency : {symbol:'$', position: 'after', rounding: 0.01, decimals: 2}; var currency = (this.pos && this.pos.currency) ? this.pos.currency : {symbol:'$', position: 'after', rounding: 0.01, decimals: 2};
@@ -540,7 +540,7 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{
*/ */
search_change: function () { search_change: function () {
var self = this; 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)); var facet = this.searchview.query.find(_.bind(this.match_facet, this));
if (!facet) { return; } if (!facet) { return; }
facet.values.each(function (v) { facet.values.each(function (v) {
+185 -157
View File
@@ -21,20 +21,26 @@ var ServicesMixin = require('web.ServicesMixin');
* *
* Here is a sample child class:: * 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 * // the name of the QWeb template to use for rendering
* template: "MyQWebTemplate", * template: "MyQWebTemplate",
* *
* init: function(parent) { * init: function (parent) {
* this._super(parent); * this._super(parent);
* // stuff that you want to init before the rendering * // 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() { * start: function() {
* // stuff you want to make after the rendering, `this.$el` holds a correct value * // stuff you want to make after the rendering, `this.$el` holds a correct value
* this.$(".my_button").click(/* an example of event binding * /); * this.$(".my_button").click(/* an example of event binding * /);
* *
* // if you have some asynchronous operations, it's a good idea to return * // 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(...); * var promise = this._rpc(...);
* return promise; * return promise;
* } * }
@@ -42,8 +48,8 @@ var ServicesMixin = require('web.ServicesMixin');
* *
* Now this class can simply be used with the following syntax:: * Now this class can simply be used with the following syntax::
* *
* var my_widget = new MyWidget(this); * var myWidget = new MyWidget(this);
* my_widget.appendTo($(".some-div")); * myWidget.appendTo($(".some-div"));
* *
* With these two lines, the MyWidget instance was initialized, rendered, * With these two lines, the MyWidget instance was initialized, rendered,
* inserted into the DOM inside the ``.some-div`` div and its events were * 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:: * 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. * That will kill the widget in a clean way and erase its content from the dom.
*/ */
var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
// Backbone-ish API // Backbone-ish API
tagName: 'div', 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 * 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. * redefined in subclasses or the default render() method can not be used.
* *
* @type {String} * @type {null|string}
*/ */
template: null, 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 * be rendered. This will not induce loading anything that has already been
* loaded. * loaded.
* *
* @type {string[]} * @type {null|string[]}
*/ */
xmlDependencies: null, xmlDependencies: null,
/** /**
* Constructs the widget and sets its parent if a parent is given. * 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. * @param {Widget|null} parent Binds the current instance to the given Widget
* When that widget is destroyed by calling destroy(), the current instance will be * instance. When that widget is destroyed by calling destroy(), the
* destroyed too. Can be null. * current instance will be destroyed too. Can be null.
*/ */
init: function (parent) { init: function (parent) {
mixins.PropertiesMixin.init.call(this); mixins.PropertiesMixin.init.call(this);
@@ -93,14 +98,12 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
// Bind on_/do_* methods to this // Bind on_/do_* methods to this
// We might remove this automatic binding in the future // We might remove this automatic binding in the future
for (var name in this) { for (var name in this) {
if(typeof(this[name]) == "function") { if(typeof(this[name]) === "function") {
if((/^on_|^do_/).test(name)) { 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 * Method called between @see init and @see start. Performs asynchronous
@@ -121,65 +124,51 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
return $.when(); 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 () { destroy: function () {
_.each(this.getChildren(), function (el) { _.invoke(this.getChildren(), 'destroy');
el.destroy();
});
if(this.$el) { if(this.$el) {
this.$el.remove(); this.$el.remove();
} }
mixins.PropertiesMixin.destroy.call(this); 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) { appendTo: function (target) {
var self = this; var self = this;
return this.__widgetRenderAndInsert(function (t) { return this._widgetRenderAndInsert(function (t) {
self.$el.appendTo(t); self.$el.appendTo(t);
}, target); }, 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 * Attach the current widget to a dom element
* *
* @param target A jQuery object or a Widget instance. * @param {jQuery} target
*/ */
attachTo: function (target) { attachTo: function (target) {
var self = this; 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. * Hides the widget
*
* @param target A jQuery object or a Widget instance.
*/ */
replace: function (target) { do_hide: function () {
return this.__widgetRenderAndInsert(_.bind(function (t) { this.$el.addClass('o_hidden');
this.$el.replaceAll(t);
}, this), target);
}, },
__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; var self = this;
return this.willStart().then(function () { return this._widgetRenderAndInsert(function (t) {
self.renderElement(); self.$el.insertAfter(t);
insertion(target); }, target);
return self.start();
});
}, },
/** /**
* Method called after rendering. Mostly used to bind actions, perform asynchronous * Renders the current widget and inserts it before to the given jQuery
* calls, etc... * object.
* *
* By convention, this method should return an object that can be passed to $.when() * @param {jQuery} target
* to inform the caller when this widget has been initialized.
*
* @returns {jQuery.Deferred or any}
*/ */
start: function () { insertBefore: function (target) {
return $.when(); var self = this;
return this._widgetRenderAndInsert(function (t) {
self.$el.insertBefore(t);
}, target);
}, },
/** /**
* Renders the element. The default implementation renders the widget using QWeb, * Renders the current widget and prepends it to the given jQuery object.
* `this.template` must be defined. The context given to QWeb contains the "widget" *
* key that references `this`. * @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 () { renderElement: function () {
var $el; var $el;
if (this.template) { if (this.template) {
$el = $(core.qweb.render(this.template, {widget: this}).trim()); $el = $(core.qweb.render(this.template, {widget: this}).trim());
} else { } 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 * Renders the current widget and replaces the given jQuery object.
* (if any) by the new one in the DOM.
* *
* @param {HTMLElement | jQuery} $el * @param target A jQuery object or a Widget instance.
* @returns {Widget} this
*/ */
replaceElement: function ($el) { replace: function (target) {
var $oldel = this.$el; return this._widgetRenderAndInsert(_.bind(function (t) {
this.setElement($el); this.$el.replaceAll(t);
if ($oldel && !$oldel.is(this.$el)) { }, this), target);
if ($oldel.length > 1) {
$oldel.wrapAll('<div/>');
$oldel.parent().replaceWith(this.$el);
} else {
$oldel.replaceWith(this.$el);
}
}
return this;
}, },
/** /**
* Re-sets the widget's root element (el/$el/$el). * 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 * @return {Widget} this
*/ */
setElement: function (element) { setElement: function (element) {
// NB: completely useless, as WidgetMixin#init creates a $el
// always
if (this.$el) { if (this.$el) {
this.undelegateEvents(); this._undelegateEvents();
} }
this.$el = (element instanceof $) ? element : $(element); this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0]; this.el = this.$el[0];
this.delegateEvents(); this._delegateEvents();
return this; 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 * @private
* @param {Object} [attributes] map of DOM attributes to set on the element * @param {string} selector CSS selector, rooted in $el
* @param {String} [content] HTML content to set on the element * @returns {jQuery} selector match
* @return {Element}
*/ */
make: function (tagName, attributes, content) { $: function (selector) {
var el = document.createElement(tagName); if (selector === undefined) {
if (!_.isEmpty(attributes)) { return this.$el;
$(el).attr(attributes);
} }
if (content) { return this.$el.find(selector);
$(el).html(content);
}
return el;
}, },
/** /**
* Makes a potential root element from the declarative builder of the * Attach event handlers for events described in the 'events' key
* widget
* *
* @return {jQuery}
* @private * @private
*/ */
_make_descriptive: function () { _delegateEvents: 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; var events = this.events;
if (_.isEmpty(events)) { return; } 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('<div/>');
$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'); 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 * @private
* @returns {jQuery} selector match * @param {function: jQuery -> any} insertion
* @param {jQuery} target
* @returns {Deferred}
*/ */
$: function (selector) { _widgetRenderAndInsert: function (insertion, target) {
if (selector === undefined) var self = this;
return this.$el; return this.willStart().then(function () {
return this.$el.find(selector); self.renderElement();
}, insertion(target);
/** return self.start();
* 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();
}
}, },
}); });
@@ -438,7 +438,7 @@ var FieldDate = InputField.extend({
def = this.datewidget.appendTo('<div>').done(function () { def = this.datewidget.appendTo('<div>').done(function () {
self.datewidget.$el.addClass(self.$el.attr('class')); self.datewidget.$el.addClass(self.$el.attr('class'));
self._prepareInput(self.datewidget.$input); self._prepareInput(self.datewidget.$input);
self.replaceElement(self.datewidget.$el); self._replaceElement(self.datewidget.$el);
}); });
} }
return $.when(def, this._super.apply(this, arguments)); return $.when(def, this._super.apply(this, arguments));
@@ -483,15 +483,18 @@ var BasicRenderer = AbstractRenderer.extend({
* rendering of the widget will be started and the associated deferred will * 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 * be added to the 'defs' attribute. This is supposed to be created and
* deleted by the calling code if necessary. * 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 * @private
* @param {Object} node * @param {Object} node
* @param {Object} record * @param {Object} record
* @param {Object} [options] * @param {Object} [options]
* @param {Object} [modifiersOptions] * @param {Object} [modifiersOptions]
* @returns {AbstractField} * @returns {jQueryElement}
*/ */
_renderFieldWidget: function (node, record, options, modifiersOptions) { _renderFieldWidget: function (node, record, options, modifiersOptions) {
var fieldName = node.attrs.name; 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 widget.__node = node; // TODO get rid of this if possible one day
// Prepare widget rendering and save the related deferred // Prepare widget rendering and save the related deferred
var def = widget.__widgetRenderAndInsert(function () {}); var def = widget._widgetRenderAndInsert(function () {});
if (def.state() === 'pending') { var async = def.state() === 'pending';
var $el = async ? $('<div>') : widget.$el;
if (async) {
this.defs.push(def); this.defs.push(def);
} }
@@ -526,6 +531,9 @@ var BasicRenderer = AbstractRenderer.extend({
// associated to new widget) // associated to new widget)
var self = this; var self = this;
def.then(function () { def.then(function () {
if (async) {
$el.replaceWith(widget.$el);
}
self._registerModifiers(node, record, widget, _.extend({ self._registerModifiers(node, record, widget, _.extend({
callback: function (element, modifiers, record) { callback: function (element, modifiers, record) {
element.$el.toggleClass('o_field_empty', !!( element.$el.toggleClass('o_field_empty', !!(
@@ -538,7 +546,7 @@ var BasicRenderer = AbstractRenderer.extend({
self._postProcessField(widget, node); self._postProcessField(widget, node);
}); });
return widget; return $el;
}, },
/** /**
* Renders the nocontent helper. * Renders the nocontent helper.
@@ -583,7 +591,7 @@ var BasicRenderer = AbstractRenderer.extend({
var widget = new Widget(this, record); var widget = new Widget(this, record);
// Prepare widget rendering and save the related deferred // Prepare widget rendering and save the related deferred
var def = widget.__widgetRenderAndInsert(function () {}); var def = widget._widgetRenderAndInsert(function () {});
if (def.state() === 'pending') { if (def.state() === 'pending') {
this.defs.push(def); this.defs.push(def);
} }
@@ -602,20 +610,17 @@ var BasicRenderer = AbstractRenderer.extend({
* @private * @private
* @param {Widget} widget * @param {Widget} widget
* @param {Object} record * @param {Object} record
* @returns {AbstractField}
*/ */
_rerenderFieldWidget: function (widget, record) { _rerenderFieldWidget: function (widget, record) {
// Render the new field widget // Render the new field widget
var newWidget = this._renderFieldWidget(widget.__node, record); var $el = this._renderFieldWidget(widget.__node, record);
widget.$el.replaceWith(newWidget.$el); widget.$el.replaceWith($el);
// Destroy the old widget and position the new one at the old one's // Destroy the old widget and position the new one at the old one's
var oldIndex = this._destroyFieldWidget(record.id, widget); var oldIndex = this._destroyFieldWidget(record.id, widget);
var recordWidgets = this.allFieldWidgets[record.id]; var recordWidgets = this.allFieldWidgets[record.id];
var newWidget = recordWidgets.pop();
recordWidgets.splice(oldIndex, 0, newWidget); recordWidgets.splice(oldIndex, 0, newWidget);
recordWidgets.pop();
return newWidget;
}, },
/** /**
* Unregisters an element of the modifiers data associated to the given * Unregisters an element of the modifiers data associated to the given
@@ -443,8 +443,8 @@ var FormRenderer = BasicRenderer.extend({
* @returns {jQueryElement} * @returns {jQueryElement}
*/ */
_renderInnerGroupField: function (node) { _renderInnerGroupField: function (node) {
var widget = this._renderFieldWidget(node, this.state); var $el = this._renderFieldWidget(node, this.state);
var $tds = $('<td/>').append(widget.$el); var $tds = $('<td/>').append($el);
if (node.attrs.nolabel !== '1') { if (node.attrs.nolabel !== '1') {
var $labelTd = this._renderInnerGroupLabel(node); var $labelTd = this._renderInnerGroupLabel(node);
@@ -570,7 +570,7 @@ var FormRenderer = BasicRenderer.extend({
* @returns {jQueryElement} * @returns {jQueryElement}
*/ */
_renderTagField: function (node) { _renderTagField: function (node) {
return this._renderFieldWidget(node, this.state).$el; return this._renderFieldWidget(node, this.state);
}, },
/** /**
* @private * @private
@@ -624,8 +624,8 @@ var FormRenderer = BasicRenderer.extend({
$statusbar.append(this._renderHeaderButtons(node)); $statusbar.append(this._renderHeaderButtons(node));
_.each(node.children, function (child) { _.each(node.children, function (child) {
if (child.tag === 'field') { if (child.tag === 'field') {
var widget = self._renderFieldWidget(child, self.state); var $el = self._renderFieldWidget(child, self.state);
$statusbar.append(widget.$el); $statusbar.append($el);
} }
}); });
this._handleAttributes($statusbar, node); this._handleAttributes($statusbar, node);
@@ -34,7 +34,6 @@ return AbstractRenderer.extend({
init: function (parent, state, params) { init: function (parent, state, params) {
this._super.apply(this, arguments); this._super.apply(this, arguments);
this.stacked = params.stacked; this.stacked = params.stacked;
this.$el.css({minWidth: '100px', minHeight: '100px'});
}, },
/** /**
* @override * @override
@@ -270,7 +270,7 @@ var KanbanRecord = Widget.extend({
var Widget = widgetRegistry.get($field.attr('name')); var Widget = widgetRegistry.get($field.attr('name'));
var widget = new Widget(self, self.state); var widget = new Widget(self, self.state);
var def = widget.__widgetRenderAndInsert(function () {}); var def = widget._widgetRenderAndInsert(function () {});
if (def.state() === 'pending') { if (def.state() === 'pending') {
self.defs.push(def); self.defs.push(def);
} }
@@ -282,7 +282,7 @@ var KanbanRecord = Widget.extend({
* Renders the record * Renders the record
*/ */
_render: function () { _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.addClass('o_kanban_record');
this.$el.data('record', this); this.$el.data('record', this);
if (this.$el.hasClass('oe_kanban_global_click') || if (this.$el.hasClass('oe_kanban_global_click') ||
@@ -274,8 +274,8 @@ var ListRenderer = BasicRenderer.extend({
return $td.append(this._renderWidget(record, node)); return $td.append(this._renderWidget(record, node));
} }
if (node.attrs.widget || (options && options.renderWidgets)) { if (node.attrs.widget || (options && options.renderWidgets)) {
var widget = this._renderFieldWidget(node, record, _.pick(options, 'mode')); var $el = this._renderFieldWidget(node, record, _.pick(options, 'mode'));
return $td.append(widget.$el); return $td.append($el);
} }
var name = node.attrs.name; var name = node.attrs.name;
var field = this.state.fields[name]; var field = this.state.fields[name];
@@ -35,7 +35,7 @@ var PivotRenderer = AbstractRenderer.extend({
_render: function () { _render: function () {
if (!this._hasContent()) { if (!this._hasContent()) {
// display the nocontent helper // display the nocontent helper
this.replaceElement(QWeb.render('PivotView.nodata')); this._replaceElement(QWeb.render('PivotView.nodata'));
return this._super.apply(this, arguments); return this._super.apply(this, arguments);
} }
+3 -5
View File
@@ -82,17 +82,15 @@ QUnit.module('core', {}, function () {
QUnit.test('renderElement, no template, default', function (assert) { QUnit.test('renderElement, no template, default', function (assert) {
assert.expect(8); assert.expect(7);
var widget = new (Widget.extend({ }))(); var widget = new (Widget.extend({ }))();
var $original = widget.$el; assert.strictEqual(widget.$el, undefined, "should not have a root element");
assert.ok($original, "should initially have a root element");
widget.renderElement(); widget.renderElement();
assert.ok(widget.$el, "should have generated a root element"); 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.strictEqual(widget.$el, widget.$el, "should provide $el alias");
assert.ok(widget.$el.is(widget.el), "should provide raw DOM 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"); assert.ok(newclicked, "should trigger bound events");
clicked = newclicked = false; clicked = newclicked = false;
widget.undelegateEvents(); widget._undelegateEvents();
widget.$('li').click(); widget.$('li').click();
assert.ok(!clicked, "undelegate should unbind events delegated"); assert.ok(!clicked, "undelegate should unbind events delegated");
assert.ok(newclicked, "undelegate should only unbind events it created"); assert.ok(newclicked, "undelegate should only unbind events it created");