diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-09-23 17:40:06 +0000 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-09-24 09:44:56 +0000 |
| commit | ac4fc6e7c7d71c3f8e2440a8ed8095c5aab15dd8 (patch) | |
| tree | e143d6d261f22cb06f2c0d5b344a4353e5b3be1b /addons/web/static/src | |
| parent | a6a3df3d0eda0fc2476c59de61d59534815f7c20 (diff) | |
| parent | bd3e510edbcd62f508dae28f573ee4d1a51bcf87 (diff) | |
[MERGE] forward port branch saas-12.3 up to bd3e510edbc
closes odoo/odoo#37314
Signed-off-by: Christophe Simonis <chs@odoo.com>
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/css/reset.min.css | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/dom.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/abstract_field.js | 6 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/basic_fields.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/field_utils.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/relational_fields.js | 92 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list/list_controller.js | 160 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list/list_editable_renderer.js | 72 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list/list_model.js | 10 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list/list_renderer.js | 21 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/view_dialogs.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/date_picker.js | 9 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 4 |
13 files changed, 333 insertions, 61 deletions
diff --git a/addons/web/static/src/css/reset.min.css b/addons/web/static/src/css/reset.min.css index 97c0b78b002..b6163764a5c 100644 --- a/addons/web/static/src/css/reset.min.css +++ b/addons/web/static/src/css/reset.min.css @@ -55,10 +55,6 @@ ul ul { list-style: circle; } -blockquote, q { - quotes: none; -} - blockquote:before, blockquote:after, q:before, q:after { content: ''; diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js index 7be17700f98..e369cea9c8c 100644 --- a/addons/web/static/src/js/core/dom.js +++ b/addons/web/static/src/js/core/dom.js @@ -540,7 +540,7 @@ var dom = { menuItemsWidth += computeFloatOuterWidthWithMargins($extraItemsToggle[0]); do { menuItemsWidth -= computeFloatOuterWidthWithMargins($items.eq(--nbItems)[0]); - } while (!(maxWidth - menuItemsWidth >= -0.001)); + } while (!(maxWidth - menuItemsWidth >= -0.001) && (nbItems > 0)); var $extraItems = $items.slice(nbItems).detach(); $extraItems.removeClass('nav-item'); diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index dc456d21ae0..317ea30e0ad 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -533,6 +533,12 @@ var AbstractField = Widget.extend({ } break; case $.ui.keyCode.ENTER: + // We preventDefault the ENTER key because of two coexisting behaviours: + // - In HTML5, pressing ENTER on a <button> triggers two events: a 'keydown' AND a 'click' + // - When creating and opening a dialog, the focus is automatically given to the primary button + // The end result caused some issues where a modal opened by an ENTER keypress (e.g. saving + // changes in multiple edition) confirmed the modal without any intentionnal user input. + ev.preventDefault(); ev.stopPropagation(); this.trigger_up('navigation_move', {direction: 'next_line'}); break; diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index eeda3d220b6..93a636c141a 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -492,6 +492,9 @@ var FieldDate = InputField.extend({ className: "o_field_date", tagName: "span", supportedFieldTypes: ['date', 'datetime'], + // we don't need to listen on 'input' nor 'change' events because the + // datepicker widget is already listening, and will correctly notify changes + events: AbstractField.prototype.events, /** * @override @@ -888,6 +891,8 @@ var FieldBoolean = AbstractField.extend({ _onKeydown: function (ev) { switch (ev.which) { case $.ui.keyCode.ENTER: + // prevent subsequent 'click' event (see _onKeydown of AbstractField) + ev.preventDefault(); this.$input.prop('checked', !this.value); this._setValue(!this.value); return; diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index ec99f4fa0f6..fd73b7a9ed2 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -415,7 +415,7 @@ function parseDate(value, field, options) { if (options && options.isUTC) { date = moment.utc(value); } else { - date = moment.utc(value, [datePattern, datePatternWoZero, moment.ISO_8601], true); + date = moment.utc(value, [datePattern, datePatternWoZero, moment.ISO_8601]); } if (date.isValid()) { if (date.year() === 0) { @@ -459,7 +459,7 @@ function parseDateTime(value, field, options) { // phatomjs crash if we don't use this format datetime = moment.utc(value.replace(' ', 'T') + 'Z'); } else { - datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601], true); + datetime = moment.utc(value, [pattern1, pattern2, moment.ISO_8601]); if (options && options.timezone) { datetime.add(-session.getTZOffset(datetime), 'minutes'); } diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index be35395aab7..6213fc8de6a 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -395,8 +395,10 @@ var FieldMany2One = AbstractField.extend({ no_create: !self.can_create, on_selected: function (records) { self.reinitialize(records[0]); + }, + on_closed: function () { self.activate(); - } + }, }; }, /** @@ -805,6 +807,48 @@ var Many2oneBarcode = FieldMany2One.extend({ }); var ListFieldMany2One = FieldMany2One.extend({ + events: _.extend({}, FieldMany2One.prototype.events, { + 'focusin input': '_onInputFocusin', + }), + + /** + * Should never be allowed to be opened while in readonly mode in a list + * + * @override + */ + init: function () { + this._super.apply(this, arguments); + // when we empty the input, we delay the setValue to prevent from + // triggering the 'fieldChanged' event twice when the user wants set + // another m2o value ; the following attribute is used to determine when + // we skipped the setValue, s.t. we can perform it later on if the user + // didn't select another value + this.mustSetValue = false; + this.m2oDialogFocused = false; + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * If in readonly, will never be considered as an active widget. + * + * @override + */ + activate: function () { + if (this.mode === 'readonly') { + return false; + } + return this._super.apply(this, arguments); + }, + /** + * @override + */ + reinitialize: function () { + this.mustSetValue = false; + return this._super.apply(this, arguments); + }, //-------------------------------------------------------------------------- // Private @@ -816,6 +860,52 @@ var ListFieldMany2One = FieldMany2One.extend({ _renderReadonly: function () { this.$el.text(this.m2o_value); }, + /** + * @override + * @private + */ + _searchCreatePopup: function () { + this.m2oDialogFocused = true; + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onInputFocusin: function () { + this.m2oDialogFocused = false; + }, + /** + * In list views, we don't want to try to trigger a fieldChange when the field + * is being emptied. Instead, it will be triggered as the user leaves the field + * while it is empty. + * + * @override + * @private + */ + _onInputFocusout: function () { + this._super.apply(this, arguments); + if (!this.m2oDialogFocused && this.$input.val() === "" && this.mustSetValue) { + this.reinitialize(false); + } + }, + /** + * Prevents the triggering of an immediate _onFieldChanged when emptying the field. + * + * @override + * @private + */ + _onInputKeyup: function () { + if (this.$input.val() !== "") { + this._super.apply(this, arguments); + } else { + this.mustSetValue = true; + } + }, }); var KanbanFieldMany2One = AbstractField.extend({ diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js index b864063ee92..e0598fcf9cc 100644 --- a/addons/web/static/src/js/views/list/list_controller.js +++ b/addons/web/static/src/js/views/list/list_controller.js @@ -50,6 +50,21 @@ var ListController = BasicController.extend({ this.editable = params.editable; this.noLeaf = params.noLeaf; this.selectedRecords = params.selectedRecords || []; + this.multipleRecordsSavingPromise = null; + this.fieldChangedPrevented = false; + this._onMouseupWindowDiscard = null; + }, + + /** + * Overriden to properly unbind any mouseup listeners still bound to the window + * + * @override + */ + destroy: function () { + if (this._onMouseupWindowDiscard) { + window.removeEventListener('mouseup', this._onMouseupWindowDiscard, true); + } + return this._super.apply(this, arguments); }, //-------------------------------------------------------------------------- @@ -134,6 +149,7 @@ var ListController = BasicController.extend({ }, trigger: 'manual', }); + this.$buttons.on('mousedown', '.o_list_button_discard', this._onDiscardMousedown.bind(this)); this.$buttons.on('click', '.o_list_button_discard', this._onDiscard.bind(this)); this.$buttons.appendTo($node); } @@ -342,15 +358,6 @@ var ListController = BasicController.extend({ return _.extend(env, {domain: record.getDomain()}); }, /** - * @private - * @returns {boolean} - */ - _inMultipleRecordEdition: function (recordId) { - var record = this.model.get(recordId, { raw: true }); - var recordIds = _.union([recordId], this.selectedRecords); - return recordIds.length > 1 && record.res_id; - }, - /** * Only display the pager when there are data to display. * * @override @@ -361,13 +368,20 @@ var ListController = BasicController.extend({ return !!state.count; }, /** + * Saves multiple records at once. This method is called by the _onFieldChanged method + * since the record must be confirmed as soon as the focus leaves a dirty cell. + * Pseudo-validation is performed with registered modifiers. + * Returns a promise that is resolved when confirming and rejected in any other case. + * * @private * @param {string} recordId * @param {Object} node * @param {Object} changes + * @returns {Promise} */ _saveMultipleRecords: function (recordId, node, changes) { var self = this; + var fieldName = Object.keys(changes)[0]; var value = Object.values(changes)[0]; var recordIds = _.union([recordId], this.selectedRecords); var validRecordIds = recordIds.reduce(function (result, recordId) { @@ -378,22 +392,52 @@ var ListController = BasicController.extend({ } return result; }, []); - var message = _.str.sprintf( - _t('Do you want to set the value on the %d valid selected records?'), - validRecordIds.length); - if (recordIds.length !== validRecordIds.length) { - var nbInvalid = recordIds.length - validRecordIds.length; - message += ' ' + _.str.sprintf(_t('(%d invalid)'), nbInvalid); - } - Dialog.confirm(this, message, { - confirm_callback: function () { - self.model.saveRecords(recordId, validRecordIds) - .then(function () { - self._updateButtons('readonly'); - var state = self.model.get(self.handle); - self.renderer.updateState(state, {}); - }); - }, + var nbInvalid = recordIds.length - validRecordIds.length; + + return new Promise(function (resolve, reject) { + var dialog; + var rejectAndDiscard = function () { + self.model.discardChanges(recordId); + return self._confirmSave(recordId).then(reject); + }; + if (validRecordIds.length > 0) { + var message; + if (nbInvalid === 0) { + message = _.str.sprintf( + _t("Do you want to set the value on the %d selected records?"), + validRecordIds.length); + } else { + message = _.str.sprintf( + _t("Do you want to set the value on the %d valid selected records? (%d invalid)"), + validRecordIds.length, nbInvalid); + } + dialog = Dialog.confirm(self, message, { + confirm_callback: function () { + return self.model.saveRecords(recordId, validRecordIds, fieldName) + .then(function () { + self._updateButtons('readonly'); + var state = self.model.get(self.handle); + return self.renderer.updateState(state, {}).then(function () { + resolve(Object.keys(changes)); + }); + }) + .guardedCatch(rejectAndDiscard); + }, + cancel_callback: rejectAndDiscard, + }); + } else { + dialog = Dialog.alert(self, _t("No valid record to save"), { + confirm_callback: rejectAndDiscard, + }); + } + dialog.on('closed', self, function () { + // we need to wait for the dialog to be actually closed, but + // the 'closed' event is triggered just before, and it prevents + // from focussing the cell + Promise.resolve().then(function () { + self.renderer.focusCell(recordId, node); + }); + }); }); }, /** @@ -404,10 +448,11 @@ var ListController = BasicController.extend({ */ _saveRecord: function (recordId) { var record = this.model.get(recordId, { raw: true }); - if (record.isDirty() && this._inMultipleRecordEdition(recordId)) { + if (record.isDirty() && this.renderer.inMultipleRecordEdition(recordId)) { // do not save the record (see _saveMultipleRecords) - return Promise.resolve(); - + var prom = this.multipleRecordsSavingPromise || Promise.reject(); + this.multipleRecordsSavingPromise = null; + return prom; } return this._super.apply(this, arguments); }, @@ -563,6 +608,31 @@ var ListController = BasicController.extend({ this._discardChanges(); }, /** + * Used to detect if the discard button is about to be clicked. + * Some focusout events might occur and trigger a save which + * is not always wanted when clicking "Discard". + * + * @param {MouseEvent} ev + * @private + */ + _onDiscardMousedown: function (ev) { + var self = this; + this.fieldChangedPrevented = true; + this._onMouseupWindowDiscard = (function (mouseupEvent) { + window.removeEventListener('mouseup', self._onMouseupWindowDiscard, true); + self._onMouseupWindowDiscard = null; + var preventedEvent = self.fieldChangedPrevented; + self.fieldChangedPrevented = false; + // If the user starts clicking (mousedown) on the button and stops clicking + // (mouseup) outside of the button, we want to trigger the original onFieldChanged + // Event that was prevented in the meantime. + if (ev.target !== mouseupEvent.target && preventedEvent.constructor.name === 'OdooEvent') { + self._onFieldChanged(preventedEvent); + } + }).bind(this); + window.addEventListener('mouseup', this._onMouseupWindowDiscard, true); + }, + /** * Called when the user asks to edit a row -> Updates the controller buttons * * @param {OdooEvent} ev @@ -618,15 +688,18 @@ var ListController = BasicController.extend({ _onFieldChanged: function (ev) { ev.stopPropagation(); var self = this; + var recordId = ev.data.dataPointID; - if (this._inMultipleRecordEdition(ev.data.dataPointID)) { + if (this.fieldChangedPrevented) { + this.fieldChangedPrevented = ev; + } else if (this.renderer.inMultipleRecordEdition(recordId)) { // deal with edition of multiple lines - var _onSuccess = ev.data.onSuccess; ev.data.onSuccess = function () { - Promise.resolve(_onSuccess()).then(function () { + self.multipleRecordsSavingPromise = self._saveMultipleRecords(ev.data.dataPointID, ev.target.__node, ev.data.changes); - }); }; + // disable onchanges as we'll save directly + ev.data.notifyChange = false; } this._super.apply(this, arguments); }, @@ -653,6 +726,29 @@ var ListController = BasicController.extend({ this._toggleSidebar(); }, /** + * If the record is set as dirty while in multiple record edition, + * we want to immediatly discard the change. + * + * @private + * @override + * @param {OdooEvent} ev + */ + _onSetDirty: function (ev) { + var self = this; + var recordId = ev.data.dataPointID; + if (this.renderer.inMultipleRecordEdition(recordId)) { + ev.stopPropagation(); + Dialog.alert(this, _t("No valid record to save"), { + confirm_callback: function () { + self.model.discardChanges(recordId); + self._confirmSave(recordId); + }, + }); + } else { + this._super.apply(this, arguments); + } + }, + /** * When the user clicks on one of the sortable column headers, we need to * tell the model to sort itself properly, to update the pager and to * rerender the view. diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index f87268ddb1e..4d5fd24124e 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -277,6 +277,17 @@ ListRenderer.include({ this._selectCell(rowIndex, 0); }, /** + * Gives focus to a specific cell, given its row and its related column. + * + * @param {string} recordId + * @param {Object} column + */ + focusCell: function (recordId, column) { + var $row = this._getRow(recordId); + var cellIndex = this.columns.indexOf(column); + $row.find('.o_data_cell')[cellIndex].focus(); + }, + /** * Returns the recordID associated to the line which is currently in edition * or null if there is no line in edition. * @@ -289,6 +300,16 @@ ListRenderer.include({ return null; }, /** + * Returns whether the list is in multiple record edition from a given record. + * + * @private + * @param {string} recordId + * @returns {boolean} + */ + inMultipleRecordEdition: function (recordId) { + return this.editable && this.selection.length > 1 && this.selection.indexOf(recordId) > -1; + }, + /** * Removes the line associated to the given recordID (the index of the row * is found thanks to the old state), then updates the state. * @@ -303,6 +324,7 @@ ListRenderer.include({ } if ($row.prop('rowIndex') - 1 === this.currentRow) { this.currentRow = null; + this._enableRecordSelectors(); } // destroy widgets first @@ -397,7 +419,11 @@ ListRenderer.include({ // Toggle selected class here so that style is applied at the end $row.toggleClass('o_selected_row', editMode); - $row.find('.o_list_record_selector input').prop('disabled', !record.res_id); + if (editMode) { + this._disableRecordSelectors(); + } else { + this._enableRecordSelectors(); + } return Promise.all(defs).then(function () { // necessary to trigger resize on fieldtexts @@ -427,17 +453,21 @@ ListRenderer.include({ var recordWidgets = this.allFieldWidgets[recordID]; toggleWidgets(true); - var prom = new Promise(function (resolve, reject) { + return new Promise(function (resolve, reject) { self.trigger_up('save_line', { recordID: recordID, onSuccess: resolve, onFailure: reject, }); - }); - prom.guardedCatch(function() { + }).then(function (changedFields) { + self._enableRecordSelectors(); + // If any field has changed and if the list is in multiple edition, + // we send a truthy boolean to _selectRow to tell it not to select + // the following record. + return changedFields && changedFields.length && self.inMultipleRecordEdition(recordID); + }).guardedCatch(function () { toggleWidgets(false); }); - return prom; function toggleWidgets(disabled) { _.each(recordWidgets, function (widget) { @@ -464,6 +494,14 @@ ListRenderer.include({ //-------------------------------------------------------------------------- /** + * When editing a row, we want to disable all record selectors. + * + * @private + */ + _disableRecordSelectors: function () { + this.$('.o_list_record_selector input').attr('disabled', 'disabled'); + }, + /** * Destroy all field widgets corresponding to a record. Useful when we are * removing a useless row. * @@ -477,6 +515,12 @@ ListRenderer.include({ } }, /** + * @private + */ + _enableRecordSelectors: function () { + this.$('.o_list_record_selector input').attr('disabled', false); + }, + /** * Returns the relative width according to the widget or the field type. * @see _renderHeader * @@ -992,7 +1036,10 @@ ListRenderer.include({ var recordId = this._getRecordID(rowIndex); // To select a row, the currently selected one must be unselected first var self = this; - return this.unselectRow().then(function () { + return this.unselectRow().then(function (noSelectNext) { + if (noSelectNext) { + return Promise.resolve(); + } if (!recordId) { // The row to selected doesn't exist anymore (probably because // an onchange triggered when unselecting the previous one @@ -1003,7 +1050,10 @@ ListRenderer.include({ return new Promise(function (resolve) { self.trigger_up('edit_line', { recordId: recordId, - onSuccess: resolve, + onSuccess: function () { + self._disableRecordSelectors(); + resolve(); + }, }); }); }); @@ -1035,7 +1085,10 @@ ListRenderer.include({ ev.stopPropagation(); var self = this; - var groupId = $(ev.target).data('group-id'); + // This method can be called when selecting the parent of the link. + // We need to ensure that the link is the actual target + var target = ev.target.tagName !== 'A' ? ev.target.getElementsByTagName('A')[0] : ev.target; + var groupId = target.dataset.groupId; this.currentGroupId = groupId; this.unselectRow().then(function () { self.trigger_up('add_record', { @@ -1213,12 +1266,13 @@ ListRenderer.include({ this.trigger_up('discard_changes', { recordID: ev.target.dataPointID, onSuccess: function () { + self._enableRecordSelectors(); var recordId = self._getRecordID(rowIndex); if (recordId) { var correspondingRow = self._getRow(recordId); correspondingRow.children().eq(cellIndex).focus(); } else if (self.currentGroupId) { - self.$('a[data-group-id=' + self.currentGroupId + ']').focus(); + self.$('a[data-group-id="' + self.currentGroupId + '"]').focus(); } else { self.$('.o_field_x2many_list_row_add a:first').focus(); // FIXME } diff --git a/addons/web/static/src/js/views/list/list_model.js b/addons/web/static/src/js/views/list/list_model.js index 25fdf28954c..7a2e0c51ada 100644 --- a/addons/web/static/src/js/views/list/list_model.js +++ b/addons/web/static/src/js/views/list/list_model.js @@ -40,12 +40,18 @@ odoo.define('web.ListModel', function (require) { * @param {string} referenceRecordId the record datapoint used to * generate the changes to apply to recordIds * @param {string[]} recordIds a list of record datapoint ids + * @param {string} fieldName the field to write */ - saveRecords: function (referenceRecordId, recordIds) { + saveRecords: function (referenceRecordId, recordIds, fieldName) { var self = this; var referenceRecord = this.localData[referenceRecordId]; var list = this.localData[referenceRecord.parentID]; - var changes = this._generateChanges(referenceRecord, {}); + // generate all record values to ensure that we'll write something + // (e.g. 2 records selected, edit a many2one in the first one, but + // reset same value, we still want to save this value on the other + // record) + var allChanges = this._generateChanges(referenceRecord, {changesOnly: false}); + var changes = _.pick(allChanges, fieldName); var records = recordIds.map(function (recordId) { return self.localData[recordId]; }); diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index afa8f09a857..8aaf09fc4c7 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -417,6 +417,7 @@ var ListRenderer = BasicRenderer.extend({ data: record.data, escape: true, isPassword: 'password' in node.attrs, + digits: node.attrs.digits ? JSON.parse(node.attrs.digits) : undefined, }); this._handleAttributes($td, node); var title = field.type !== 'boolean' ? formattedValue : ''; @@ -979,11 +980,18 @@ var ListRenderer = BasicRenderer.extend({ * @private */ _updateSelection: function () { - var $selectedRows = this.$('tbody .o_list_record_selector input:checked') - .closest('tr'); - this.selection = _.map($selectedRows, function (row) { - return $(row).data('id'); + this.selection = []; + var self = this; + var $inputs = this.$('tbody .o_list_record_selector input:visible:not(:disabled)'); + var allChecked = $inputs.length > 0; + $inputs.each(function (index, input) { + if (input.checked) { + self.selection.push($(input).closest('tr').data('id')); + } else { + allChecked = false; + } }); + this.$('thead .o_list_record_selector input').prop('checked', allChecked); this.trigger_up('selection_changed', { selection: this.selection }); this._updateFooter(); }, @@ -1156,9 +1164,6 @@ var ListRenderer = BasicRenderer.extend({ _onSelectRecord: function (ev) { ev.stopPropagation(); this._updateSelection(); - if (!$(ev.currentTarget).find('input').prop('checked')) { - this.$('thead .o_list_record_selector input').prop('checked', false); - } }, /** * @private @@ -1175,11 +1180,13 @@ var ListRenderer = BasicRenderer.extend({ _onToggleGroup: function (ev) { ev.preventDefault(); ev.stopPropagation(); + var self = this; var group = $(ev.currentTarget).closest('tr').data('group'); if (group.count) { this.trigger_up('toggle_group', { group: group, onSuccess: function() { + self._updateSelection(); // Refocus the header after re-render unless the user // already focused something else by now if (document.activeElement.tagName === 'BODY') { diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index f665bf49791..abfad01558e 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -329,6 +329,7 @@ var SelectCreateDialog = ViewDialog.extend({ this._super.apply(this, arguments); _.defaults(this.options, { initial_view: 'search' }); this.on_selected = this.options.on_selected || (function () {}); + this.on_closed = this.options.on_closed || (function () {}); this.initialIDs = this.options.initial_ids; }, @@ -412,6 +413,10 @@ var SelectCreateDialog = ViewDialog.extend({ return fragment; }); }, + close: function () { + this._super.apply(this, arguments); + this.on_closed(); + }, create_edit_record: function () { var self = this; var dialog = new FormViewDialog(this, _.extend({}, this.options, { diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js index ac8e83c8e30..bde1d793374 100644 --- a/addons/web/static/src/js/widgets/date_picker.js +++ b/addons/web/static/src/js/widgets/date_picker.js @@ -12,7 +12,6 @@ var DateWidget = Widget.extend({ template: "web.datepicker", type_of_date: "date", events: { - 'change.datetimepicker': 'changeDatetime', 'error.datetimepicker': 'errorDatetime', 'change .o_datepicker_input': 'changeDatetime', 'click input': '_onInputClicked', @@ -92,6 +91,13 @@ var DateWidget = Widget.extend({ * set datetime value */ changeDatetime: function () { + if (this.__libInput > 0) { + if (this.options.warn_future) { + this._warnFuture(this.getValue()); + } + this.trigger("datetime_changed"); + return; + } var oldValue = this.getValue(); if (this.isValid()) { this._setValueFromUi(); @@ -254,6 +260,7 @@ var DateWidget = Widget.extend({ */ _onDateTimePickerHide: function () { this.__isOpen = false; + this.changeDatetime(); if (this._onScroll) { window.removeEventListener('scroll', this._onScroll, true); } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 5eff2fb4422..657ad2d6ebb 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -712,8 +712,8 @@ <t t-name="FieldMany2One"> <t t-if="widget.mode === 'readonly'"> - <a t-if="!widget.nodeOptions.no_open" t-att-tabindex="widget.attrs.tabindex" class="o_form_uri" href="#"/> - <span t-if="widget.nodeOptions.no_open"/> + <a t-if="!widget.noOpen" t-att-tabindex="widget.attrs.tabindex" class="o_form_uri" href="#"/> + <span t-if="widget.noOpen"/> </t> <div t-if="widget.mode === 'edit'" class="o_field_widget o_field_many2one" aria-atomic="true"> <div class="o_input_dropdown"> |
