summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-02-23 17:20:52 +0100
committerChristophe Simonis <chs@odoo.com>2018-02-23 17:20:52 +0100
commita0d7763e099a35c0ad374a2d05dc5598404a6535 (patch)
treec74ffdf27c85fd531656ed1a0c9d3fc84877de97 /addons/web/static/src
parent12b580c953a762e40fb4a07a3fe5be3d119fa406 (diff)
parent932e641be1798f4660fe9b284aaf9af10e57f663 (diff)
[MERGE] forward port branch saas-11.2 up to 932e641be17
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js1
-rw-r--r--addons/web/static/src/js/chrome/action_manager_act_window.js2
-rw-r--r--addons/web/static/src/js/chrome/web_client.js11
-rw-r--r--addons/web/static/src/js/core/domain.js2
-rw-r--r--addons/web/static/src/js/core/utils.js17
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js4
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js73
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js7
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js1
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js20
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_model.js25
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer.js13
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js9
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js1
-rw-r--r--addons/web/static/src/js/views/pivot/pivot_view.js7
-rw-r--r--addons/web/static/src/less/views.less15
-rw-r--r--addons/web/static/src/xml/base.xml22
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>