diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-02-23 17:20:52 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-02-23 17:20:52 +0100 |
| commit | a0d7763e099a35c0ad374a2d05dc5598404a6535 (patch) | |
| tree | c74ffdf27c85fd531656ed1a0c9d3fc84877de97 /addons/web/static/src | |
| parent | 12b580c953a762e40fb4a07a3fe5be3d119fa406 (diff) | |
| parent | 932e641be1798f4660fe9b284aaf9af10e57f663 (diff) | |
[MERGE] forward port branch saas-11.2 up to 932e641be17
Diffstat (limited to 'addons/web/static/src')
17 files changed, 140 insertions, 90 deletions
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js index 237ce1f1c96..1651e5d72d9 100644 --- a/addons/web/static/src/js/chrome/action_manager.js +++ b/addons/web/static/src/js/chrome/action_manager.js @@ -510,6 +510,7 @@ var ActionManager = Widget.extend({ }, }); return this.dp.add(runDef).then(function (action) { + action = action || { type: 'ir.actions.act_window_close' }; return self.doAction(action, options); }); }, diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index 7963af8ec32..9cfa99a9e7d 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -329,7 +329,7 @@ ActionManager.include({ _generateActionFlags: function (action) { var popup = action.target === 'new'; var inline = action.target === 'inline'; - var form = _.str.startsWith(action.view_mode, 'form'); + var form = action.views[0][1] === 'form'; return _.defaults({}, action.flags, { disableCustomFilters: action.context && action.context.search_disable_custom_filters, footerToButtons: popup, diff --git a/addons/web/static/src/js/chrome/web_client.js b/addons/web/static/src/js/chrome/web_client.js index d824e3e4176..76aadf49463 100644 --- a/addons/web/static/src/js/chrome/web_client.js +++ b/addons/web/static/src/js/chrome/web_client.js @@ -56,7 +56,7 @@ return AbstractWebClient.extend({ update_logo: function(reload) { var company = session.company_id; var img = session.url('/web/binary/company_logo' + '?db=' + session.db + (company ? '&company=' + company : '')); - this.$('.o_sub_menu_logo img').attr('src', '').attr('src', img + (reload ? "#" + Date.now() : '')); + this.$('.o_sub_menu_logo img').attr('src', '').attr('src', img + (reload ? "&t=" + Date.now() : '')); this.$('.oe_logo_edit').toggleClass('oe_logo_edit_admin', session.is_superuser); }, logo_edit: function(ev) { @@ -80,13 +80,8 @@ return AbstractWebClient.extend({ action_buttons: true, headless: true, }; - self.action_manager.doAction(result).then(function () { - var form = self.action_manager.dialog_widget.views.form.controller; - form.on("on_button_cancel", self.action_manager, self.action_manager.dialog_stop); - form.on('record_saved', self, function() { - self.action_manager.dialog_stop(); - self.update_logo(); - }); + self.action_manager.doAction(result, { + on_close: self.update_logo.bind(self, true), }); }); }); diff --git a/addons/web/static/src/js/core/domain.js b/addons/web/static/src/js/core/domain.js index 75aaf0105ed..8ce570b8605 100644 --- a/addons/web/static/src/js/core/domain.js +++ b/addons/web/static/src/js/core/domain.js @@ -240,7 +240,7 @@ var Domain = collections.Tree.extend({ */ stringToArray: function (domain, evalContext) { if (!_.isString(domain)) return _.clone(domain); - return pyeval.eval("domain", domain || "[]", evalContext); + return pyeval.eval("domain", domain ? domain.replace(/%%/g, '%') : "[]", evalContext); }, /** * Makes implicit "&" operators explicit in the given JS prefix-array diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js index 1bdd9e70a69..251cd2b508c 100644 --- a/addons/web/static/src/js/core/utils.js +++ b/addons/web/static/src/js/core/utils.js @@ -392,6 +392,23 @@ var utils = { array[i2] = elem1; array[i1] = elem2; }, + + /** + * @param {string} value + * @param {boolean} allow_mailto + * @returns boolean + */ + is_email: function (value, allow_mailto) { + // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript + var re; + if (allow_mailto) { + re = /^(mailto:)?(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + } else { + re = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + } + return re.test(value); + }, + /** * @param {any} str * @param {any} elseValues 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 2ddadfb0027..bc82710905b 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -335,7 +335,9 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { } self.model.discardChanges(recordID); if (self.model.isNew(recordID)) { - self._abandonRecord(recordID); + if (self.model.canBeAbandoned(recordID)) { + self._abandonRecord(recordID); + } return; } return self._confirmSave(recordID); 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 b8970a2ec89..74e41d1ee51 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -489,6 +489,30 @@ var BasicModel = AbstractModel.extend({ return _t("New"); }, /** + * Returns true if a record can be abandoned from a list datapoint. + * + * A record cannot be abandonned if it has been registered as "added" + * in the parent's savepoint, otherwise it can be abandonned. + * + * This is useful when discarding changes on this record, as it means that + * we must keep the record even if some fields are invalids (e.g. required + * field is empty). + * + * @param {string} id id for a local resource + * @returns {boolean} + */ + canBeAbandoned: function (id) { + var data = this.localData[id]; + var parent = this.localData[data.parentID]; + var abandonable = true; + if (parent) { + abandonable = !_.some(parent._savePoint, function (entry) { + return entry.operation === 'ADD' && entry.id === id; + }); + } + return abandonable; + }, + /** * Returns true if a record is dirty. A record is considered dirty if it has * some unsaved changes, marked by the _isDirty property on the record or * one of its subrecords. @@ -1445,25 +1469,23 @@ var BasicModel = AbstractModel.extend({ defs.push(self._applyOnChange(command[2], rec)); } else if (command[0] === 4) { // LINK TO - rec = self.localData[list._cache[command[1]]]; - if (rec) { - // modifications done on a record are discarded if - // the onchange uses a LINK TO - self.discardChanges(rec.id); - } - // the dataPoint id will be set when the record will be - // fetched (for now, this dataPoint may not exist yet) - list._changes.push({ - operation: 'ADD', - id: rec ? rec.id : null, - resID: command[1], - }); + linkRecord(list, command[1]); } else if (command[0] === 5) { // DELETE ALL list._changes = [{operation: 'REMOVE_ALL'}]; + } else if (command[0] === 6) { + list._changes = [{operation: 'REMOVE_ALL'}]; + _.each(command[2], function (resID) { + linkRecord(list, resID); + }); } }); - defs.push(self._readUngroupedList(list)); + var def = self._readUngroupedList(list).then(function () { + var x2ManysDef = self._fetchX2ManysBatched(list); + var referencesDef = self._fetchReferencesBatched(list); + return $.when(x2ManysDef, referencesDef); + }); + defs.push(def); } else { var newValue = self._parseServerValue(field, val); if (newValue !== oldValue) { @@ -1472,6 +1494,25 @@ var BasicModel = AbstractModel.extend({ } }); return $.when.apply($, defs); + + // inner function that adds a record (based on its res_id) to a list + // dataPoint (used for onchanges that return commands 4 (LINK TO) or + // commands 6 (REPLACE WITH)) + function linkRecord (list, resID) { + rec = self.localData[list._cache[resID]]; + if (rec) { + // modifications done on a record are discarded if the onchange + // uses a LINK TO or a REPLACE WITH + self.discardChanges(rec.id); + } + // the dataPoint id will be set when the record will be fetched (for + // now, this dataPoint may not exist yet) + list._changes.push({ + operation: 'ADD', + id: rec ? rec.id : null, + resID: resID, + }); + } }, /** * When an operation is applied to a x2many field, the field widgets @@ -1969,7 +2010,9 @@ var BasicModel = AbstractModel.extend({ _.each(list.data, function (dataPoint) { var record = self.localData[dataPoint]; var value = record.data[fieldName]; - if (value) { + // if the reference field has already been fetched, the value is a + // datapoint ID, and in this case there's nothing to do + if (value && !self.localData[value]) { var model = value.split(',')[0]; var resID = value.split(',')[1]; if (!(model in toFetch)) { 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 15df50d42ac..01dab22217c 100644 --- a/addons/web/static/src/js/views/calendar/calendar_controller.js +++ b/addons/web/static/src/js/views/calendar/calendar_controller.js @@ -57,6 +57,13 @@ var CalendarController = AbstractController.extend({ //-------------------------------------------------------------------------- /** + * @override + * @returns {string} + */ + getTitle: function () { + return this.get('title'); + }, + /** * Render the buttons according to the CalendarView.buttons template and * add listeners on it. Set this.$buttons with the produced jQuery element * 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 07155e327e3..8ce6ec14aba 100644 --- a/addons/web/static/src/js/views/graph/graph_renderer.js +++ b/addons/web/static/src/js/views/graph/graph_renderer.js @@ -60,7 +60,6 @@ return AbstractRenderer.extend({ * @returns {Deferred} The _super deferred is actually resolved immediately */ _render: function () { - this.$el.toggleClass('o_view_nocontent_container', !this.state.data.length); if (this.to_remove) { nv.utils.offWindowResize(this.to_remove); } diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 8e2222be3a9..8a779becdea 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -83,24 +83,6 @@ var KanbanController = BasicController.extend({ return this.renderer.updateRecord(this.model.get(id)); }, /** - * The column quick create should be displayed in kanban iff grouped by an - * m2o field and group_create action enabled. - * - * @private - * @param {Object} state the current state - * @returns {boolean} - */ - _isCreateColumnEnabled: function (state) { - var groupCreate = this.is_action_enabled('group_create'); - if (!groupCreate) { - // pre-return to avoid a lot of the following processing - return false; - } - var groupByField = state.fields[state.groupedBy[0]]; - var groupedByM2o = groupByField && (groupByField.type === 'many2one'); - return !!groupedByM2o; - }, - /** * This method calls the server to ask for a resequence. Note that this * does not rerender the user interface, because in most case, the * resequencing operation has already been displayed by the renderer. @@ -136,7 +118,7 @@ var KanbanController = BasicController.extend({ _updateButtons: function () { if (this.$buttons) { var state = this.model.get(this.handle, {raw: true}); - var createHidden = state.data.length === 0 && this._isCreateColumnEnabled(state); + var createHidden = this.is_action_enabled('group_create') && state.isGroupedByM2ONoColumn; this.$buttons.find('.o-kanban-button-new').toggleClass('o_hidden', createHidden); } }, diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index ba6b2ce3dbd..15d6284cfad 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -74,7 +74,6 @@ var KanbanModel = BasicModel.extend({ domain: parent.domain.concat([[groupBy,"=",result[0]]]), fields: parent.fields, fieldsInfo: parent.fieldsInfo, - groupedBy: parent.groupedBy, isOpen: true, limit: parent.limit, parentID: parent.id, @@ -132,7 +131,11 @@ var KanbanModel = BasicModel.extend({ }); }, /** - * Add the key `tooltipData` (kanban specific) when performing a `geĊ§`. + * Add the following (kanban specific) keys when performing a `get`: + * + * - tooltipData + * - progressBarValues + * - isGroupedByM2ONoColumn * * @override * @see _readTooltipFields @@ -141,11 +144,19 @@ var KanbanModel = BasicModel.extend({ get: function () { var result = this._super.apply(this, arguments); var dp = result && this.localData[result.id]; - if (dp && dp.tooltipData) { - result.tooltipData = $.extend(true, {}, dp.tooltipData); - } - if (dp && dp.progressBarValues) { - result.progressBarValues = $.extend(true, {}, dp.progressBarValues); + if (dp) { + if (dp.tooltipData) { + result.tooltipData = $.extend(true, {}, dp.tooltipData); + } + if (dp.progressBarValues) { + result.progressBarValues = $.extend(true, {}, dp.progressBarValues); + } + if (dp.fields[dp.groupedBy[0]]) { + var groupedByM2O = dp.fields[dp.groupedBy[0]].type === 'many2one'; + result.isGroupedByM2ONoColumn = !dp.data.length && groupedByM2O; + } else { + result.isGroupedByM2ONoColumn = false; + } } return result; }, diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index 6721f6d446c..913a4257257 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -302,16 +302,6 @@ var KanbanRenderer = BasicRenderer.extend({ } }, /** - * @private - * @override - * adds a specific class to the kanban helper so that it can be targetted by specific css - */ - _renderNoContentHelper: function() { - var $el = this._super.apply(this, arguments); - $el.toggleClass('o_kanban_view_nocontent',true) - return $el; - }, - /** * Renders an ungrouped kanban view in a fragment. * * @private @@ -360,7 +350,8 @@ var KanbanRenderer = BasicRenderer.extend({ !remove && !this._hasContent() && !!this.noContentHelp && - !(this.quickCreate && !this.quickCreate.folded); + !(this.quickCreate && !this.quickCreate.folded) && + !this.state.isGroupedByM2ONoColumn; var $noContentHelper = this.$('.o_view_nocontent'); 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 91a914be8df..8155862dbb4 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 @@ -58,8 +58,13 @@ ListRenderer.include({ * @returns {Deferred} */ start: function () { - if (this._isEditable()) { - this.$el.css({height: '100%'}); + // deliberately use the 'editable' attribute instead of '_isEditable' + // function, because the groupBy must not be taken into account to + // enable the '_onWindowClicked' handler (otherwise, an editable grouped + // list which is reloaded without groupBy wouldn't have this handler + // bound, and edited rows couldn't be left by clicking outside the list) + if (this.editable) { + this.$el.css({height: '100%'}); // seems useless: to remove in master core.bus.on('click', this, this._onWindowClicked.bind(this)); } return this._super(); 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 601752ce532..e9c9a4d1f76 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -633,7 +633,6 @@ var ListRenderer = BasicRenderer.extend({ this.pagers = []; var displayNoContentHelper = !this._hasContent() && !!this.noContentHelp; - this.$el.toggleClass('o_view_nocontent_container', displayNoContentHelper); // display the no content helper if there is no data to display if (displayNoContentHelper) { this.$el.html(this._renderNoContentHelper()); diff --git a/addons/web/static/src/js/views/pivot/pivot_view.js b/addons/web/static/src/js/views/pivot/pivot_view.js index 0e908f29b8c..0d42fc7bd46 100644 --- a/addons/web/static/src/js/views/pivot/pivot_view.js +++ b/addons/web/static/src/js/views/pivot/pivot_view.js @@ -101,9 +101,10 @@ var PivotView = AbstractView.extend({ _findView(params.action && params.action.views, 'form'), ]; function _findView(views, viewType) { - return _.find(views, function (view) { - return view[1] === viewType; - }) || [false, viewType]; + var view = _.find(views, function (view) { + return view.type === viewType; + }); + return [view ? view.viewID : false, viewType]; } }, }); diff --git a/addons/web/static/src/less/views.less b/addons/web/static/src/less/views.less index d5a868a11c5..104d4e1b2a5 100644 --- a/addons/web/static/src/less/views.less +++ b/addons/web/static/src/less/views.less @@ -39,18 +39,11 @@ } // No content helper -.o_view_nocontent_container { - .o-flex-display(); - height: 100%; - padding: 0; -} - .o_view_nocontent { - &.o_kanban_view_nocontent { - .o-position-absolute(20%, 0, 0, 0); - pointer-events: none; - } - + .o-position-absolute(20%, 0, 0, 0); + pointer-events: none; + z-index: 1; + .o_nocontent_help { pointer-events: auto; max-width: 650px; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 3ed21a8b0c3..65b75fa00e0 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -488,8 +488,10 @@ </div> </t> <div t-name="GraphView.error" class="o_view_nocontent" > - <p class="o_view_nocontent_empty_folder"><t t-esc="title"/></p> - <p><t t-esc="description"/></p> + <div class="o_nocontent_help"> + <p class="o_view_nocontent_empty_folder"><t t-esc="title"/></p> + <p><t t-esc="description"/></p> + </div> </div> <div t-name="PivotView" class="o_pivot"> @@ -539,14 +541,16 @@ </t> </ul> </t> -<div t-name="PivotView.nodata" class="o_view_nocontent_container" > +<div t-name="PivotView.nodata"> <div class="o_view_nocontent"> - <p class="o_view_nocontent_empty_folder"> - No data to display - </p><p> - Try to add some records, or make sure - that there is at least one measure and no active filter in the search bar. - </p> + <div class="o_nocontent_help"> + <p class="o_view_nocontent_empty_folder"> + No data to display + </p><p> + Try to add some records, or make sure + that there is at least one measure and no active filter in the search bar. + </p> + </div> </div> </div> |
