[FIX] web: reimplement custom widgets in basic views
With the new views, we lost a (almost unused and undocumented) feature:
the ability to instantiate custom widgets in a form view, not linked to
a particular field. For example:
<widget type="weekly_timesheet" attrs="{'readonly': [['state', 'not in', ['new', 'draft']]]}"/>
We reintroduce this feature in this commit, with a nice twist: it also
works for the list view and the kanban view.
This commit is contained in:
@@ -11,6 +11,7 @@ var AbstractRenderer = require('web.AbstractRenderer');
|
||||
var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var dom = require('web.dom');
|
||||
var widgetRegistry = require('web.widget_registry');
|
||||
|
||||
var qweb = core.qweb;
|
||||
|
||||
@@ -331,6 +332,19 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
_getModifiersData: function (node) {
|
||||
return _.findWhere(this.allModifiersData, {node: node});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {jQueryElement} $el
|
||||
* @param {Object} node
|
||||
*/
|
||||
_handleAttributes: function ($el, node) {
|
||||
if (node.attrs.class) {
|
||||
$el.addClass(node.attrs.class);
|
||||
}
|
||||
if (node.attrs.style) {
|
||||
$el.attr('style', node.attrs.style);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Used by list and kanban renderers to determine whether or not to display
|
||||
* the no content helper (if there is no data in the state to display)
|
||||
@@ -540,6 +554,31 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
_renderView: function () {
|
||||
return $.when();
|
||||
},
|
||||
/**
|
||||
* Instantiate custom widgets
|
||||
*
|
||||
* @private
|
||||
* @param {Object} record
|
||||
* @param {Object} node
|
||||
* @returns {jQueryElement}
|
||||
*/
|
||||
_renderWidget: function (record, node) {
|
||||
var Widget = widgetRegistry.get(node.attrs.name);
|
||||
var widget = new Widget(this, record);
|
||||
|
||||
// Prepare widget rendering and save the related deferred
|
||||
var def = widget.__widgetRenderAndInsert(function () {});
|
||||
if (def.state() === 'pending') {
|
||||
this.defs.push(def);
|
||||
}
|
||||
|
||||
// handle other attributes/modifiers
|
||||
this._handleAttributes(widget.$el, node);
|
||||
this._registerModifiers(node, record, widget);
|
||||
widget.$el.addClass('o_widget');
|
||||
return widget.$el;
|
||||
},
|
||||
|
||||
/**
|
||||
* Rerenders a given widget and make sure the associated data which
|
||||
* referenced the old one is updated.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
odoo.define('web.widget_registry', function (require) {
|
||||
"use strict";
|
||||
|
||||
// This registry is supposed to contain all custom widgets that will be
|
||||
// available in the basic views, with the tag <widget/>. There are
|
||||
// currently no such widget in the web client, but the functionality is
|
||||
// certainly useful to be able to cleanly add custom behaviour in basic
|
||||
// views (and most notably, the form view)
|
||||
//
|
||||
// The way custom widgets work is that they register themselves to this
|
||||
// registry:
|
||||
//
|
||||
// widgetRegistry.add('some_name', MyWidget);
|
||||
//
|
||||
// Then, they are available with the <widget/> tag (in the arch):
|
||||
//
|
||||
// <widget name="some_name"/>
|
||||
//
|
||||
// Widgets will be then properly instantiated, rendered and destroyed at the
|
||||
// appropriate time, with the current state in second argument.
|
||||
//
|
||||
// For more examples, look at the tests (grep '<widget' in the test folder)
|
||||
|
||||
var Registry = require('web.Registry');
|
||||
|
||||
return new Registry();
|
||||
});
|
||||
@@ -225,19 +225,6 @@ var FormRenderer = BasicRenderer.extend({
|
||||
}
|
||||
return idForLabel;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {jQueryElement} $el
|
||||
* @param {Object} node
|
||||
*/
|
||||
_handleAttributes: function ($el, node) {
|
||||
if (node.attrs.class) {
|
||||
$el.addClass(node.attrs.class);
|
||||
}
|
||||
if (node.attrs.style) {
|
||||
$el.attr('style', node.attrs.style);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} node
|
||||
@@ -758,6 +745,16 @@ var FormRenderer = BasicRenderer.extend({
|
||||
$sheet.append(_.map(node.children, this._renderNode.bind(this)));
|
||||
return $sheet;
|
||||
},
|
||||
/**
|
||||
* Instantiate custom widgets
|
||||
*
|
||||
* @private
|
||||
* @param {Object} node
|
||||
* @returns {jQueryElement}
|
||||
*/
|
||||
_renderTagWidget: function (node) {
|
||||
return this._renderWidget(this.state, node);
|
||||
},
|
||||
/**
|
||||
* Main entry point for the rendering. From here, we call _renderNode on
|
||||
* the root of the arch, then, when every deferred (from the field widgets)
|
||||
|
||||
@@ -11,6 +11,7 @@ var Domain = require('web.Domain');
|
||||
var field_utils = require('web.field_utils');
|
||||
var utils = require('web.utils');
|
||||
var Widget = require('web.Widget');
|
||||
var widgetRegistry = require('web.widget_registry');
|
||||
|
||||
var _t = core._t;
|
||||
var QWeb = core.qweb;
|
||||
@@ -262,6 +263,21 @@ var KanbanRecord = Widget.extend({
|
||||
this._setFieldDisplay(widget.$el, field_name);
|
||||
return widget;
|
||||
},
|
||||
_processWidgets: function () {
|
||||
var self = this;
|
||||
this.$("widget").each(function () {
|
||||
var $field = $(this);
|
||||
var Widget = widgetRegistry.get($field.attr('name'));
|
||||
var widget = new Widget(self, self.state);
|
||||
|
||||
var def = widget.__widgetRenderAndInsert(function () {});
|
||||
if (def.state() === 'pending') {
|
||||
self.defs.push(def);
|
||||
}
|
||||
widget.$el.addClass('o_widget');
|
||||
$field.replaceWith(widget.$el);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Renders the record
|
||||
*/
|
||||
@@ -274,6 +290,7 @@ var KanbanRecord = Widget.extend({
|
||||
this.$el.on('click', this._onGlobalClick.bind(this));
|
||||
}
|
||||
this._processFields();
|
||||
this._processWidgets();
|
||||
this._setupColor();
|
||||
this._setupColorPicker();
|
||||
this._attachTooltip();
|
||||
|
||||
@@ -222,7 +222,7 @@ var ListRenderer = BasicRenderer.extend({
|
||||
var tdClassName = 'o_data_cell';
|
||||
if (node.tag === 'button') {
|
||||
tdClassName += ' o_list_button';
|
||||
} else {
|
||||
} else if (node.tag === 'field') {
|
||||
var typeClass = FIELD_CLASSES[this.state.fields[node.attrs.name].type];
|
||||
if (typeClass) {
|
||||
tdClassName += (' ' + typeClass);
|
||||
@@ -245,6 +245,8 @@ var ListRenderer = BasicRenderer.extend({
|
||||
|
||||
if (node.tag === 'button') {
|
||||
return $td.append(this._renderButton(record, node));
|
||||
} else if (node.tag === 'widget') {
|
||||
return $td.append(this._renderWidget(record, node));
|
||||
}
|
||||
if (node.attrs.widget || (options && options.renderWidgets)) {
|
||||
var widget = this._renderFieldWidget(node, record, _.pick(options, 'mode'));
|
||||
|
||||
@@ -8,6 +8,8 @@ var FormView = require('web.FormView');
|
||||
var pyeval = require('web.pyeval');
|
||||
var RainbowMan = require('web.rainbow_man');
|
||||
var testUtils = require('web.test_utils');
|
||||
var widgetRegistry = require('web.widget_registry');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var _t = core._t;
|
||||
var createView = testUtils.createView;
|
||||
@@ -5867,5 +5869,36 @@ QUnit.module('Views', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('basic support for widgets', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var MyWidget = Widget.extend({
|
||||
init: function (parent, dataPoint) {
|
||||
this.data = dataPoint.data;
|
||||
},
|
||||
start: function () {
|
||||
this.$el.text(JSON.stringify(this.data));
|
||||
},
|
||||
});
|
||||
widgetRegistry.add('test', MyWidget);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<field name="foo"/>' +
|
||||
'<field name="bar"/>' +
|
||||
'<widget name="test"/>' +
|
||||
'</form>',
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$el.text(), '{"foo":"My little Foo Value","bar":false}',
|
||||
"widget should have been instantiated");
|
||||
|
||||
form.destroy();
|
||||
delete widgetRegistry.map.test;
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,8 @@ odoo.define('web.kanban_tests', function (require) {
|
||||
|
||||
var KanbanView = require('web.KanbanView');
|
||||
var testUtils = require('web.test_utils');
|
||||
var widgetRegistry = require('web.widget_registry');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
@@ -1898,6 +1900,40 @@ QUnit.module('Views', {
|
||||
"the add button should still be visible");
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('basic support for widgets', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var MyWidget = Widget.extend({
|
||||
init: function (parent, dataPoint) {
|
||||
this.data = dataPoint.data;
|
||||
},
|
||||
start: function () {
|
||||
this.$el.text(JSON.stringify(this.data));
|
||||
},
|
||||
});
|
||||
widgetRegistry.add('test', MyWidget);
|
||||
|
||||
var kanban = createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<kanban class="o_kanban_test"><templates><t t-name="kanban-box">' +
|
||||
'<div>' +
|
||||
'<t t-esc="record.foo.value"/>' +
|
||||
'<field name="foo" blip="1"/>' +
|
||||
'<widget name="test"/>' +
|
||||
'</div>' +
|
||||
'</t></templates></kanban>',
|
||||
});
|
||||
|
||||
assert.strictEqual(kanban.$('.o_widget:eq(2)').text(), '{"foo":"gnap","id":3}',
|
||||
"widget should have been instantiated");
|
||||
|
||||
kanban.destroy();
|
||||
delete widgetRegistry.map.test;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ var config = require('web.config');
|
||||
var FormView = require('web.FormView');
|
||||
var ListView = require('web.ListView');
|
||||
var testUtils = require('web.test_utils');
|
||||
var widgetRegistry = require('web.widget_registry');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
@@ -2936,6 +2938,34 @@ QUnit.module('Views', {
|
||||
"The first element of the row name should be a span");
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('basic support for widgets', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var MyWidget = Widget.extend({
|
||||
init: function (parent, dataPoint) {
|
||||
this.data = dataPoint.data;
|
||||
},
|
||||
start: function () {
|
||||
this.$el.text(JSON.stringify(this.data));
|
||||
},
|
||||
});
|
||||
widgetRegistry.add('test', MyWidget);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree><field name="foo"/><field name="int_field"/><widget name="test"/></tree>',
|
||||
});
|
||||
|
||||
assert.strictEqual(list.$('.o_widget').first().text(), '{"foo":"yop","int_field":10,"id":1}',
|
||||
"widget should have been instantiated");
|
||||
|
||||
list.destroy();
|
||||
delete widgetRegistry.map.test;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/fields/abstract_field.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/fields/basic_fields.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/fields/field_registry.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/basic/widget_registry.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/fields/field_utils.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/fields/relational_fields.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/fields/special_fields.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user