summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2018-07-10 13:52:28 +0200
committerSébastien Theys <seb@odoo.com>2018-07-17 11:50:35 +0200
commitf241441433046c41e4f3edd05d5d69028da1f8a7 (patch)
tree5224ef02e0ad137f96219339b38fadeae7dca38f /addons/web/static
parentd1f1cc9c275de726de3cd2491c8d8b4866bbeb0e (diff)
[IMP] web: add control + create tags to customize "add a line"
The default "add a line" button creates a new line with the default values from the model. Sometimes we want to create a new line with different default values (or more generally a customized context), which is possible with this commit. Moreover it is possible to create multiple buttons for multiple default values/contexts. This works both for in-line creation ("editable=") and dialog creation. The new tags are: <control> defines custom controls for the current view. Does not support any attribute, but can have children: <create> adds a button to create a new element on the current list. This makes sense when the parent tree view is inside a One2many field. If any create is defined, it will overwrite the default "add a line" button. For more information and example, have a look at the Views documentation. Technically, the commit will: - parse the new XML tags (+ add appropriate validation) - display the buttons based on the XML - on button click/keyboard: pull up the additional context information from the button event up to the default_get - add unit tests - add documentation PR #25209
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js12
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js11
-rw-r--r--addons/web/static/src/js/views/field_manager_mixin.js2
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js64
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js3
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js106
6 files changed, 185 insertions, 13 deletions
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index b48c0dfbe69..104d8f3bed8 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -1266,11 +1266,18 @@ var FieldOne2Many = FieldX2Many.extend({
/**
* @private
* @param {Object} params
+ * @param {Object} [params.context] We allow additional context, this is
+ * used for example to define default values when adding new lines to
+ * a one2many with control/create tags.
*/
_openFormDialog: function (params) {
+ var context = this.record.getContext(_.extend({},
+ this.recordParams,
+ { additionalContext: params.context }
+ ));
this.trigger_up('open_one2many_record', _.extend(params, {
domain: this.record.getDomain(this.recordParams),
- context: this.record.getContext(this.recordParams),
+ context: context,
field: this.field,
fields_view: this.attrs.views && this.attrs.views.form,
parentID: this.value.id,
@@ -1297,6 +1304,7 @@ var FieldOne2Many = FieldX2Many.extend({
// we don't want interference with the components upstream.
ev.stopPropagation();
+
if (this.editable) {
if (!this.activeActions.create) {
if (data.onFail) {
@@ -1308,12 +1316,14 @@ var FieldOne2Many = FieldX2Many.extend({
this._setValue({
operation: 'CREATE',
position: this.editable,
+ context: data.context,
}).always(function () {
self.creatingRecord = false;
});
}
} else {
this._openFormDialog({
+ context: data.context,
on_saved: function (record) {
self._setValue({ operation: 'ADD', id: record.id });
},
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index 86308a32736..e0c516f02fb 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -1315,13 +1315,17 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {string} [options.position=top] if the new record should be added
* on top or on bottom of the list
+ * @param {Object} [options.context] additional context to be merged before
+ * calling the default_get (eg. to set default values)
* @returns {Deferred<string>} resolves to the new record id
*/
_addX2ManyDefaultRecord: function (list, options) {
var self = this;
var position = options && options.position || 'top';
+ var additionalContext = options && options.context;
+ var context = this._getContext(list, {additionalContext: additionalContext});
var params = {
- context: this._getContext(list),
+ context: context,
fields: list.fields,
fieldsInfo: list.fieldsInfo,
parentID: list.id,
@@ -1799,7 +1803,10 @@ var BasicModel = AbstractModel.extend({
}
break;
case 'CREATE':
- var options = {position: command.position};
+ var options = {
+ context: command.context,
+ position: command.position,
+ };
def = this._addX2ManyDefaultRecord(list, options).then(function (id) {
if (command.position === 'bottom' && list.orderedResIDs && list.orderedResIDs.length >= list.limit) {
list.tempLimitIncrement = (list.tempLimitIncrement || 0) + 1;
diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js
index 0bc1b5acfce..cf67b9dd18d 100644
--- a/addons/web/static/src/js/views/field_manager_mixin.js
+++ b/addons/web/static/src/js/views/field_manager_mixin.js
@@ -46,7 +46,7 @@ var FieldManagerMixin = {
*/
_applyChanges: function (dataPointID, changes, event) {
var self = this;
- var options = _.pick(event.data, 'viewType', 'doNotSetDirty', 'notifyChange');
+ var options = _.pick(event.data, 'context', 'doNotSetDirty', 'notifyChange', 'viewType');
return this.model.notifyChanges(dataPointID, changes, options)
.then(function (result) {
if (event.data.force_save) {
diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js
index 382df0b9444..5c33a32b6d5 100644
--- a/addons/web/static/src/js/views/list/list_editable_renderer.js
+++ b/addons/web/static/src/js/views/list/list_editable_renderer.js
@@ -15,6 +15,7 @@ var core = require('web.core');
var dom = require('web.dom');
var ListRenderer = require('web.ListRenderer');
var utils = require('web.utils');
+var Context = require('web.Context');
var _t = core._t;
@@ -37,12 +38,49 @@ ListRenderer.include({
* @param {boolean} params.addTrashIcon
*/
init: function (parent, state, params) {
+ var self = this
this._super.apply(this, arguments);
// if addCreateLine is true, the renderer will add a 'Add a line' link
// at the bottom of the list view
this.addCreateLine = params.addCreateLine;
+ // Controls allow overriding "add a line" by custom controls.
+
+ // Each <control> (only one is actually needed) is a container for (multiple) <create>.
+ // Each <create> will be a "add a line" button with custom text and context.
+
+ // The following code will browse the arch to find
+ // all the <create> that are inside <control>
+
+ if (this.addCreateLine) {
+ this.creates = [];
+
+ _.each(this.arch.children, function (child) {
+ if (child.tag !== 'control') {
+ return;
+ }
+
+ _.each(child.children, function (child) {
+ if (child.tag !== 'create') {
+ return;
+ }
+
+ self.creates.push({
+ 'context': child.attrs.context,
+ 'string': child.attrs.string,
+ });
+ });
+ });
+
+ // Add the default button if we didn't find any custom button.
+ if (this.creates.length === 0) {
+ this.creates.push({
+ string: _t("Add a line"),
+ });
+ }
+ }
+
// if addTrashIcon is true, there will be a small trash icon at the end
// of each line, so the user can delete a record.
this.addTrashIcon = params.addTrashIcon;
@@ -525,15 +563,23 @@ ListRenderer.include({
* @returns {jQueryElement}
*/
_renderRows: function () {
+ var self = this;
var $rows = this._super();
+
if (this.addCreateLine) {
- var $a = $('<a href="#" role="button">').text(_t("Add a line"));
var $td = $('<td>')
- .attr('colspan', this._getNumberOfCols())
- .addClass('o_field_x2many_list_row_add')
- .append($a);
+ .attr('colspan', self._getNumberOfCols())
+ .addClass('o_field_x2many_list_row_add');
var $tr = $('<tr>').append($td);
$rows.push($tr);
+
+ _.each(self.creates, function (create) {
+ var $a = $('<a href="#" role="button">')
+ .attr('data-context', create.context)
+ .addClass('ml16')
+ .text(create.string);
+ $td.append($a);
+ });
}
return $rows;
},
@@ -701,20 +747,20 @@ ListRenderer.include({
* This method is called when we click on the 'Add a line' button in a sub
* list such as a one2many in a form view.
*
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onAddRecord: function (event) {
+ _onAddRecord: function (ev) {
// we don't want the browser to navigate to a the # url
- event.preventDefault();
+ ev.preventDefault();
// we don't want the click to cause other effects, such as unselecting
// the row that we are creating, because it counts as a click on a tr
- event.stopPropagation();
+ ev.stopPropagation();
// but we do want to unselect current row
var self = this;
this.unselectRow().then(function () {
- self.trigger_up('add_record'); // TODO write a test, the deferred was not considered
+ self.trigger_up('add_record', {context: ev.currentTarget.dataset.context}); // TODO write a test, the deferred was not considered
});
},
/**
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 79304309eb6..82a08b3b27f 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -184,6 +184,9 @@ var ListRenderer = BasicRenderer.extend({
var self = this;
self.handleField = null;
this.columns = _.reject(this.arch.children, function (c) {
+ if (c.tag === 'control') {
+ return true;
+ }
var reject = c.attrs.modifiers.column_invisible;
// If there is an evaluated domain for the field we override the node
// attribute to have the evaluated modifier value.
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 54620fcf958..2001bed1d65 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -9797,6 +9797,112 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('add a line custom control editable', function (assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree editable="bottom">' +
+ '<control>' +
+ '<create string="Add food" context="" />' +
+ '<create string="Add pizza" context="{\'default_display_name\': \'pizza\'}"/>' +
+ '</control>' +
+
+ '<control>' +
+ '<create string="Add pasta" context="{\'default_display_name\': \'pasta\'}"/>' +
+ '</control>' +
+
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '<form>' +
+ '<field name="display_name"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ });
+
+ // new controls correctly added
+ assert.strictEqual($('.o_field_x2many_list_row_add').length, 1);
+ assert.strictEqual($('.o_field_x2many_list_row_add').text(), "Add foodAdd pizzaAdd pasta");
+
+ // click add food
+ // check it's empty
+ form.$('.o_field_x2many_list_row_add a:eq(0)').click();
+ assert.strictEqual($('.o_data_cell').text(), "");
+
+ // click add pizza
+ // save the modal
+ // check it's pizza
+ form.$('.o_field_x2many_list_row_add a:eq(1)').click();
+ // click add pasta
+ form.$('.o_field_x2many_list_row_add a:eq(2)').click();
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual($('.o_data_cell').text(), "pizzapasta");
+
+ form.destroy();
+ });
+
+ QUnit.test('add a line custom control non-editable', function (assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:
+ '<form string="Partners">' +
+ '<field name="p">' +
+ '<tree>' +
+ '<control>' +
+ '<create string="Add food" context="" />' +
+ '<create string="Add pizza" context="{\'default_display_name\': \'pizza\'}" />' +
+ '</control>' +
+
+ '<control>' +
+ '<create string="Add pasta" context="{\'default_display_name\': \'pasta\'}" />' +
+ '</control>' +
+
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '<form>' +
+ '<field name="display_name"/>' +
+ '</form>' +
+ '</field>' +
+ '</form>',
+ });
+
+ // new controls correctly added
+ assert.strictEqual($('.o_field_x2many_list_row_add').length, 1);
+ assert.strictEqual($('.o_field_x2many_list_row_add').text(), "Add foodAdd pizzaAdd pasta");
+
+ // click add food
+ // check it's empty
+ form.$('.o_field_x2many_list_row_add a:eq(0)').click();
+ $('.modal .modal-footer .btn-primary:first').click();
+ assert.strictEqual($('.o_data_cell').text(), "");
+
+ // click add pizza
+ // save the modal
+ // check it's pizza
+ form.$('.o_field_x2many_list_row_add a:eq(1)').click();
+ $('.modal .modal-footer .btn-primary:first').click();
+ assert.strictEqual($('.o_data_cell').text(), "pizza");
+
+ // click add pasta
+ // save the whole record
+ // check it's pizzapasta
+ form.$('.o_field_x2many_list_row_add a:eq(2)').click();
+ $('.modal .modal-footer .btn-primary:first').click();
+ assert.strictEqual($('.o_data_cell').text(), "pizzapasta");
+
+ form.destroy();
+ });
+
QUnit.test('one2many form view with action button', function (assert) {
// once the action button is clicked, the record is reloaded (via the
// on_close handler, executed because the python method does not return