summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorAaron Bohy <aab@odoo.com>2018-01-02 16:25:28 +0100
committerAaron Bohy <aab@odoo.com>2018-01-26 19:26:03 +0100
commitd18d08f01a589053e40c7af1fd4dd59c13aaa625 (patch)
tree63a243bccee505c4263fe441357946401cf7985e /addons/web/static/src
parentaf2686fee55b20d74ce1799aea84bf1910b20051 (diff)
[IMP] web: Kanban: quick create record with more fields
This rev. improves the QuickCreate widget for grouped Kanban views. It allows specifying in the Kanban arch a form view xmlid, e.g.: <kanban quick_create_view="some_form_view_xmlid"> ... </kanban> When this attribute is set, the referenced form view is loaded and rendered inside the QuickCreate widget. A real FormView widget is instantiated, meaning that all form view mechanisms are available in the QuickCreate (modifiers, default_get, onchanges, widgets...). Task #27475.
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js11
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js11
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_column.js38
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js59
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_model.js40
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_quick_create.js411
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer.js43
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js4
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_view.js1
-rw-r--r--addons/web/static/src/js/views/kanban/quick_create_form_view.js110
-rw-r--r--addons/web/static/src/less/form_view.less51
-rw-r--r--addons/web/static/src/less/form_view_extra.less7
-rw-r--r--addons/web/static/src/less/kanban_view.less11
-rw-r--r--addons/web/static/src/xml/kanban.xml5
14 files changed, 583 insertions, 219 deletions
diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js
index 2c7c1c40210..c4dff8fad2e 100644
--- a/addons/web/static/src/js/views/basic/basic_controller.js
+++ b/addons/web/static/src/js/views/basic/basic_controller.js
@@ -68,7 +68,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* rejected otherwise
*/
canBeDiscarded: function (recordID) {
- if (!this.model.isDirty(recordID || this.handle)) {
+ if (!this.isDirty(recordID)) {
return $.when(false);
}
@@ -121,6 +121,15 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
return [];
},
/**
+ * Returns true iff the given recordID (or the main recordID) is dirty.
+ *
+ * @param {string} [recordID] - default to main recordID
+ * @returns {boolean}
+ */
+ isDirty: function (recordID) {
+ return this.model.isDirty(recordID || this.handle);
+ },
+ /**
* @override
*/
renderPager: function ($node, options) {
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 7d9c3cd2d78..b289663fb19 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -18,10 +18,19 @@ var FormRenderer = BasicRenderer.extend({
/**
* @override
*/
- init: function (parent, state, params) {
+ init: function () {
this._super.apply(this, arguments);
this.idsForLabels = {};
},
+ /**
+ * @override
+ */
+ start: function () {
+ if (config.device.size_class <= config.device.SIZES.XXS) {
+ this.$el.addClass('o_xxs_form_view');
+ }
+ return this._super.apply(this, arguments);
+ },
//--------------------------------------------------------------------------
// Public
diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js
index bde447870bb..563df7a4630 100644
--- a/addons/web/static/src/js/views/kanban/kanban_column.js
+++ b/addons/web/static/src/js/views/kanban/kanban_column.js
@@ -47,6 +47,8 @@ var KanbanColumn = Widget.extend({
this.modelName = data.model;
this.quick_create = options.quick_create;
+ this.quickCreateView = options.quickCreateView;
+ this.groupedBy = options.groupedBy;
this.grouped_by_m2o = options.grouped_by_m2o;
this.editable = options.editable;
this.deletable = options.deletable;
@@ -156,6 +158,18 @@ var KanbanColumn = Widget.extend({
return $.when.apply($, defs);
},
+ /**
+ * Called when a record has been quick created, as a new column is rendered
+ * and appended into a fragment, before replacing the old column in the DOM.
+ * When this happens, the quick create widget is inserted into the new
+ * column directly, and it should be focused. However, as it is rendered
+ * into a fragment, the focus has to be set manually once in the DOM.
+ */
+ on_attach_callback: function () {
+ if (this.quickCreateWidget) {
+ this.quickCreateWidget.on_attach_callback();
+ }
+ },
//--------------------------------------------------------------------------
// Public
@@ -163,6 +177,8 @@ var KanbanColumn = Widget.extend({
/**
* Adds the quick create record to the top of the column.
+ *
+ * @returns {Deferred}
*/
addQuickCreate: function () {
if (this.folded) {
@@ -173,11 +189,25 @@ var KanbanColumn = Widget.extend({
return;
}
if (this.quickCreateWidget) {
- return;
+ return $.Deferred().reject();
+ }
+ this.trigger_up('close_quick_create'); // close other quick create widgets
+ var context = this.data.getContext();
+ context['default_' + this.groupedBy] = this.id;
+ this.quickCreateWidget = new RecordQuickCreate(this, {
+ context: context,
+ formViewRef: this.quickCreateView,
+ model: this.modelName,
+ });
+ return this.quickCreateWidget.insertAfter(this.$header);
+ },
+ /**
+ * Closes the quick create widget if it isn't dirty.
+ */
+ cancelQuickCreate: function () {
+ if (this.quickCreateWidget) {
+ this.quickCreateWidget.cancel();
}
- var width = this.records.length ? this.records[0].$el.innerWidth() : this.$el.width() - 8;
- this.quickCreateWidget = new RecordQuickCreate(this, width);
- this.quickCreateWidget.insertAfter(this.$header);
},
/**
* @returns {Boolean} true iff the column is empty
diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js
index 2ddd5577330..ca2999b05b0 100644
--- a/addons/web/static/src/js/views/kanban/kanban_controller.js
+++ b/addons/web/static/src/js/views/kanban/kanban_controller.js
@@ -297,52 +297,51 @@ var KanbanController = BasicController.extend({
/**
* @private
* @param {OdooEvent} event
+ * @param {KanbanColumn} event.target the column in which the record should
+ * be added
+ * @param {Object} event.data.values the field values of the record to
+ * create; if values only contains the value of the 'display_name', a
+ * 'name_create' is performed instead of 'create'
*/
_onQuickCreateRecord: function (event) {
var self = this;
+ var values = event.data.values;
var column = event.target;
- var name = event.data.value;
- var state = this.model.get(this.handle, {raw: true});
- var columnState = this.model.get(column.db_id, {raw: true});
- var context = columnState.getContext();
- context['default_' + state.groupedBy[0]] = columnState.res_id;
- this._rpc({
- model: state.model,
- method: 'name_create',
- args: [name],
- context: context,
- })
- .then(add_record)
+ // function that updates the kanban view once the record has been added
+ // it receives the local id of the created record in arguments
+ var update = function (db_id) {
+ self._updateEnv();
+
+ var columnState = self.model.getColumn(db_id);
+ return self.renderer
+ .updateColumn(columnState.id, columnState, {openQuickCreate: true})
+ .then(function () {
+ if (event.data.openRecord) {
+ self.trigger_up('open_record', {id: db_id, mode: 'edit'});
+ }
+ });
+ };
+
+ this.model.createRecordInGroup(column.db_id, values)
+ .then(update)
.fail(function (error, event) {
event.preventDefault();
+ var columnState = self.model.get(column.db_id, {raw: true});
+ var context = columnState.getContext();
+ var state = self.model.get(self.handle, {raw: true});
+ context['default_' + state.groupedBy[0]] = columnState.res_id;
new view_dialogs.FormViewDialog(self, {
res_model: state.model,
context: _.extend({default_name: name}, context),
title: _t("Create"),
disable_multiple_selection: true,
on_saved: function (record) {
- add_record([record.res_id]);
+ self.model.addRecordToGroup(column.db_id, record.res_id)
+ .then(update);
},
}).open();
});
-
- function add_record(records) {
- return self.model
- .addRecordToGroup(columnState.id, records[0])
- .then(function (db_id) {
- self._updateEnv();
-
- var columnState = self.model.getColumn(db_id);
- return self.renderer
- .updateColumn(columnState.id, columnState, {openQuickCreate: true})
- .then(function () {
- if (event.data.openRecord) {
- self.trigger_up('open_record', {id: db_id, mode: 'edit'});
- }
- });
- });
- }
},
/**
* @private
diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js
index df6172f0d8b..ba6b2ce3dbd 100644
--- a/addons/web/static/src/js/views/kanban/kanban_model.js
+++ b/addons/web/static/src/js/views/kanban/kanban_model.js
@@ -33,7 +33,7 @@ var KanbanModel = BasicModel.extend({
parentID: groupID,
});
- return this._fetchRecord(new_record).then(function (result) {
+ var def = this._fetchRecord(new_record).then(function (result) {
group.data.unshift(new_record.id);
group.res_ids.unshift(resId);
group.count++;
@@ -44,6 +44,7 @@ var KanbanModel = BasicModel.extend({
return result.id;
});
+ return this._reloadProgressBarGroupFromRecord(new_record.id, def);
},
/**
* Creates a new group from a name (performs a name_create).
@@ -94,6 +95,43 @@ var KanbanModel = BasicModel.extend({
});
},
/**
+ * Creates a new record from the given value, and add it to the given group.
+ *
+ * @param {string} groupID
+ * @param {Object} values
+ * @returns {Deferred} resolved with the local id of the created record
+ */
+ createRecordInGroup: function (groupID, values) {
+ var self = this;
+ var group = this.localData[groupID];
+ var context = this._getContext(group);
+ var parent = this.localData[group.parentID];
+ context['default_' + parent.groupedBy[0]] = group.res_id;
+ var def;
+ if (Object.keys(values).length === 1 && 'display_name' in values) {
+ // only 'display_name is given, perform a 'name_create'
+ def = this._rpc({
+ model: parent.model,
+ method: 'name_create',
+ args: [values.display_name],
+ context: context,
+ }).then(function (records) {
+ return records[0];
+ });
+ } else {
+ // other fields are specified, perform a classical 'create'
+ def = this._rpc({
+ model: parent.model,
+ method: 'create',
+ args: [values],
+ context: context,
+ });
+ }
+ return def.then(function (resID) {
+ return self.addRecordToGroup(group.id, resID);
+ });
+ },
+ /**
* Add the key `tooltipData` (kanban specific) when performing a `geĊ§`.
*
* @override
diff --git a/addons/web/static/src/js/views/kanban/kanban_quick_create.js b/addons/web/static/src/js/views/kanban/kanban_quick_create.js
index 9c317552329..66c6a2676f1 100644
--- a/addons/web/static/src/js/views/kanban/kanban_quick_create.js
+++ b/addons/web/static/src/js/views/kanban/kanban_quick_create.js
@@ -6,229 +6,263 @@ odoo.define('web.kanban_quick_create', function (require) {
* kanban records, and one to quick create kanban columns.
*/
+var core = require('web.core');
+var QuickCreateFormView = require('web.QuickCreateFormView');
var Widget = require('web.Widget');
-var AbstractQuickCreate = Widget.extend({
+var qweb = core.qweb;
+
+var RecordQuickCreate = Widget.extend({
+ className: 'o_kanban_quick_create',
+ custom_events: {
+ add: '_onAdd',
+ cancel: '_onCancel',
+ },
events: {
'click .o_kanban_add': '_onAddClicked',
+ 'click .o_kanban_edit': '_onEditClicked',
'click .o_kanban_cancel': '_onCancelClicked',
- 'keydown': '_onKeydown',
- 'keypress input': '_onKeypress',
- 'mousedown .o_kanban_add': '_onMousedown',
- 'mousedown .o_kanban_cancel': '_onMousedown',
- 'focusout': '_onFocusOut',
},
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
/**
- * Clears the input value and notify the environment that a quick create
- * has been done
- *
- * @private
- * @param {Object} [options] dict of options to pass to call to '_notifyAdd'
+ * @override
+ * @param {Widget} parent
+ * @param {Object} options
+ * @param {Object} options.context
+ * @param {string|null} options.formViewRef
+ * @param {string} options.model
*/
- _add: function (options) {
- var value = this.$input.val();
- this.$input.val('');
- if (/^\s*$/.test(value)) {
- return;
- }
- this._notifyAdd(value, options);
- this.$input.focus();
+ init: function (parent, options) {
+ this._super.apply(this, arguments);
+ this.context = options.context;
+ this.formViewRef = options.formViewRef;
+ this.model = options.model;
},
/**
- * Should implement what to do when the quick creation has been cancelled
+ * Loads the form fieldsView (if not provided), instantiates the form view
+ * and starts the form controller.
*
- * @abstract
- * @private
+ * @override
*/
- _cancel: function () {
+ willStart: function () {
+ var self = this;
+ var def1 = this._super.apply(this, arguments);
+ var def2;
+ if (this.formViewRef) {
+ var views = [[false, 'form']];
+ var context = _.extend({}, this.context, {
+ form_view_ref: this.formViewRef,
+ });
+ def2 = this.loadViews(this.model, context, views);
+ } else {
+ var fieldsView = {};
+ fieldsView.arch = '<form>' +
+ '<field name="display_name" placeholder="Title" modifiers=\'{"required": true}\'/>' +
+ '</form>';
+ var fields = {
+ display_name: {string: 'Display name', type: 'char'},
+ };
+ fieldsView.fields = fields;
+ fieldsView.viewFields = fields;
+ def2 = $.when({form: fieldsView});
+ }
+ def2 = def2.then(function (fieldsViews) {
+ var formView = new QuickCreateFormView(fieldsViews.form, {
+ context: self.context,
+ modelName: self.model,
+ userContext: self.getSession().user_context,
+ });
+ return formView.getController(self).then(function (controller) {
+ self.controller = controller;
+ return self.controller.appendTo(document.createDocumentFragment());
+ });
+ });
+ return $.when(def1, def2);
},
/**
- * Should implement what to do to notify the environment that a quick
- * create has been done
- *
- * @abstract
- * @private
+ * @override
*/
- _notifyAdd: function (name, options) {
- },
+ start: function () {
+ this.$el.append(this.controller.$el);
+ this.$el.append(qweb.render('KanbanView.RecordQuickCreate.buttons'));
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
+ // focus the first field
+ this.controller.autofocus();
- /**
- * @private
- * @param {MouseEvent} event
- */
- _onAddClicked: function (event) {
- event.stopPropagation();
- this._add();
- },
- /**
- * @private
- */
- _onCancelClicked: function () {
- this._cancel();
+ // destroy the quick create when the user clicks outside
+ core.bus.on('click', this, this._onWindowClicked);
+
+ return this._super.apply(this, arguments);
},
/**
- * Cancels quick creation on focusout input event
- *
- * @private
- * @param {KeyEvent} ev
+ * Called when the quick create is appended into the DOM.
*/
- _onFocusOut: function (ev) {
- if (!this.$input.val()) {
- this._cancel();
+ on_attach_callback: function () {
+ if (this.controller) {
+ this.controller.autofocus();
}
},
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
/**
- * Cancels quick creation on escape keydown event
+ * Cancels the quick creation if the record isn't dirty, i.e. if no changes
+ * have been made yet
*
* @private
- * @param {KeyEvent} event
+ * @returns {Deferred}
*/
- _onKeydown: function (event) {
- if (event.keyCode === 27) { // escape
- this._cancel();
- }
+ cancel: function () {
+ var self = this;
+ return this.controller.commitChanges().then(function () {
+ if (!self.controller.isDirty()) {
+ self._cancel();
+ }
+ });
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
/**
- * Validates quick creation on enter keypress event
- *
+ * @override
* @private
- * @param {KeyEvent} event
+ * @param {Object} [options]
+ * @param {boolean} [options.openRecord] set to true to directly open the
+ * newly created record in a form view (in edit mode)
+ * @returns {Deferred}
*/
- _onKeypress: function (event) {
- if (event.keyCode === 13) { // enter
- this._add();
- }
+ _add: function (options) {
+ var self = this;
+ return this.controller.commitChanges().then(function () {
+ var canBeSaved = self.controller.canBeSaved();
+ if (canBeSaved) {
+ self.trigger_up('quick_create_add_record', {
+ openRecord: options && options.openRecord || false,
+ values: self.controller.getChanges(),
+ });
+ }
+ });
},
/**
- * In all browsers the 'focus/blur' event is triggered before a button's
- * 'click' (it's actually triggered by mousedown).
- * The quick create is destroyed on blur of its input but still needs
- * to create the data when the "Add" button is clicked.
- * This problem is adressed by suppressing the 'focus/blur' event from the
- * relevant buttons (Add and Cancel) entirely by preventing the browser's
- * default mousedown handler.
+ * Notifies the environment that the quick creation must be cancelled
*
* @private
- * @param {MouseEvent} event
- */
- _onMousedown: function (event) {
- event.preventDefault();
- },
-});
-
-var RecordQuickCreate = AbstractQuickCreate.extend({
- template: "KanbanView.QuickCreate",
- events: _.extend({}, AbstractQuickCreate.prototype.events, {
- 'click .o_kanban_edit': '_onEditClicked',
- 'mousedown .o_kanban_edit': '_onMousedown',
- }),
- /**
- * @override
- * @param {Widget} parent
- * @param {Object} options
- * @param {string|number} options.width defines the element's width
- * @param {string} [options.defaultName] the record's default name
- */
- init: function (parent, options) {
- this._super.apply(this, arguments);
- this.width = options.width;
- this.defaultName = options.defaultName;
- },
- /**
- * @override
+ * @returns {Deferred}
*/
- start: function () {
- this.$el.css({width: this.width});
- this.$input = this.$('input');
- this._addDefaultName();
- this.$input.focus();
- return this._super.apply(this, arguments);
+ _cancel: function () {
+ this.trigger_up('cancel_quick_create');
},
//--------------------------------------------------------------------------
- // Private
+ // Handlers
//--------------------------------------------------------------------------
/**
- * Set the default value for the record name.
- *
* @private
+ * @param {OdooEvent} ev
*/
- _addDefaultName: function () {
- if (this.defaultName) {
- this.$input.val(this.defaultName);
- }
+ _onAdd: function (ev) {
+ ev.stopPropagation();
+ this._add();
},
/**
- * Triggers up an event to cancel the quick creation
- *
- * @override
* @private
+ * @param {MouseEvent} ev
*/
- _cancel: function () {
- this.trigger_up('cancel_quick_create');
+ _onAddClicked: function (ev) {
+ ev.stopPropagation();
+ this._add();
},
/**
- * Triggers up an event to quick create a record with the given value
- *
- * @override
* @private
- * @param {string} value
- * @param {Object} [options]
- * @param {boolean} [options.openRecord] set to true to directly open the
- * newly created record in a form view (in edit mode)
+ * @param {OdooEvent} ev
*/
- _notifyAdd: function (value, options) {
- this.trigger_up('quick_create_add_record', {
- value: value,
- openRecord: options && options.openRecord || false,
- });
+ _onCancel: function (ev) {
+ ev.stopPropagation();
+ this._cancel();
},
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
/**
- * Add the default value for the record name again after creating
- * the previous record.
- *
- * @override
* @private
+ * @param {MouseEvent} ev
*/
- _onAddClicked: function () {
- this._super.apply(this, arguments);
- this._addDefaultName();
+ _onCancelClicked: function (ev) {
+ ev.stopPropagation();
+ this._cancel();
},
/**
* Validates the quick creation and directly opens the record in a form
* view in edit mode.
*
* @private
- * @param {MouseEvent} event
+ * @param {MouseEvent} ev
*/
- _onEditClicked: function (event) {
- event.stopPropagation();
+ _onEditClicked: function (ev) {
+ ev.stopPropagation();
this._add({openRecord: true});
},
+ /**
+ * When a click happens outside the quick create, we want to close the quick
+ * create.
+ *
+ * This is quite tricky, because in some cases a click is performed outside
+ * the quick create, but is still related to it (e.g. click in a dialog
+ * opened from the quick create).
+ *
+ * @param {MouseEvent} ev
+ */
+ _onWindowClicked: function (ev) {
+ // ignore clicks if the quick create is not in the dom
+ if (!document.contains(this.el)) {
+ return;
+ }
+
+ // ignore clicks on elements that open the quick create widget, to
+ // prevent from closing quick create widget that has just been opened
+ if ($(ev.target).closest('.o-kanban-button-new, .o_kanban_quick_add').length) {
+ return;
+ }
+
+ // ignore clicks in autocomplete dropdowns
+ if ($(ev.target).parents('.ui-autocomplete').length) {
+ return;
+ }
+
+ // ignore clicks in modals
+ if ($(ev.target).closest('.modal').length) {
+ return;
+ }
+
+ // ignore clicks if target is no longer in dom (e.g., a click on the
+ // 'delete' trash icon of a m2m tag)
+ if (!document.contains(ev.target)) {
+ return;
+ }
+
+ // ignore clicks if target is inside the quick create
+ if (this.el.contains(ev.target) && this.el !== ev.target) {
+ return;
+ }
+
+ this.cancel();
+ },
});
-var ColumnQuickCreate = AbstractQuickCreate.extend({
+var ColumnQuickCreate = Widget.extend({
template: 'KanbanView.ColumnQuickCreate',
- events: _.extend({}, AbstractQuickCreate.prototype.events, {
+ events: {
'click': 'toggleFold',
'click input': '_onInputClicked',
+ 'click .o_kanban_add': '_onAddClicked',
'focusout': '_onFocusout',
- }),
+ 'keydown': '_onKeydown',
+ 'keypress input': '_onKeypress',
+ 'mousedown .o_kanban_add': '_onMousedown',
+ },
+
/**
* @override
*/
@@ -251,7 +285,7 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({
//--------------------------------------------------------------------------
/**
- * Toggle fold/unfold the Column quick create widget
+ * Folds/unfolds the Column quick create widget
*/
toggleFold: function () {
this.folded = !this.folded;
@@ -267,26 +301,31 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({
//--------------------------------------------------------------------------
/**
- * @override
+ * Clears the input value and notify the environment to create a column
+ *
* @private
*/
- _cancel: function () {
- this.folded = true;
+ _add: function () {
+ var value = this.$input.val().trim();
+ if (!value.length) {
+ return;
+ }
this.$input.val('');
- this._update();
+ this.trigger_up('quick_create_add_column', {value: value});
+ this.$input.focus();
},
/**
- * Triggers up an event to quick create a column with the given value
+ * Cancels the quick creation
*
- * @override
* @private
- * @param {string} value
*/
- _notifyAdd: function (value) {
- this.trigger_up('quick_create_add_column', {value: value});
+ _cancel: function () {
+ this.folded = true;
+ this.$input.val('');
+ this._update();
},
/**
- * Updates the rendering according to the current state
+ * Updates the rendering according to the current state (folded/unfolded)
*
* @private
*/
@@ -301,6 +340,16 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({
/**
* @private
+ * @param {MouseEvent} event
+ */
+ _onAddClicked: function (event) {
+ event.stopPropagation();
+ this._add();
+ },
+ /**
+ * Cancels the quick creation when the input loses the focus
+ *
+ * @private
*/
_onFocusout: function () {
var hasFocus = this.$(':focus').length > 0;
@@ -319,6 +368,42 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({
_onInputClicked: function (event) {
event.stopPropagation();
},
+ /**
+ * Cancels quick creation on escape keydown event
+ *
+ * @private
+ * @param {KeyEvent} event
+ */
+ _onKeydown: function (event) {
+ if (event.keyCode === 27) { // escape
+ this._cancel();
+ }
+ },
+ /**
+ * Validates quick creation on enter keypress event
+ *
+ * @private
+ * @param {KeyEvent} event
+ */
+ _onKeypress: function (event) {
+ if (event.keyCode === 13) { // enter
+ this._add();
+ }
+ },
+ /**
+ * In all browsers the 'focus/blur' event is triggered before a button's
+ * 'click' (it's actually triggered by mousedown).
+ * The quick create is hidden on blur of its input but still needs to create
+ * the data when the "Add" button is clicked.
+ * This problem is adressed by suppressing the 'focus/blur' event from the
+ * Add button entirely by preventing the browser's default mousedown handler.
+ *
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onMousedown: function (event) {
+ event.preventDefault();
+ },
});
return {
diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js
index 1dad8e9c35d..52f57287296 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js
@@ -84,7 +84,8 @@ function transformQwebTemplate(node, fields) {
var KanbanRenderer = BasicRenderer.extend({
className: 'o_kanban_view',
custom_events: _.extend({}, BasicRenderer.prototype.custom_events || {}, {
- 'set_progress_bar_state': '_onSetProgressBarState',
+ close_quick_create: '_onCloseQuickCreate',
+ set_progress_bar_state: '_onSetProgressBarState',
}),
/**
@@ -117,9 +118,11 @@ var KanbanRenderer = BasicRenderer.extend({
/**
* Displays the quick create record in the first column.
+ *
+ * @returns {Deferred}
*/
addQuickCreate: function () {
- this.widgets[0].addQuickCreate();
+ return this.widgets[0].addQuickCreate();
},
/**
* Toggle fold/unfold the Column quick create widget
@@ -152,11 +155,21 @@ var KanbanRenderer = BasicRenderer.extend({
var index = _.findIndex(this.widgets, {db_id: localID});
var column = this.widgets[index];
this.widgets[index] = newColumn;
- return newColumn.insertAfter(column.$el).then(function () {
+ return newColumn.appendTo(document.createDocumentFragment()).then(function () {
+ var def;
if (options && options.openQuickCreate) {
- newColumn.addQuickCreate();
+ def = newColumn.addQuickCreate();
}
- column.destroy();
+ return $.when(def).then(function () {
+ newColumn.$el.insertAfter(column.$el);
+ // When a record has been quick created, the new column directly
+ // renders the quick create widget (to allow quick creating several
+ // records in a row). However, as we render this column in a
+ // fragment, the quick create widget can't be correctly focused. So
+ // we manually call on_attach_callback to focus it once in the DOM.
+ newColumn.on_attach_callback();
+ column.destroy();
+ });
});
},
/**
@@ -334,8 +347,9 @@ var KanbanRenderer = BasicRenderer.extend({
_setState: function (state) {
this.state = state;
- var groupByFieldAttrs = state.fields[state.groupedBy[0]];
- var groupByFieldInfo = state.fieldsInfo.kanban[state.groupedBy[0]];
+ var groupByField = state.groupedBy[0];
+ var groupByFieldAttrs = state.fields[groupByField];
+ var groupByFieldInfo = state.fieldsInfo.kanban[groupByField];
// Deactivate the drag'n'drop if the groupedBy field:
// - is a date or datetime since we group by month or
// - is readonly (on the field attrs or in the view)
@@ -353,13 +367,14 @@ var KanbanRenderer = BasicRenderer.extend({
}
}
this.groupedByM2O = groupByFieldAttrs && (groupByFieldAttrs.type === 'many2one');
- var grouped_by_field = this.groupedByM2O && groupByFieldAttrs.relation;
+ var relation = this.groupedByM2O && groupByFieldAttrs.relation;
var groupByTooltip = groupByFieldInfo && groupByFieldInfo.options.group_by_tooltip;
this.columnOptions = _.extend(this.columnOptions, {
draggable: draggable,
group_by_tooltip: groupByTooltip,
+ groupedBy: groupByField,
grouped_by_m2o: this.groupedByM2O,
- relation: grouped_by_field,
+ relation: relation,
});
this.createColumnEnabled = this.groupedByM2O && this.columnOptions.group_creatable;
},
@@ -369,6 +384,16 @@ var KanbanRenderer = BasicRenderer.extend({
//--------------------------------------------------------------------------
/**
+ * Closes the opened quick create widgets in columns
+ *
+ * @private
+ */
+ _onCloseQuickCreate: function () {
+ if (this.state.groupedBy.length) {
+ _.invoke(this.widgets, 'cancelQuickCreate');
+ }
+ },
+ /**
* Updates progressbar internal states (necessary for animations) with
* received data.
*
diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
index 7614d6bca95..c609e45fc60 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
@@ -38,9 +38,11 @@ KanbanRenderer.include({
/**
* Displays the quick create record in the active column
+ *
+ * @returns {Deferred}
*/
addQuickCreate: function () {
- this.widgets[this.activeColumnIndex].addQuickCreate();
+ return this.widgets[this.activeColumnIndex].addQuickCreate();
},
/**
* Overrides to restore the left property and the scrollTop on the updated
diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js
index 87b4a366502..c5ebbf5243f 100644
--- a/addons/web/static/src/js/views/kanban/kanban_view.js
+++ b/addons/web/static/src/js/views/kanban/kanban_view.js
@@ -62,6 +62,7 @@ var KanbanView = BasicView.extend({
deletable: activeActions.group_delete,
group_creatable: activeActions.group_create && !config.device.isMobile,
quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(),
+ quickCreateView: this.arch.attrs.quick_create_view || null,
hasProgressBar: !!progressBar,
};
this.rendererParams.record_options = {
diff --git a/addons/web/static/src/js/views/kanban/quick_create_form_view.js b/addons/web/static/src/js/views/kanban/quick_create_form_view.js
new file mode 100644
index 00000000000..fe0684a9ada
--- /dev/null
+++ b/addons/web/static/src/js/views/kanban/quick_create_form_view.js
@@ -0,0 +1,110 @@
+odoo.define('web.QuickCreateFormView', function (require) {
+"use strict";
+
+/**
+ * This file defines the QuickCreateFormView, an extension of the FormView that
+ * is used by the RecordQuickCreate in Kanban views.
+ */
+
+var FormController = require('web.FormController');
+var BasicModel = require('web.BasicModel');
+var FormRenderer = require('web.FormRenderer');
+var FormView = require('web.FormView');
+
+var QuickCreateFormRenderer = FormRenderer.extend({
+ /**
+ * @override
+ */
+ start: function () {
+ this.$el.addClass('o_xxs_form_view');
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onNavigationMove: function (ev) {
+ var direction = ev.data.direction;
+ if (direction === 'cancel' || direction === 'next_line') {
+ ev.stopPropagation();
+ this.trigger_up(direction === 'cancel' ? 'cancel' : 'add');
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+});
+
+var QuickCreateFormModel = BasicModel.extend({
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @returns {Object} the changes of the given resource (server commands for
+ * x2manys)
+ */
+ getChanges: function (localID) {
+ var record = this.localData[localID];
+ return this._generateChanges(record, {changesOnly: false});
+ },
+});
+
+var QuickCreateFormController = FormController.extend({
+ custom_events: _.extend({}, FormController.prototype.custom_events, {
+ env_updated: '_onEnvUpdated',
+ }),
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Asks all field widgets to notify the environment with their current value
+ * (useful for instance for input fields that still have the focus and that
+ * could have not notified the environment of their changes yet).
+ *
+ * @return {Deferred}
+ */
+ commitChanges: function () {
+ return this.mutex
+ .exec(this.renderer.commitChanges.bind(this.renderer, this.handle));
+ },
+ /**
+ * @returns {Object} the changes done on the current record
+ */
+ getChanges: function () {
+ return this.model.getChanges(this.handle);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Stops the propagation of the 'env_updated' event to prevent interferences
+ * with the kanban controller.
+ *
+ * @private
+ */
+ _onEnvUpdated: function (ev) {
+ ev.stopPropagation();
+ },
+});
+
+var QuickCreateFormView = FormView.extend({
+ config: _.extend({}, FormView.prototype.config, {
+ Model: QuickCreateFormModel,
+ Renderer: QuickCreateFormRenderer,
+ Controller: QuickCreateFormController,
+ }),
+});
+
+return QuickCreateFormView;
+
+});
diff --git a/addons/web/static/src/less/form_view.less b/addons/web/static/src/less/form_view.less
index 8a053a8cca3..505f62ac42f 100644
--- a/addons/web/static/src/less/form_view.less
+++ b/addons/web/static/src/less/form_view.less
@@ -649,6 +649,57 @@ label.o_light_label, .o_light_label label {
font-weight: normal;
}
+// XXS form view specific rules
+.form-break-table() {
+ display: block;
+ margin-bottom: @odoo-form-spacing-unit * 4;
+
+ > tbody {
+ display: block;
+
+ > tr {
+ .o-flex-display();
+ .o-flex-flow(row, wrap);
+
+ > td {
+ .o-flex(1, 0, auto);
+ display: block;
+ max-width: 100%;
+ padding: 0;
+
+ width: auto!important; // !important is required to override the width computed in JS
+ &.o_td_label {
+ width: 94%!important; // The label must be on its own line except if the form field is small enough (checkbox)
+ line-height: @odoo-label-font-size-factor;
+ }
+
+ .o_field_widget {
+ margin-bottom: @odoo-form-spacing-unit * 2;
+ &.o_field_boolean {
+ margin-right: 0;
+ }
+ }
+
+ .o_input_dropdown {
+ width: auto;
+ max-width: 100%;
+ }
+ }
+ }
+ }
+}
+.o_form_view.o_xxs_form_view {
+ .oe_title {
+ word-break: break-all;
+ }
+ .o_group {
+ &.o_inner_group {
+ .form-break-table();
+ }
+ }
+}
+
+// Settings form views
.o_settings_container {
.o-flex-display();
.o-flex(0, 1, auto);
diff --git a/addons/web/static/src/less/form_view_extra.less b/addons/web/static/src/less/form_view_extra.less
index 2349ee5c0b0..95da6a2b35b 100644
--- a/addons/web/static/src/less/form_view_extra.less
+++ b/addons/web/static/src/less/form_view_extra.less
@@ -181,3 +181,10 @@
}
}
}
+
+// XXS form view specific rules
+.o_form_view.o_xxs_form_view {
+ .o_group .o_td_label {
+ border: none;
+ }
+}
diff --git a/addons/web/static/src/less/kanban_view.less b/addons/web/static/src/less/kanban_view.less
index 9158aa22df6..2b5482e36ff 100644
--- a/addons/web/static/src/less/kanban_view.less
+++ b/addons/web/static/src/less/kanban_view.less
@@ -161,12 +161,11 @@
box-shadow: 0 0 20px -10px;
margin: 0 @o-kanban-record-margin -1px @o-kanban-record-margin;
- input {
- width: 100%;
- margin: 2px 0 8px;
- padding: 3px 6px;
- color: @headings-color;
- font-weight: 500;
+ .o_form_view {
+ padding: 0;
+ .o_group.o_inner_group {
+ margin: 0;
+ }
}
}
diff --git a/addons/web/static/src/xml/kanban.xml b/addons/web/static/src/xml/kanban.xml
index dd8427a7643..97ac827df2a 100644
--- a/addons/web/static/src/xml/kanban.xml
+++ b/addons/web/static/src/xml/kanban.xml
@@ -54,9 +54,8 @@
</div>
</t>
-<t t-name="KanbanView.QuickCreate">
- <div class="o_kanban_quick_create">
- <input type="text" class="o_input" placeholder="Title"/>
+<t t-name="KanbanView.RecordQuickCreate.buttons">
+ <div>
<button class="btn btn-sm btn-primary o_kanban_add">Add</button>
<button class="btn btn-sm btn-primary o_kanban_edit">Edit</button>
<button class="btn btn-sm btn-default o_kanban_cancel ml8">Discard</button>