diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-08-14 16:48:10 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-08-14 16:48:10 +0200 |
| commit | 140ee6b8f0b0de7ddcc816496269963612c5d0c6 (patch) | |
| tree | 7bdaa065d215b88fea4fa56adbfa79cdc24deb69 /addons/web/static/src/js | |
| parent | 839696d2d7dd574fd8958d17726379926e94d590 (diff) | |
| parent | 98a55917a602c0c47093b4270ebc4e8b416ecf32 (diff) | |
[MERGE] forward port branch saas-12.4 up to 98a55917a60
Diffstat (limited to 'addons/web/static/src/js')
14 files changed, 185 insertions, 35 deletions
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index d226ef655c7..1466402cc98 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -1803,8 +1803,8 @@ var FieldBinaryImage = AbstractFieldBinary.extend({ } this.$('> img').remove(); this.$el.prepend($img); + $img.one('error', function () { - self._clearFile(); $img.attr('src', self.placeholder); self.do_warn(_t("Image"), _t("Could not display the selected image.")); }); diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 58dada8703e..d547ef6849f 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -857,6 +857,8 @@ var FieldX2Many = AbstractField.extend({ toggle_column_order: '_onToggleColumnOrder', activate_next_widget: '_onActiveNextWidget', navigation_move: '_onNavigationMove', + save_optional_fields: '_onSaveOrLoadOptionalFields', + load_optional_fields: '_onSaveOrLoadOptionalFields', }), // We need to trigger the reset on every changes to be aware of the parent changes @@ -1389,6 +1391,18 @@ var FieldX2Many = AbstractField.extend({ }); }, /** + * Add necessary key parts for the basic controller to compute the local + * storage key. The event will be properly handled by the basic controller. + * + * @param {OdooEvent} ev + * @private + */ + _onSaveOrLoadOptionalFields: function (ev) { + ev.data.keyParts.relationalField = this.name; + ev.data.keyParts.subViewId = this.view.view_id; + ev.data.keyParts.subViewType = this.view.type; + }, + /** * Forces a resequencing of the records. * * @private diff --git a/addons/web/static/src/js/public/public_root_instance.js b/addons/web/static/src/js/public/public_root_instance.js index 8202e011cc3..988d939bbe1 100644 --- a/addons/web/static/src/js/public/public_root_instance.js +++ b/addons/web/static/src/js/public/public_root_instance.js @@ -1,7 +1,7 @@ odoo.define('root.widget', function (require) { 'use strict'; -require('web.dom_ready'); +var lazyloader = require('web.public.lazyloader'); var rootData = require('web.public.root'); /** @@ -11,7 +11,10 @@ var rootData = require('web.public.root'); * been consumed. */ var publicRoot = new rootData.PublicRoot(null); -return publicRoot.attachTo(document.body).then(function () { - return publicRoot; +return lazyloader.allScriptsLoaded.then(function () { + return publicRoot.attachTo(document.body).then(function () { + return publicRoot; + }); }); + }); 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 2fb6f6400c9..2163f074cb0 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -21,6 +21,8 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { reload: '_onReload', resequence_records: '_onResequenceRecords', set_dirty: '_onSetDirty', + load_optional_fields: '_onLoadOptionalFields', + save_optional_fields: '_onSaveOptionalFields', sidebar_data_asked: '_onSidebarDataAsked', translate: '_onTranslate', }), @@ -43,6 +45,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { // operations to complete before checking if there are changes to // discard when discardChanges is called this.savingDef = Promise.resolve(); + this.viewId = params.viewId; }, /** * @override @@ -419,6 +422,49 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { }); }, /** + * Compute the optional fields local storage key using the given parts. + * + * @param {Object} keyParts + * @param {string} keyParts.viewType view type + * @param {string} [keyParts.relationalField] name of the field with subview + * @param {integer} [keyParts.subViewId] subview id + * @param {string} [keyParts.subViewType] type of the subview + * @param {Object} keyParts.fields fields + * @param {string} keyParts.fields.name field name + * @param {string} keyParts.fields.type field type + * @returns {string} local storage key for optional fields in this view + * @private + */ + _getOptionalFieldsLocalStorageKey: function (keyParts) { + keyParts.model = this.modelName; + keyParts.viewType = this.viewType; + keyParts.viewId = this.viewId; + + var parts = [ + 'model', + 'viewType', + 'viewId', + 'relationalField', + 'subViewType', + 'subViewId', + ]; + + var viewIdentifier = parts.reduce(function (identifier, partName) { + if (partName in keyParts) { + return identifier + ',' + keyParts[partName]; + } + return identifier; + }, 'optional_fields'); + + viewIdentifier = + keyParts.fields.sort(this._nameSortComparer) + .reduce(function (identifier, field) { + return identifier + ',' + field.name; + }, viewIdentifier); + + return viewIdentifier; + }, + /** * Return the params (current_min, limit and size) to pass to the pager, * according to the current state. * @@ -458,6 +504,16 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { return true; }, /** + * Sort function used to sort the fields by names, to compute the optional fields keys + * + * @param {Object} left + * @param {Object} right + * @private + */ + _nameSortComparer: function(left, right) { + return left.name < right.name ? -1 : 1; + }, + /** * Helper function to display a warning that some fields have an invalid * value. This is used when a save operation cannot be completed. * @@ -676,6 +732,39 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { }); }, /** + * Load the optional columns settings in local storage for this view + * + * @param {OdooEvent} ev + * @param {Object} ev.data.keyParts see _getLocalStorageKey + * @param {function} ev.data.callback function to call with the result + * @private + */ + _onLoadOptionalFields: function (ev) { + var res = this.call( + 'local_storage', + 'getItem', + this._getOptionalFieldsLocalStorageKey(ev.data.keyParts) + ); + ev.data.callback(res); + }, + /** + * Save the optional columns settings in local storage for this view + * + * @param {OdooEvent} ev + * @param {Object} ev.data.keyParts see _getLocalStorageKey + * @param {Array<string>} ev.data.optionalColumnsEnabled list of optional + * field names that have been enabled + * @private + */ + _onSaveOptionalFields: function (ev) { + this.call( + 'local_storage', + 'setItem', + this._getOptionalFieldsLocalStorageKey(ev.data.keyParts), + ev.data.optionalColumnsEnabled + ); + }, + /** * @private * @param {OdooEvent} ev */ diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 00a1be6c228..13ea270870f 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -796,6 +796,7 @@ var BasicModel = AbstractModel.extend({ }); _.each(fields, function (field) { var dataPoint; + record.data[field.name] = null; if (field.type === 'many2one') { if (field.value) { var id = _.isArray(field.value) ? field.value[0] : field.value; diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 7fd8af532a0..38d2f220c2f 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -48,6 +48,7 @@ var BasicView = AbstractView.extend({ this.controllerParams.archiveEnabled = 'active' in this.fields; this.controllerParams.hasButtons = 'action_buttons' in params ? params.action_buttons : true; + this.controllerParams.viewId = viewInfo.view_id; this.loadParams.fieldsInfo = this.fieldsInfo; this.loadParams.fields = this.fields; diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js index a0de1c665f5..edb8e54e0b7 100644 --- a/addons/web/static/src/js/views/calendar/calendar_controller.js +++ b/addons/web/static/src/js/views/calendar/calendar_controller.js @@ -55,6 +55,7 @@ var CalendarController = AbstractController.extend({ this.readonlyFormViewId = params.readonlyFormViewId; this.mapping = params.mapping; this.context = params.context; + this.previousOpen = null; // The quickCreating attribute ensures that we don't do several create this.quickCreating = false; }, @@ -285,7 +286,8 @@ var CalendarController = AbstractController.extend({ title += ': ' + this.renderer.arch.attrs.string; } if (this.eventOpenPopup) { - new dialogs.FormViewDialog(self, { + if (this.previousOpen) { this.previousOpen.close(); } + this.previousOpen = new dialogs.FormViewDialog(self, { res_model: this.modelName, context: context, title: title, @@ -297,7 +299,8 @@ var CalendarController = AbstractController.extend({ } self.reload(); }, - }).open(); + }); + this.previousOpen.open(); } else { this.do_action({ type: 'ir.actions.act_window', diff --git a/addons/web/static/src/js/views/control_panel/search/groupby_menu.js b/addons/web/static/src/js/views/control_panel/search/groupby_menu.js index 982ac097b5c..a41bd1c23b5 100644 --- a/addons/web/static/src/js/views/control_panel/search/groupby_menu.js +++ b/addons/web/static/src/js/views/control_panel/search/groupby_menu.js @@ -31,6 +31,7 @@ var GroupByMenu = DropdownMenu.extend({ */ init: function (parent, groupBys, fields, options) { var self = this; + options = options || {}; this._super(parent, groupBys); this.fields = fields; // determines when the 'Add custom groupby' submenu is open @@ -55,9 +56,9 @@ var GroupByMenu = DropdownMenu.extend({ this.dropdownCategory = 'groupby'; this.dropdownTitle = _t('Group By'); this.dropdownIcon = 'fa fa-bars'; - this.dropdownSymbol = this.isMobile ? 'fa fa-chevron-right float-right mt4' : false; + this.dropdownSymbol = this.isMobile && !options.noSymbol ? 'fa fa-chevron-right float-right mt4' : false; // the default style of the groupby menu can be changed here using the options key 'headerStyle' - if (options && options.headerStyle === 'primary') { + if (options.headerStyle === 'primary') { this.dropdownStyle = { el: {class: 'btn-group o_group_by_menu o_dropdown', attrs: {'role': 'group'}}, mainButton: {class: 'btn btn-primary dropdown-toggle'}, diff --git a/addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js b/addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js index 5c421a6e7ee..a51386d20aa 100644 --- a/addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js +++ b/addons/web/static/src/js/views/control_panel/search/groupby_menu_mixin.js @@ -36,13 +36,14 @@ var GroupByMenuMixin = { * private * @param {jQuery} $node * @param {Object} groupableFields + * @param {Promise} */ _addGroupByMenu: function ($node, groupableFields) { this.sortedFieldNames = Object.keys(groupableFields).sort(); this.groupableFields = groupableFields; const groupBys = this._getGroupBys(this.model.get().groupBy); - this.groupByMenu = new GroupByMenu(this, groupBys, this.groupableFields); - this.groupByMenu.insertAfter($node.find('div:first')); + this.groupByMenu = new GroupByMenu(this, groupBys, this.groupableFields, {noSymbol: true}); + return this.groupByMenu.insertAfter($node.find('div:first')); }, /** * This method puts the active groupBys in a convenient form. diff --git a/addons/web/static/src/js/views/graph/graph_controller.js b/addons/web/static/src/js/views/graph/graph_controller.js index 8cfed52a050..2953b99f731 100644 --- a/addons/web/static/src/js/views/graph/graph_controller.js +++ b/addons/web/static/src/js/views/graph/graph_controller.js @@ -96,8 +96,13 @@ var GraphController = AbstractController.extend(GroupByMenuMixin,{ this._updateButtons(); this.$buttons.appendTo($node); if (this.isEmbedded) { - this._addGroupByMenu($node, this.groupableFields); + this._addGroupByMenu($node, this.groupableFields).then(function(){ + var groupByButton = $node.find('.o_dropdown_toggler_btn'); + groupByButton.removeClass("o_dropdown_toggler_btn btn btn-secondary dropdown-toggle"); + groupByButton.addClass("btn dropdown-toggle btn-outline-secondary"); + }); } + } }, diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js index ea94020388f..e24de02879c 100644 --- a/addons/web/static/src/js/views/graph/graph_renderer.js +++ b/addons/web/static/src/js/views/graph/graph_renderer.js @@ -314,6 +314,24 @@ return AbstractRenderer.extend({ return this.state.origins[dataPt.originIndex]; }, /** + * Returns an object used to style chart elements independently from the datasets. + * + * @private + * @returns {Object} + */ + _getElementOptions: function () { + var elementOptions = {}; + if (this.state.mode === 'bar') { + elementOptions.rectangle = {borderWidth: 1}; + } else if (this.state.mode === 'line') { + elementOptions.line = { + tension: 0, + fill: false, + }; + } + return elementOptions; + }, + /** * Returns a DateClasses instance used to manage equivalence of dates. * * @private @@ -348,9 +366,8 @@ return AbstractRenderer.extend({ } else { return dataPt.labels.slice(0, 1); } - } else if (i >= 0) { + } else if (i === 0) { return Array.prototype.concat.apply([], [ - dataPt.labels.slice(0, i), this.dateClasses.dateClass(dataPt.originIndex, dataPt.labels[i]), dataPt.labels.slice(i+1) ]); @@ -667,6 +684,7 @@ return AbstractRenderer.extend({ scales: this._getScaleOptions(), legend: this._getLegendOptions(datasetsCount), tooltips: this._getTooltipOptions(), + elements: this._getElementOptions(), }; }, /** @@ -687,7 +705,7 @@ return AbstractRenderer.extend({ if (_.contains(['bar', 'line'], this.state.mode) && i === 0) { // here label is an array of length 1 and contains a number return this.dateClasses.representative(label, originIndex) || ''; - } else if (this.state.mode === 'pie' && i >= 0) { + } else if (this.state.mode === 'pie' && i === 0) { // here label is an array of length at least one containing string or numbers var labelCopy = label.slice(0); if (originIndex !== undefined) { @@ -737,7 +755,7 @@ return AbstractRenderer.extend({ this.$el.append($canvasContainer); var i = this.state.comparisonFieldIndex; - if (i === 0 || (i > 0 && this.state.mode === 'pie')) { + if (i === 0) { this.dateClasses = this._getDateClasses(dataPoints); } if (this.state.mode === 'bar') { @@ -761,9 +779,6 @@ return AbstractRenderer.extend({ _renderBarChart: function (dataPoints) { var self = this; - // style rectangles - Chart.defaults.global.elements.rectangle.borderWidth = 1; - // prepare data var data = this._prepareData(dataPoints); @@ -820,10 +835,6 @@ return AbstractRenderer.extend({ _renderLineChart: function (dataPoints) { var self = this; - // style lines - Chart.defaults.global.elements.line.tension = 0; - Chart.defaults.global.elements.line.fill = false; - // prepare data var data = this._prepareData(dataPoints); data.datasets.forEach(function (dataset, index) { 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 845014926b3..2ddf21b1ef8 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -70,7 +70,16 @@ var ListRenderer = BasicRenderer.extend({ this.editable = params.editable; this.isGrouped = this.state.groupedBy.length > 0; this.groupbys = params.groupbys; + }, + /** + * Compute columns visilibity. This can't be done earlier as we need the + * controller to respond to the load_optional_fields call of processColumns. + * + * @override + */ + willStart: function() { this._processColumns(this.columnInvisibleFields || {}); + return this._super.apply(this, arguments); }, //-------------------------------------------------------------------------- @@ -241,14 +250,13 @@ var ListRenderer = BasicRenderer.extend({ * @private * @returns {string} */ - _getOptionalColumnsStorageKey: function () { + _getOptionalColumnsStorageKeyParts: function () { var self = this; - var fields = []; - _.each(this.state.fieldsInfo[this.viewType], function (field, name) { - fields.push(name + ":" + self.state.fields[name].type); - }); - fields.sort(); - return "list_optional_fields," + this.state.model + "," + this.viewType + "," + fields.join(','); + return { + fields: _.map(this.state.fieldsInfo[this.viewType], function (_, fieldName) { + return {name: fieldName, type: self.state.fields[fieldName].type}; + }), + } }, /** * Removes the columns which should be invisible. @@ -261,8 +269,13 @@ var ListRenderer = BasicRenderer.extend({ this.columns = []; this.optionalColumns = []; this.optionalColumnsEnabled = []; - var localStorageKey = this._getOptionalColumnsStorageKey(); - var storedOptionalColumns = this.call('local_storage', 'getItem', localStorageKey); + var storedOptionalColumns; + this.trigger_up('load_optional_fields', { + keyParts: this._getOptionalColumnsStorageKeyParts(), + callback: function(res) { + storedOptionalColumns = res; + }, + }); _.each(this.arch.children, function (c) { if (c.tag !== 'control' && c.tag !== 'groupby') { var reject = c.attrs.modifiers.column_invisible; @@ -750,7 +763,7 @@ var ListRenderer = BasicRenderer.extend({ } if (field.type === 'float' || field.type === 'integer' || field.type === 'monetary') { - $th.css({ textAlign: 'right' }); + $th.addClass('o_list_number_th'); } if (config.isDebug()) { @@ -1032,7 +1045,10 @@ var ListRenderer = BasicRenderer.extend({ } else { this.optionalColumnsEnabled.push(input.name); } - this.call('local_storage', 'setItem', this._getOptionalColumnsStorageKey(), this.optionalColumnsEnabled); + this.trigger_up('save_optional_fields', { + keyParts: this._getOptionalColumnsStorageKeyParts(), + optionalColumnsEnabled: this.optionalColumnsEnabled, + }); this._processColumns(this.columnInvisibleFields || {}); this._renderView().then(function() { self._onToggleOptionalColumnDropdown(ev); diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js index ed71679699c..755ee32979e 100644 --- a/addons/web/static/src/js/widgets/domain_selector.js +++ b/addons/web/static/src/js/widgets/domain_selector.js @@ -652,7 +652,7 @@ var DomainLeaf = DomainNode.extend({ this.fieldSelector = new ModelFieldSelector( this, this.model, - this.chain ? this.chain.split(".") : [], + this.chain !== undefined ? this.chain.toString().split(".") : [], this.options ); defs.push(this.fieldSelector.appendTo($("<div/>")).then((function () { diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js index 08a7ae47c81..ed74b2da44a 100644 --- a/addons/web/static/src/js/widgets/model_field_selector.js +++ b/addons/web/static/src/js/widgets/model_field_selector.js @@ -301,11 +301,16 @@ var ModelFieldSelector = Widget.extend({ }).bind(this)); function processChain(chain) { - var field = this._getLastPageField(chain.pop()); + var fieldName = chain.pop(); + var field = this._getLastPageField(fieldName); if (field && field.relation && chain.length > 0) { // Fetch next chain node if any and possible return this._pushPageData(field.relation).then(processChain.bind(this, chain)); } else if (field && chain.length === 0) { // Last node fetched return Promise.resolve(); + } else if (!field && fieldName === "1") { // TRUE_LEAF + this._validate(true); + } else if (!field && fieldName === "0") { // FALSE_LEAF + this._validate(true); } else { // Wrong node chain this._validate(false); } |
