[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.
This commit is contained in:
Aaron Bohy
2018-01-26 19:26:03 +01:00
parent af2686fee5
commit d18d08f01a
17 changed files with 1219 additions and 344 deletions
@@ -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);
}
@@ -120,6 +120,15 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
getSelectedIds: function () {
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
*/
@@ -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
@@ -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
@@ -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
@@ -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).
@@ -93,6 +94,43 @@ var KanbanModel = BasicModel.extend({
return newGroup.id;
});
},
/**
* 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ŧ`.
*
@@ -6,17 +6,121 @@ 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',
},
/**
* @override
* @param {Widget} parent
* @param {Object} options
* @param {Object} options.context
* @param {string|null} options.formViewRef
* @param {string} options.model
*/
init: function (parent, options) {
this._super.apply(this, arguments);
this.context = options.context;
this.formViewRef = options.formViewRef;
this.model = options.model;
},
/**
* Loads the form fieldsView (if not provided), instantiates the form view
* and starts the form controller.
*
* @override
*/
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);
},
/**
* @override
*/
start: function () {
this.$el.append(this.controller.$el);
this.$el.append(qweb.render('KanbanView.RecordQuickCreate.buttons'));
// focus the first field
this.controller.autofocus();
// destroy the quick create when the user clicks outside
core.bus.on('click', this, this._onWindowClicked);
return this._super.apply(this, arguments);
},
/**
* Called when the quick create is appended into the DOM.
*/
on_attach_callback: function () {
if (this.controller) {
this.controller.autofocus();
}
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Cancels the quick creation if the record isn't dirty, i.e. if no changes
* have been made yet
*
* @private
* @returns {Deferred}
*/
cancel: function () {
var self = this;
return this.controller.commitChanges().then(function () {
if (!self.controller.isDirty()) {
self._cancel();
}
});
},
//--------------------------------------------------------------------------
@@ -24,37 +128,210 @@ var AbstractQuickCreate = Widget.extend({
//--------------------------------------------------------------------------
/**
* Clears the input value and notify the environment that a quick create
* has been done
*
* @override
* @private
* @param {Object} [options] dict of options to pass to call to '_notifyAdd'
* @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}
*/
_add: function (options) {
var value = this.$input.val();
this.$input.val('');
if (/^\s*$/.test(value)) {
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(),
});
}
});
},
/**
* Notifies the environment that the quick creation must be cancelled
*
* @private
* @returns {Deferred}
*/
_cancel: function () {
this.trigger_up('cancel_quick_create');
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {OdooEvent} ev
*/
_onAdd: function (ev) {
ev.stopPropagation();
this._add();
},
/**
* @private
* @param {MouseEvent} ev
*/
_onAddClicked: function (ev) {
ev.stopPropagation();
this._add();
},
/**
* @private
* @param {OdooEvent} ev
*/
_onCancel: function (ev) {
ev.stopPropagation();
this._cancel();
},
/**
* @private
* @param {MouseEvent} ev
*/
_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} ev
*/
_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;
}
this._notifyAdd(value, options);
// 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 = Widget.extend({
template: 'KanbanView.ColumnQuickCreate',
events: {
'click': 'toggleFold',
'click input': '_onInputClicked',
'click .o_kanban_add': '_onAddClicked',
'focusout': '_onFocusout',
'keydown': '_onKeydown',
'keypress input': '_onKeypress',
'mousedown .o_kanban_add': '_onMousedown',
},
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.folded = true;
},
/**
* @override
*/
start: function () {
this.$header = this.$('.o_column_header');
this.$quick_create = this.$('.o_kanban_quick_create');
this.$input = this.$('input');
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Folds/unfolds the Column quick create widget
*/
toggleFold: function () {
this.folded = !this.folded;
this._update();
if (!this.folded) {
this.$input.focus();
this.trigger_up('scrollTo', {selector: '.o_column_quick_create'});
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Clears the input value and notify the environment to create a column
*
* @private
*/
_add: function () {
var value = this.$input.val().trim();
if (!value.length) {
return;
}
this.$input.val('');
this.trigger_up('quick_create_add_column', {value: value});
this.$input.focus();
},
/**
* Should implement what to do when the quick creation has been cancelled
* Cancels the quick creation
*
* @abstract
* @private
*/
_cancel: function () {
this.folded = true;
this.$input.val('');
this._update();
},
/**
* Should implement what to do to notify the environment that a quick
* create has been done
* Updates the rendering according to the current state (folded/unfolded)
*
* @abstract
* @private
*/
_notifyAdd: function (name, options) {
_update: function () {
this.$header.toggle(this.folded);
this.$quick_create.toggle(!this.folded);
},
//--------------------------------------------------------------------------
@@ -70,21 +347,26 @@ var AbstractQuickCreate = Widget.extend({
this._add();
},
/**
* Cancels the quick creation when the input loses the focus
*
* @private
*/
_onCancelClicked: function () {
_onFocusout: function () {
var hasFocus = this.$(':focus').length > 0;
if (hasFocus) {
return;
}
this._cancel();
},
/**
* Cancels quick creation on focusout input event
* Stops the propagation of the event to prevent the quick create from
* toggling when the user clicks in the input
*
* @private
* @param {KeyEvent} ev
* @param {MouseEvent} event
*/
_onFocusOut: function (ev) {
if (!this.$input.val()) {
this._cancel();
}
_onInputClicked: function (event) {
event.stopPropagation();
},
/**
* Cancels quick creation on escape keydown event
@@ -111,11 +393,10 @@ var AbstractQuickCreate = Widget.extend({
/**
* 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.
* 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
* relevant buttons (Add and Cancel) entirely by preventing the browser's
* default mousedown handler.
* Add button entirely by preventing the browser's default mousedown handler.
*
* @private
* @param {MouseEvent} event
@@ -125,202 +406,6 @@ var AbstractQuickCreate = Widget.extend({
},
});
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
*/
start: function () {
this.$el.css({width: this.width});
this.$input = this.$('input');
this._addDefaultName();
this.$input.focus();
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Set the default value for the record name.
*
* @private
*/
_addDefaultName: function () {
if (this.defaultName) {
this.$input.val(this.defaultName);
}
},
/**
* Triggers up an event to cancel the quick creation
*
* @override
* @private
*/
_cancel: function () {
this.trigger_up('cancel_quick_create');
},
/**
* 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)
*/
_notifyAdd: function (value, options) {
this.trigger_up('quick_create_add_record', {
value: value,
openRecord: options && options.openRecord || false,
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Add the default value for the record name again after creating
* the previous record.
*
* @override
* @private
*/
_onAddClicked: function () {
this._super.apply(this, arguments);
this._addDefaultName();
},
/**
* Validates the quick creation and directly opens the record in a form
* view in edit mode.
*
* @private
* @param {MouseEvent} event
*/
_onEditClicked: function (event) {
event.stopPropagation();
this._add({openRecord: true});
},
});
var ColumnQuickCreate = AbstractQuickCreate.extend({
template: 'KanbanView.ColumnQuickCreate',
events: _.extend({}, AbstractQuickCreate.prototype.events, {
'click': 'toggleFold',
'click input': '_onInputClicked',
'focusout': '_onFocusout',
}),
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.folded = true;
},
/**
* @override
*/
start: function () {
this.$header = this.$('.o_column_header');
this.$quick_create = this.$('.o_kanban_quick_create');
this.$input = this.$('input');
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Toggle fold/unfold the Column quick create widget
*/
toggleFold: function () {
this.folded = !this.folded;
this._update();
if (!this.folded) {
this.$input.focus();
this.trigger_up('scrollTo', {selector: '.o_column_quick_create'});
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
* @private
*/
_cancel: function () {
this.folded = true;
this.$input.val('');
this._update();
},
/**
* Triggers up an event to quick create a column with the given value
*
* @override
* @private
* @param {string} value
*/
_notifyAdd: function (value) {
this.trigger_up('quick_create_add_column', {value: value});
},
/**
* Updates the rendering according to the current state
*
* @private
*/
_update: function () {
this.$header.toggle(this.folded);
this.$quick_create.toggle(!this.folded);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onFocusout: function () {
var hasFocus = this.$(':focus').length > 0;
if (hasFocus) {
return;
}
this._cancel();
},
/**
* Stops the propagation of the event to prevent the quick create from
* toggling when the user clicks in the input
*
* @private
* @param {MouseEvent} event
*/
_onInputClicked: function (event) {
event.stopPropagation();
},
});
return {
RecordQuickCreate: RecordQuickCreate,
ColumnQuickCreate: ColumnQuickCreate,
@@ -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;
},
@@ -368,6 +383,16 @@ var KanbanRenderer = BasicRenderer.extend({
// Handlers
//--------------------------------------------------------------------------
/**
* 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.
@@ -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
@@ -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 = {
@@ -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;
});
+51
View File
@@ -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);
@@ -181,3 +181,10 @@
}
}
}
// XXS form view specific rules
.o_form_view.o_xxs_form_view {
.o_group .o_td_label {
border: none;
}
}
+5 -6
View File
@@ -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;
}
}
}
+2 -3
View File
@@ -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>
@@ -485,6 +485,12 @@ var MockServer = Class.extend({
_.each(kwargs.views, function (view_descr) {
var viewID = view_descr[0] || false;
var viewType = view_descr[1];
if (!viewID) {
var contextKey = viewType + '_view_ref';
if (contextKey in kwargs.context) {
viewID = kwargs.context[contextKey];
}
}
var key = [model, viewID, viewType].join(',');
var arch = self.archs[key];
if (!arch) {
+565 -61
View File
@@ -329,7 +329,367 @@ QUnit.module('Views', {
kanban.destroy();
});
QUnit.test('quick create and change state in grouped mode', function (assert) {
QUnit.test('quick create record without quick_create_view', function (assert) {
assert.expect(16);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
groupBy: ['bar'],
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'name_create') {
assert.strictEqual(args.args[0], 'new partner',
"should send the correct value");
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1,
"first column should contain one record");
// click on 'Create' -> should open the quick create in the first column
kanban.$buttons.find('.o-kanban-button-new').click();
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1,
"should have a quick create element in the first column");
assert.strictEqual($quickCreate.find('.o_form_view.o_xxs_form_view').length, 1,
"should have rendered an XXS form view");
assert.strictEqual($quickCreate.find('input').length, 1,
"should have only one input");
assert.ok($quickCreate.find('input').hasClass('o_required_modifier'),
"the field should be required");
assert.strictEqual($quickCreate.find('input[placeholder=Title]').length, 1,
"input placeholder should be 'Title'");
// fill the quick create and validate
$quickCreate.find('input').val('new partner').trigger('input');
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2,
"first column should contain two records");
assert.verifySteps([
'read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'default_get', // quick create
'name_create', // should perform a name_create to create the record
'read', // read the created record
'default_get', // reopen the quick create automatically
]);
kanban.destroy();
});
QUnit.test('quick create record with quick_create_view', function (assert) {
assert.expect(17);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'<field name="state" widget="priority"/>' +
'</form>',
},
groupBy: ['bar'],
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'create') {
assert.deepEqual(args.args[0], {
foo: 'new partner',
int_field: 4,
state: 'def',
}, "should send the correct values");
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1,
"first column should contain one record");
// click on 'Create' -> should open the quick create in the first column
kanban.$buttons.find('.o-kanban-button-new').click();
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1,
"should have a quick create element in the first column");
assert.strictEqual($quickCreate.find('.o_form_view.o_xxs_form_view').length, 1,
"should have rendered an XXS form view");
assert.strictEqual($quickCreate.find('input').length, 2,
"should have two inputs");
assert.strictEqual($quickCreate.find('.o_field_widget').length, 3,
"should have rendered three widgets");
// fill the quick create and validate
$quickCreate.find('.o_field_widget[name=foo]').val('new partner').trigger('input');
$quickCreate.find('.o_field_widget[name=int_field]').val('4').trigger('input');
$quickCreate.find('.o_field_widget[name=state] .o_priority_star:first').click();
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2,
"first column should contain two records");
assert.verifySteps([
'read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'load_views', // form view in quick create
'default_get', // quick create
'create', // should perform a create to create the record
'read', // read the created record
'load_views', // form view in quick create (is actually in cache)
'default_get', // reopen the quick create automatically
]);
kanban.destroy();
});
QUnit.test('quick create record in grouped on m2o (no quick_create_view)', function (assert) {
assert.expect(12);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'name_create') {
assert.strictEqual(args.args[0], 'new partner',
"should send the correct value");
assert.deepEqual(args.kwargs.context, {
default_product_id: 3,
default_qux: 2.5,
}, "should send the correct context");
}
return this._super.apply(this, arguments);
},
viewOptions: {
context: {default_qux: 2.5},
},
});
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2,
"first column should contain two records");
// click on 'Create', fill the quick create and validate
kanban.$buttons.find('.o-kanban-button-new').click();
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
$quickCreate.find('input').val('new partner').trigger('input');
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 3,
"first column should contain three records");
assert.verifySteps([
'read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'default_get', // quick create
'name_create', // should perform a name_create to create the record
'read', // read the created record
'default_get', // reopen the quick create automatically
]);
kanban.destroy();
});
QUnit.test('quick create record in grouped on m2o (with quick_create_view)', function (assert) {
assert.expect(14);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'<field name="state" widget="priority"/>' +
'</form>',
},
groupBy: ['product_id'],
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'create') {
assert.deepEqual(args.args[0], {
foo: 'new partner',
int_field: 4,
state: 'def',
}, "should send the correct values");
assert.deepEqual(args.kwargs.context, {
default_product_id: 3,
default_qux: 2.5,
}, "should send the correct context");
}
return this._super.apply(this, arguments);
},
viewOptions: {
context: {default_qux: 2.5},
},
});
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 2,
"first column should contain two records");
// click on 'Create', fill the quick create and validate
kanban.$buttons.find('.o-kanban-button-new').click();
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
$quickCreate.find('.o_field_widget[name=foo]').val('new partner').trigger('input');
$quickCreate.find('.o_field_widget[name=int_field]').val('4').trigger('input');
$quickCreate.find('.o_field_widget[name=state] .o_priority_star:first').click();
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 3,
"first column should contain three records");
assert.verifySteps([
'read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'load_views', // form view in quick create
'default_get', // quick create
'create', // should perform a create to create the record
'read', // read the created record
'load_views', // form view in quick create (is actually in cache)
'default_get', // reopen the quick create automatically
]);
kanban.destroy();
});
QUnit.test('quick create record with default values and onchanges', function (assert) {
assert.expect(11);
this.data.partner.fields.int_field.default = 4;
this.data.partner.onchanges = {
foo: function (obj) {
if (obj.foo) {
obj.int_field = 8;
}
},
};
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo"/>' +
'<field name="int_field"/>' +
'</form>',
},
groupBy: ['bar'],
mockRPC: function (route, args) {
assert.step(args.method || route);
return this._super.apply(this, arguments);
},
});
// click on 'Create' -> should open the quick create in the first column
kanban.$buttons.find('.o-kanban-button-new').click();
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1,
"should have a quick create element in the first column");
assert.strictEqual($quickCreate.find('.o_field_widget[name=int_field]').val(), '4',
"default value should be set");
// fill the 'foo' field -> should trigger the onchange
$quickCreate.find('.o_field_widget[name=foo]').val('new partner').trigger('input');
assert.strictEqual($quickCreate.find('.o_field_widget[name=int_field]').val(), '8',
"onchange should have been triggered");
assert.verifySteps([
'read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'load_views', // form view in quick create
'default_get', // quick create
'onchange', // default_get's onchange
'onchange', // onchange due to 'foo' field change
]);
kanban.destroy();
});
QUnit.test('quick create record with quick_create_view: modifiers', function (assert) {
assert.expect(3);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban quick_create_view="some_view_ref">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="foo" required="1"/>' +
'<field name="int_field" attrs=\'{"invisible": [["foo", "=", false]]}\'/>' +
'</form>',
},
groupBy: ['bar'],
});
// create a new record
kanban.$('.o_kanban_group:first .o_kanban_quick_add').click();
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
assert.ok($quickCreate.find('.o_field_widget[name=foo]').hasClass('o_required_modifier'),
"foo field should be required");
assert.ok($quickCreate.find('.o_field_widget[name=int_field]').hasClass('o_invisible_modifier'),
"int_field should be invisible");
// fill 'foo' field
$quickCreate.find('.o_field_widget[name=foo]').val('new partner').trigger('input');
assert.notOk($quickCreate.find('.o_field_widget[name=int_field]').hasClass('o_invisible_modifier'),
"int_field should now be visible");
kanban.destroy();
});
QUnit.test('quick create record and change state in grouped mode', function (assert) {
assert.expect(1);
this.data.partner.fields.kanban_state = {
@@ -367,7 +727,7 @@ QUnit.module('Views', {
kanban.destroy();
});
QUnit.test('quick create in grouped mode', function (assert) {
QUnit.test('quick create record: cancel and validate without using the buttons', function (assert) {
assert.expect(8);
var nbRecords = 4;
@@ -375,7 +735,7 @@ QUnit.module('Views', {
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban class="o_kanban_test" on_create="quick_create">' +
arch: '<kanban on_create="quick_create">' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
@@ -389,34 +749,41 @@ QUnit.module('Views', {
}
});
// click to add an element and cancel the quick creation
// click to add an element and cancel the quick creation by pressing ESC
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
var $quickCreate = kanban.$('.o_kanban_quick_create');
assert.strictEqual($quickCreate.length, 1, "should have a quick create element");
$quickCreate.find('input').trigger($.Event('keydown', {keyCode: $.ui.keyCode.ESCAPE}));
$quickCreate.find('input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ESCAPE,
which: $.ui.keyCode.ESCAPE,
}));
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0,
"should have destroyed the quick create element");
//click to add and element and focus out the blank input, should cancel the quick creation
// click to add and element and click outside, should cancel the quick creation
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
$quickCreate = kanban.$('.o_kanban_quick_create');
$quickCreate.find('input').blur();
kanban.$('.o_kanban_group .o_kanban_record:first').click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0,
"Blur should have destroyed the quick create element");
"the quick create should be destroyed when the user clicks outside");
// click to really add an element
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
$quickCreate = kanban.$('.o_kanban_quick_create');
$quickCreate.find('input').val('new partner');
$quickCreate.find('input').blur();
$quickCreate.find('input').val('new partner').trigger('input');
// When focus out the input containing value, should not delete the Quick Creation
// clicking outside should no longer destroy the quick create as it is dirty
kanban.$('.o_kanban_group .o_kanban_record:first').click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1,
"Blur should not have destroyed the quick create element");
"the quick create should not have been destroyed");
// confirm by pressing ENTER
nbRecords = 5;
$quickCreate.find('button.o_kanban_add').click();
$quickCreate.find('input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ENTER,
which: $.ui.keyCode.ENTER,
}));
assert.strictEqual(this.data.partner.records.length, 5,
"should have created a partner");
@@ -470,7 +837,128 @@ QUnit.module('Views', {
kanban.destroy();
});
QUnit.test('quick create and edit in grouped mode', function (assert) {
QUnit.test('quick create record: cancel when not dirty', function (assert) {
assert.expect(9);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban>' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
groupBy: ['bar'],
});
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1,
"first column should contain one record");
// click to add an element
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1,
"should have open the quick create widget");
// click again to add an element -> should have kept the quick create open
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1,
"should have kept the quick create open");
// click outside: should remove the quick create
kanban.$('.o_kanban_group .o_kanban_record:first').click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0,
"the quick create should not have been destroyed");
// click to reopen the quick create
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1,
"should have open the quick create widget");
// press ESC: should remove the quick create
kanban.$('.o_kanban_quick_create input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ESCAPE,
which: $.ui.keyCode.ESCAPE,
}));
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0,
"quick create widget should have been removed");
// click to reopen the quick create
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1,
"should have open the quick create widget");
// click on 'Discard': should remove the quick create
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
kanban.$('.o_kanban_group .o_kanban_record:first').click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0,
"the quick create should be destroyed when the user clicks outside");
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1,
"first column should still contain one record");
kanban.destroy();
});
QUnit.test('quick create record: cancel when dirty', function (assert) {
assert.expect(7);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban>' +
'<field name="bar"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates></kanban>',
groupBy: ['bar'],
});
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1,
"first column should contain one record");
// click to add an element and edit it
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1,
"should have open the quick create widget");
var $quickCreate = kanban.$('.o_kanban_quick_create');
$quickCreate.find('input').val('some value').trigger('input');
// click outside: should not remove the quick create
kanban.$('.o_kanban_group .o_kanban_record:first').click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1,
"the quick create should not have been destroyed");
// press ESC: should remove the quick create
$quickCreate.find('input').trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ESCAPE,
which: $.ui.keyCode.ESCAPE,
}));
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0,
"quick create widget should have been removed");
// click to reopen quick create and edit it
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 1,
"should have open the quick create widget");
$quickCreate = kanban.$('.o_kanban_quick_create');
$quickCreate.find('input').val('some value').trigger('input');
// click on 'Discard': should remove the quick create
kanban.$('.o_kanban_quick_create .o_kanban_cancel').click();
assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0,
"the quick create should be destroyed when the user clicks outside");
assert.strictEqual(kanban.$('.o_kanban_group:first .o_kanban_record').length, 1,
"first column should still contain one record");
kanban.destroy();
});
QUnit.test('quick create record and edit in grouped mode', function (assert) {
assert.expect(6);
var newRecordID;
@@ -510,7 +998,7 @@ QUnit.module('Views', {
var $quickCreate = kanban.$('.o_kanban_quick_create');
kanban.$('.o_kanban_header .o_kanban_quick_add i').first().click();
$quickCreate = kanban.$('.o_kanban_quick_create');
$quickCreate.find('input').val('new partner');
$quickCreate.find('input').val('new partner').trigger('input');
$quickCreate.find('button.o_kanban_edit').click();
assert.strictEqual(this.data.partner.records.length, 5,
@@ -549,7 +1037,8 @@ QUnit.module('Views', {
kanban.$('.o_kanban_quick_create input')
.val('new partner 1')
.trigger($.Event('keypress', {
.trigger('input')
.trigger($.Event('keydown', {
which: $.ui.keyCode.ENTER,
keyCode: $.ui.keyCode.ENTER,
}));
@@ -562,7 +1051,8 @@ QUnit.module('Views', {
// create a second element in a row
kanban.$('.o_kanban_quick_create input')
.val('new partner 2')
.trigger($.Event('keypress', {
.trigger('input')
.trigger($.Event('keydown', {
which: $.ui.keyCode.ENTER,
keyCode: $.ui.keyCode.ENTER,
}));
@@ -621,7 +1111,8 @@ QUnit.module('Views', {
def = $.Deferred();
kanban.$('.o_kanban_quick_create input')
.val('new partner 1')
.trigger($.Event('keypress', {
.trigger('input')
.trigger($.Event('keydown', {
which: $.ui.keyCode.ENTER,
keyCode: $.ui.keyCode.ENTER,
}));
@@ -629,7 +1120,8 @@ QUnit.module('Views', {
def = null;
kanban.$('.o_kanban_quick_create input')
.val('new partner 2')
.trigger($.Event('keypress', {
.trigger('input')
.trigger($.Event('keydown', {
which: $.ui.keyCode.ENTER,
keyCode: $.ui.keyCode.ENTER,
}));
@@ -649,7 +1141,7 @@ QUnit.module('Views', {
kanban.destroy();
});
QUnit.test('quick create fail in grouped', function (assert) {
QUnit.test('quick create record fail in grouped', function (assert) {
assert.expect(7);
var kanban = createView({
@@ -690,7 +1182,11 @@ QUnit.module('Views', {
kanban.$('.o_kanban_quick_create input')
.val('test')
.trigger($.Event('keypress', {keyCode: $.ui.keyCode.ENTER}));
.trigger('input')
.trigger($.Event('keydown', {
keyCode: $.ui.keyCode.ENTER,
which: $.ui.keyCode.ENTER,
}));
assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1,
"a form view dialog should have been opened (in edit)");
@@ -1181,37 +1677,6 @@ QUnit.module('Views', {
kanban.destroy();
});
QUnit.test('quick create record & column in grouped on m2o', function (assert) {
assert.expect(2);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
});
kanban.$('.o_kanban_group:first .o_kanban_quick_add').click();
var $quickCreate = kanban.$('.o_kanban_quick_create');
$quickCreate.find('input').val('new partner');
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(this.data.partner.records.length, 5,
"should have created a partner");
kanban.$('.o_column_quick_create').click();
kanban.$('.o_column_quick_create input').val('new column');
kanban.$('.o_column_quick_create button.o_kanban_add').click();
assert.strictEqual(kanban.$('.o_kanban_group:last span:contains(new column)').length, 1,
"the last column should be the newly created one");
kanban.destroy();
});
QUnit.test('delete a column in grouped on m2o', function (assert) {
assert.expect(28);
@@ -2172,7 +2637,7 @@ QUnit.module('Views', {
kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_add i').click();
var $quickCreate = kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_create');
$quickCreate.find('input').val('new partner');
$quickCreate.find('input').val('new partner').trigger('input');
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 0,
@@ -2232,12 +2697,12 @@ QUnit.module('Views', {
kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i').click();
var $quickCreate = kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_create');
$quickCreate.find('input').val('record1');
$quickCreate.find('input').val('record1').trigger('input');
$quickCreate.find('button.o_kanban_add').click();
kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i').click();
$quickCreate = kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_create');
$quickCreate.find('input').val('record2');
$quickCreate.find('input').val('record2').trigger('input');
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2,
@@ -2289,7 +2754,7 @@ QUnit.module('Views', {
// add a new column
kanban.$('.o_kanban_group:first .o_kanban_quick_add').click();
var $quickCreate = kanban.$('.o_kanban_quick_create');
$quickCreate.find('input').val('new partner');
$quickCreate.find('input').val('new partner').trigger('input');
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(this.data.partner.records.length, 5,
"should have created a 'new partner' column");
@@ -2414,11 +2879,11 @@ QUnit.module('Views', {
groupBy: ['bar'],
});
var initialCount = parseInt(kanban.$('.o_kanban_counter_side').eq(1).text());
kanban.$('.o_kanban_quick_add').eq(1).click();
kanban.$('.o_input').val('Test');
var initialCount = parseInt(kanban.$('.o_kanban_counter_side:first').text());
kanban.$('.o_kanban_quick_add:first').click();
kanban.$('.o_kanban_quick_create input').val('Test').trigger('input');
kanban.$('.o_kanban_add').click();
var lastCount = parseInt(kanban.$('.o_kanban_counter_side').eq(1).text());
var lastCount = parseInt(kanban.$('.o_kanban_counter_side:first').text());
assert.strictEqual(lastCount, initialCount + 1,
"kanban counters should have updated on quick create");
@@ -2455,6 +2920,45 @@ QUnit.module('Views', {
kanban.destroy();
});
QUnit.test('column progressbars on quick create with quick_create_view are updated', function (assert) {
assert.expect(1);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create" quick_create_view="some_view_ref">' +
'<field name="int_field"/>' +
'<progressbar field="foo" colors=\'{"yop": "success", "gnap": "warning", "blip": "danger"}\' sum_field="int_field"/>' +
'<templates><t t-name="kanban-box">' +
'<div>' +
'<field name="name"/>' +
'</div>' +
'</t></templates>' +
'</kanban>',
archs: {
'partner,some_view_ref,form': '<form>' +
'<field name="int_field"/>' +
'</form>',
},
groupBy: ['bar'],
});
var initialCount = parseInt(kanban.$('.o_kanban_counter_side:first').text());
// click on 'Create', fill the quick create and validate
kanban.$buttons.find('.o-kanban-button-new').click();
var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create');
$quickCreate.find('.o_field_widget[name=int_field]').val('44').trigger('input');
$quickCreate.find('button.o_kanban_add').click();
var lastCount = parseInt(kanban.$('.o_kanban_counter_side:first').text());
assert.strictEqual(lastCount, initialCount + 44,
"kanban counters should have been updated on quick create");
kanban.destroy();
});
QUnit.test('keep adding quickcreate in first column after a record from this column was moved', function (assert) {
assert.expect(2);
+1
View File
@@ -221,6 +221,7 @@
<script type="text/javascript" src="/web/static/src/js/views/kanban/kanban_renderer.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/kanban/kanban_renderer_mobile.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/kanban/kanban_view.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/kanban/quick_create_form_view.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/list/list_editable_renderer.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/list/list_renderer.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/list/list_view.js"></script>