summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js15
-rw-r--r--addons/web/static/src/js/chrome/action_manager_act_window.js8
-rw-r--r--addons/web/static/src/js/core/rpc.js14
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js15
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js31
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js10
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js76
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_model.js5
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js15
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js7
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js48
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_model.js5
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js11
-rw-r--r--addons/web/static/src/js/widgets/debug_manager.js1
-rw-r--r--addons/web/static/src/less/bootstrap_overridden.less2
-rw-r--r--addons/web/static/src/less/datepicker.less3
-rw-r--r--addons/web/static/src/less/fields.less2
-rw-r--r--addons/web/static/src/less/form_view.less4
-rw-r--r--addons/web/static/src/less/kanban_column_progressbar.less1
-rw-r--r--addons/web/static/src/less/kanban_dashboard.less1
-rw-r--r--addons/web/static/src/less/list_view.less6
21 files changed, 214 insertions, 66 deletions
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index b97262079ef..b36e1b8eec9 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -839,12 +839,27 @@ var ActionManager = Widget.extend({
* is ready when it will be appended to the DOM. This allows to prevent
* flickering for widgets doing async stuff in willStart() or start().
*
+ * Also updates the control panel on any change of the title on controller's
+ * widget.
+ *
* @private
* @param {Object} controller
* @returns {Deferred<Object>} resolved with the controller when it is ready
*/
_startController: function (controller) {
+ var self = this;
var fragment = document.createDocumentFragment();
+ // AAB: change this logic to stop using the properties mixin
+ controller.widget.on("change:title", this, function () {
+ if (self.getCurrentController() !== controller) {
+ return;
+ }
+ var action = self.actions[controller.actionID];
+ if (!action.flags || !action.flags.headless) {
+ var breadcrumbs = self._getBreadcrumbs();
+ self.controlPanel.update({breadcrumbs: breadcrumbs}, {clear: false});
+ }
+ });
return controller.widget.appendTo(fragment).then(function () {
return controller;
});
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 9dc0ff4c1c8..0ed909e2512 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
@@ -229,15 +229,7 @@ ActionManager.include({
// the action has been removed, so simply destroy the widget
widget.destroy();
} else {
- // AAB: change this logic to stop using the properties mixin
- widget.on("change:title", this, function () {
- if (!action.flags.headless) {
- var breadcrumbs = self._getBreadcrumbs();
- self.controlPanel.update({breadcrumbs: breadcrumbs}, {clear: false});
- }
- });
controller.widget = widget;
-
def.resolve(controller);
}
}).fail(def.reject.bind(def));
diff --git a/addons/web/static/src/js/core/rpc.js b/addons/web/static/src/js/core/rpc.js
index 53d298c9019..cc56cb74ebb 100644
--- a/addons/web/static/src/js/core/rpc.js
+++ b/addons/web/static/src/js/core/rpc.js
@@ -52,19 +52,19 @@ return {
if (options.method === 'read_group') {
if (!(params.args && params.args[0] !== undefined)) {
- params.kwargs.domain = options.domain || params.domain || params.kwargs.domain || [];
+ params.kwargs.domain = options.domain || params.domain || params.kwargs.domain || [];
}
if (!(params.args && params.args[1] !== undefined)) {
- params.kwargs.fields = options.fields || params.fields || params.kwargs.fields || [];
+ params.kwargs.fields = options.fields || params.fields || params.kwargs.fields || [];
}
if (!(params.args && params.args[2] !== undefined)) {
- params.kwargs.groupby = options.groupBy || params.groupBy || params.kwargs.groupby || [];
+ params.kwargs.groupby = options.groupBy || params.groupBy || params.kwargs.groupby || [];
}
- params.kwargs.offset = options.offset || params.offset || params.kwargs.offset;
- params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
+ params.kwargs.offset = options.offset || params.offset || params.kwargs.offset;
+ params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
// In kwargs, we look for "orderby" rather than "orderBy" (note the absence of capital B),
// since the Python argument to the actual function is "orderby".
- var orderBy = options.orderBy || params.orderBy || params.kwargs.orderby;
+ var orderBy = options.orderBy || params.orderBy || params.kwargs.orderby;
params.kwargs.orderby = orderBy ? this._serializeSort(orderBy) : orderBy;
params.kwargs.lazy = 'lazy' in options ? options.lazy : params.lazy;
}
@@ -72,7 +72,7 @@ return {
if (options.method === 'search_read') {
// call the model method
params.kwargs.domain = options.domain || params.domain || params.kwargs.domain;
- params.kwargs.fields = options.fields || params.fields || params.kwargs.fields;
+ params.kwargs.fields = options.fields || params.fields || params.kwargs.fields;
params.kwargs.offset = options.offset || params.offset || params.kwargs.offset;
params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
// In kwargs, we look for "order" rather than "orderBy" since the Python
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 0f426dedd3b..c703694a9d4 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -998,6 +998,21 @@ var FieldPhone = FieldEmail.extend({
// important right now.
this.$el.removeClass('o_text_overflow');
},
+ * Remove possibly present &shy; characters when saving number
+ *
+ * @override
+ * @private
+ */
+ _setValue: function (value, options) {
+ // NOT NEEDED AS OF SAAS-11.3
+ if (value) {
+ // remove possibly pasted &shy; characters
+ value = value.replace(/\u00AD/g, '');
+ }
+ return this._super(value, options);
+ },
+
+ /**
});
var UrlWidget = InputField.extend({
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 93c924fad4c..c3df03fcff9 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -1059,7 +1059,7 @@ var FieldX2Many = AbstractField.extend({
*/
_onEditLine: function (ev) {
ev.stopPropagation();
- this.trigger_up('freeze_order', {id: this.value.id});
+ this.trigger_up('edited_list', { id: this.value.id });
var editedRecord = this.value.data[ev.data.index];
this.renderer.setRowMode(editedRecord.id, 'edit')
.done(ev.data.onSuccess);
@@ -1147,7 +1147,7 @@ var FieldX2Many = AbstractField.extend({
_onResequence: function (event) {
event.stopPropagation();
var self = this;
- this.trigger_up('freeze_order', {id: this.value.id});
+ this.trigger_up('edited_list', { id: this.value.id });
var rowIDs = event.data.rowIDs.slice();
var rowID = rowIDs.pop();
var defs = _.map(rowIDs, function (rowID, index) {
@@ -1300,7 +1300,7 @@ var FieldOne2Many = FieldX2Many.extend({
}
} else if (!this.creatingRecord) {
this.creatingRecord = true;
- this.trigger_up('freeze_order', {id: this.value.id});
+ this.trigger_up('edited_list', { id: this.value.id });
this._setValue({
operation: 'CREATE',
position: this.editable,
@@ -1330,12 +1330,27 @@ var FieldOne2Many = FieldX2Many.extend({
// we don't want interference with the components upstream.
ev.stopPropagation();
+ var self = this;
var id = ev.data.id;
- // trigger an empty 'UPDATE' operation when the user clicks on 'Save' in
- // the dialog, to notify the main record that a subrecord of this
- // relational field has changed (those changes will be already stored on
- // that subrecord, thanks to the 'Save').
- var onSaved = this._setValue.bind(this, { operation: 'UPDATE', id: id }, {});
+ var onSaved = function (record, hasChanged) {
+ if (!hasChanged) {
+ return;
+ }
+ if (_.some(self.value.data, {id: record.id})) {
+ // the record already exists in the relation, so trigger an
+ // empty 'UPDATE' operation when the user clicks on 'Save' in
+ // the dialog, to notify the main record that a subrecord of
+ // this relational field has changed (those changes will be
+ // already stored on that subrecord, thanks to the 'Save').
+ self._setValue({ operation: 'UPDATE', id: record.id });
+ } else {
+ // the record isn't in the relation yet, so add it ; this can
+ // happen if the user clicks on 'Save & New' in the dialog (the
+ // opened record will be updated, and other records will be
+ // created)
+ self._setValue({ operation: 'ADD', id: record.id });
+ }
+ };
this._openFormDialog({
id: id,
on_saved: onSaved,
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 8a9b3713f15..b4d00b6bb76 100644
--- a/addons/web/static/src/js/views/basic/basic_controller.js
+++ b/addons/web/static/src/js/views/basic/basic_controller.js
@@ -314,7 +314,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
*
* @private
*/
- _disableButtons: function () {
+ _disableButtons: function () {
if (this.$buttons) {
this.$buttons.find('button').attr('disabled', true);
}
@@ -346,10 +346,8 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
return;
}
self.model.discardChanges(recordID);
- if (self.model.isNew(recordID)) {
- if (self.model.canBeAbandoned(recordID)) {
- self._abandonRecord(recordID);
- }
+ if (self.model.canBeAbandoned(recordID)) {
+ self._abandonRecord(recordID);
return;
}
return self._confirmSave(recordID);
@@ -360,7 +358,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
*
* @private
*/
- _enableButtons: function () {
+ _enableButtons: function () {
if (this.$buttons) {
this.$buttons.find('button').removeAttr('disabled');
}
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 4cf6f9fa27b..962bb2899e1 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -386,11 +386,12 @@ var BasicModel = AbstractModel.extend({
duplicateRecord: function (recordID) {
var self = this;
var record = this.localData[recordID];
+ var context = this._getContext(record);
return this._rpc({
model: record.model,
method: 'copy',
args: [record.data.id],
- context: this._getContext(record),
+ context: context,
})
.then(function (res_id) {
var index = record.res_ids.indexOf(record.res_id);
@@ -402,6 +403,7 @@ var BasicModel = AbstractModel.extend({
res_id: res_id,
res_ids: record.res_ids.slice(0),
viewType: record.viewType,
+ context: context,
});
});
},
@@ -578,10 +580,15 @@ var BasicModel = AbstractModel.extend({
return _t("New");
},
/**
- * Returns true if a record can be abandoned from a list datapoint.
+ * Returns true if a record can be abandoned.
+ *
+ * A record can be abandonned if it is a new record, except if
+ * this datapoint has been specifically tagged as "do not abandon".
+ *
+ * Example:
*
- * A record cannot be abandonned if it has been registered as "added"
- * in the parent's savepoint, otherwise it can be abandonned.
+ * - Discard record from "Add an item" => "New" record => abandon
+ * - Discard record from `default_get`/`onchange` => do not abandon
*
* 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
@@ -591,15 +598,7 @@ var BasicModel = AbstractModel.extend({
* @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;
+ return !this.localData[id]._noAbandon && this.isNew(id);
},
/**
* Returns true if a record is dirty. A record is considered dirty if it has
@@ -960,11 +959,11 @@ var BasicModel = AbstractModel.extend({
* @returns {Deferred}
* Resolved with the list of field names (whose value has been modified)
*/
- save: function (record_id, options) {
+ save: function (recordID, options) {
var self = this;
return this.mutex.exec(function () {
options = options || {};
- var record = self.localData[record_id];
+ var record = self.localData[recordID];
if (options.savePoint) {
self._visitChildren(record, function (rec) {
var newValue = rec._changes || rec.data;
@@ -982,7 +981,7 @@ var BasicModel = AbstractModel.extend({
});
}
var shouldReload = 'reload' in options ? options.reload : true;
- var method = self.isNew(record_id) ? 'create' : 'write';
+ var method = self.isNew(recordID) ? 'create' : 'write';
if (record._changes) {
// id never changes, and should not be written
delete record._changes.id;
@@ -1553,6 +1552,7 @@ var BasicModel = AbstractModel.extend({
list._cache[rec.res_id] = rec.id;
}
+ rec._noAbandon = true;
list._changes.push({operation: 'ADD', id: rec.id});
if (command[0] === 1) {
list._changes.push({operation: 'UPDATE', id: rec.id});
@@ -2029,7 +2029,7 @@ var BasicModel = AbstractModel.extend({
.then(function () {
return $.when(
self._fetchX2Manys(record, options),
- self._fetchReferences(record)
+ self._fetchReferences(record, options)
).then(function () {
return self._postprocess(record, options);
});
@@ -2076,10 +2076,10 @@ var BasicModel = AbstractModel.extend({
* @param {Object} record
* @returns {Deferred}
*/
- _fetchReferences: function (record) {
+ _fetchReferences: function (record, options) {
var self = this;
var defs = [];
- var fieldNames = record.getFieldNames();
+ var fieldNames = options && options.fieldNames || record.getFieldNames();
_.each(fieldNames, function (fieldName) {
var field = record.fields[fieldName];
if (field.type === 'reference') {
@@ -3199,6 +3199,9 @@ var BasicModel = AbstractModel.extend({
* @param {Object} [options]
* @param {string[]} [options.fieldNames] the fields to fetch for a record
* @param {boolean} [options.onlyGroups=false]
+ * @param {boolean} [options.keepEmptyGroups=false] if set, the groups not
+ * present in the read_group anymore (empty groups) will stay in the
+ * datapoint (used to mimic the kanban renderer behaviour for example)
* @returns {Deferred}
*/
_load: function (dataPoint, options) {
@@ -3351,9 +3354,29 @@ var BasicModel = AbstractModel.extend({
*/
_makeDefaultRecord: function (modelName, params) {
var self = this;
+
+ var determineExtraFields = function() {
+ // Fields that are present in the originating view, that need to be initialized
+ // Hence preventing their value to crash when getting back to the originating view
+ var parentRecord = self.localData[params.parentID];
+
+ var originView = parentRecord && parentRecord.fieldsInfo;
+ if (!originView || !originView[parentRecord.viewType])
+ return [];
+
+ var fieldsFromOrigin = _.filter(Object.keys(originView[parentRecord.viewType]),
+ function(fieldname) {
+ return params.fields[fieldname] !== undefined;
+ });
+
+ return fieldsFromOrigin;
+ }
+
var fieldNames = Object.keys(params.fieldsInfo[params.viewType]);
var fields_key = _.without(fieldNames, '__last_update');
+ var extraFields = determineExtraFields();
+
return this._rpc({
model: modelName,
method: 'default_get',
@@ -3371,7 +3394,7 @@ var BasicModel = AbstractModel.extend({
viewType: params.viewType,
});
- return self.applyDefaultValues(record.id, result)
+ return self.applyDefaultValues(record.id, result, {fieldNames: _.union(fieldNames, extraFields)})
.then(function () {
var def = $.Deferred();
self._performOnChange(record, fields_key).always(function () {
@@ -3605,6 +3628,7 @@ var BasicModel = AbstractModel.extend({
parentID: x2manyList.id,
viewType: viewType,
});
+ r._noAbandon = true;
x2manyList._changes.push({operation: 'ADD', id: r.id});
x2manyList._cache[r.res_id] = r.id;
@@ -3634,6 +3658,8 @@ var BasicModel = AbstractModel.extend({
} else if (_.contains(['one2many', 'many2many'], fieldType)) {
var x2mCommands = commands[0][2][fieldName];
defs.push(self._processX2ManyCommands(r, fieldName, x2mCommands));
+ } else {
+ r._changes[fieldName] = self._parseServerValue(field, r._changes[fieldName]);
}
}
}
@@ -3832,6 +3858,16 @@ var BasicModel = AbstractModel.extend({
defs.push(self._load(newGroup, options));
}
});
+ if (options && options.keepEmptyGroups) {
+ // Find the groups that were available in a previous
+ // readGroup but are not there anymore.
+ // Note that these groups are put after existing groups so
+ // the order is not conserved. A sort *might* be useful.
+ var emptyGroupsIDs = _.difference(_.pluck(previousGroups, 'id'), list.data);
+ _.each(emptyGroupsIDs, function (groupID) {
+ list.data.push(groupID);
+ });
+ }
return $.when.apply($, defs).then(function () {
// generate the res_ids of the main list, being the concatenation
// of the fetched res_ids in each group
diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js
index 6a2010708fc..b847d7ca391 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -262,7 +262,10 @@ return AbstractModel.extend({
* @param {Moment} start
*/
setDate: function (start) {
- this.data.start_date = this.data.end_date = this.data.target_date = this.data.highlight_date = start;
+ // keep highlight/target_date in localtime
+ this.data.highlight_date = this.data.target_date = start.clone();
+ // set dates in UTC with timezone applied manually
+ this.data.start_date = this.data.end_date = start;
this.data.start_date.utc().add(this.getSession().getTZOffset(this.data.start_date), 'minutes');
switch (this.data.scale) {
diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js
index 084c9ae4579..e66194dcf99 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -15,7 +15,7 @@ var FormController = BasicController.extend({
bounce_edit: '_onBounceEdit',
button_clicked: '_onButtonClicked',
do_action: '_onDoAction',
- freeze_order: '_onFreezeOrder',
+ edited_list: '_onEditedList',
open_one2many_record: '_onOpenOne2ManyRecord',
open_record: '_onOpenRecord',
toggle_column_order: '_onToggleColumnOrder',
@@ -109,6 +109,7 @@ var FormController = BasicController.extend({
* @todo convert to new style
*/
on_attach_callback: function () {
+ this._super.apply(this, arguments);
this.autofocus();
},
/**
@@ -529,11 +530,15 @@ var FormController = BasicController.extend({
* in a x2many list view
*
* @private
- * @param {OdooEvent} event
+ * @param {OdooEvent} ev
+ * @param {integer} ev.id of the list to freeze while editing a line
*/
- _onFreezeOrder: function (event) {
- event.stopPropagation();
- this.model.freezeOrder(event.data.id);
+ _onEditedList: function (ev) {
+ ev.stopPropagation();
+ if (ev.data.id) {
+ this.model.save(ev.data.id, {savePoint: true});
+ }
+ this.model.freezeOrder(ev.data.id);
},
/**
* Set the focus on the first primary button of the controller (likely Edit)
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 bc11bb675df..1fbd6572a9a 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -74,10 +74,9 @@ return AbstractRenderer.extend({
* Render the chart.
*
* Note that This method is synchronous, but the actual rendering is done
- * asynchronously (in a setTimeout). The reason for that is that nvd3/d3
- * needs to be in the DOM to correctly render itself. So, we trick Odoo by
- * returning immediately, then wait a tiny interval before actually
- * displaying the data.
+ * asynchronously. The reason for that is that nvd3/d3 needs to be in the
+ * DOM to correctly render itself. So, we trick Odoo by returning
+ * immediately, then we render the chart when the widget is in the DOM.
*
* @override
* @private
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 5f758953658..9be13fb0ae6 100644
--- a/addons/web/static/src/js/views/kanban/kanban_controller.js
+++ b/addons/web/static/src/js/views/kanban/kanban_controller.js
@@ -10,6 +10,7 @@ odoo.define('web.KanbanController', function (require) {
var BasicController = require('web.BasicController');
var Context = require('web.Context');
var core = require('web.core');
+var Domain = require('web.Domain');
var view_dialogs = require('web.view_dialogs');
var _t = core._t;
@@ -190,10 +191,57 @@ var KanbanController = BasicController.extend({
resIDs: record.res_ids,
},
on_closed: function () {
+ var recordModel = self.model.localData[record.id];
+ var group = self.model.localData[recordModel.parentID];
+ var parent = self.model.localData[group.parentID];
+
self.model.reload(record.id).then(function (db_id) {
var data = self.model.get(db_id);
var kanban_record = event.target;
kanban_record.update(data);
+
+ // Check if we still need to display the record. Some fields of the domain are
+ // not guaranteed to be in data. This is for example the case if the action
+ // contains a domain on a field which is not in the Kanban view. Therefore,
+ // we need to handle multiple cases based on 3 variables:
+ // domInData: all domain fields are in the data
+ // activeInDomain: 'active' is already in the domain
+ // activeInData: 'active' is available in the data
+
+ var domain = (parent ? parent.domain : group.domain) || [];
+ var domInData = _.every(domain, function (d) {
+ return d[0] in data.data;
+ });
+ var activeInDomain = _.pluck(domain, 0).indexOf('active') !== -1;
+ var activeInData = 'active' in data.data;
+
+ // Case # | domInData | activeInDomain | activeInData
+ // 1 | true | true | true => no domain change
+ // 2 | true | true | false => not possible
+ // 3 | true | false | true => add active in domain
+ // 4 | true | false | false => no domain change
+ // 5 | false | true | true => no evaluation
+ // 6 | false | true | false => no evaluation
+ // 7 | false | false | true => replace domain
+ // 8 | false | false | false => no evaluation
+
+ // There are 3 cases which cannot be evaluated since we don't have all the
+ // necessary information. The complete solution would be to perform a RPC in
+ // these cases, but this is out of scope. A simpler one is to do a try / catch.
+
+ if (domInData && !activeInDomain && activeInData) {
+ domain = domain.concat([['active', '=', true]]);
+ } else if (!domInData && !activeInDomain && activeInData) {
+ domain = [['active', '=', true]];
+ }
+ try {
+ var visible = new Domain(domain).compute(data.evalContext);
+ } catch (e) {
+ return;
+ }
+ if (!visible) {
+ kanban_record.destroy();
+ }
});
},
});
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 15d6284cfad..411d4cc7b14 100644
--- a/addons/web/static/src/js/views/kanban/kanban_model.js
+++ b/addons/web/static/src/js/views/kanban/kanban_model.js
@@ -419,7 +419,10 @@ var KanbanModel = BasicModel.extend({
while (element) {
if (element.progressBar) {
return def.then(function (data) {
- return self._load(element, {onlyGroups: true}).then(function () {
+ return self._load(element, {
+ keepEmptyGroups: true,
+ onlyGroups: true,
+ }).then(function () {
return data;
});
});
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 8d0b04332ad..8f87b36c874 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -79,8 +79,15 @@ var DateWidget = Widget.extend({
var oldValue = this.getValue();
this._setValueFromUi();
var newValue = this.getValue();
-
- if (!oldValue !== !newValue || oldValue && newValue && !oldValue.isSame(newValue)) {
+ var hasChanged = !oldValue !== !newValue;
+ if (oldValue && newValue) {
+ var formattedOldValue = oldValue.format(time.getLangDatetimeFormat());
+ var formattedNewValue = newValue.format(time.getLangDatetimeFormat())
+ if (formattedNewValue !== formattedOldValue) {
+ hasChanged = true;
+ }
+ }
+ if (hasChanged) {
// The condition is strangely written; this is because the
// values can be false/undefined
this.trigger("datetime_changed");
diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js
index ffe65a7efcf..4b86b76c36d 100644
--- a/addons/web/static/src/js/widgets/debug_manager.js
+++ b/addons/web/static/src/js/widgets/debug_manager.js
@@ -178,6 +178,7 @@ var DebugManager = Widget.extend({
})
.then(function (views) {
var view = views[0];
+ view.type = view.type === 'tree' ? 'list' : view.type; // ignore tree view
self.do_action({
type: 'ir.actions.act_window',
name: view.name,
diff --git a/addons/web/static/src/less/bootstrap_overridden.less b/addons/web/static/src/less/bootstrap_overridden.less
index 012efa60cda..5bbe61ecdf6 100644
--- a/addons/web/static/src/less/bootstrap_overridden.less
+++ b/addons/web/static/src/less/bootstrap_overridden.less
@@ -164,3 +164,5 @@
overflow-x: initial;
}
}
+
+@icon-font-path: "/web/static/lib/bootstrap/fonts/";
diff --git a/addons/web/static/src/less/datepicker.less b/addons/web/static/src/less/datepicker.less
index 99c909efacc..fdf3a585b57 100644
--- a/addons/web/static/src/less/datepicker.less
+++ b/addons/web/static/src/less/datepicker.less
@@ -17,11 +17,12 @@
.o_datepicker_input {
width: 100%;
+ cursor: pointer;
}
.o_datepicker_button {
.o-position-absolute(2px, 4px);
- cursor: pointer;
+ pointer-events: none; // let click events go through the underlying input
&:after {
.o-caret-down;
}
diff --git a/addons/web/static/src/less/fields.less b/addons/web/static/src/less/fields.less
index ee9f1d9556a..ff6fe9eb20b 100644
--- a/addons/web/static/src/less/fields.less
+++ b/addons/web/static/src/less/fields.less
@@ -185,7 +185,7 @@
display: inline-block;
padding: 0;
margin: 0;
- vertical-align: middle;
+ vertical-align: baseline;
> .o_priority_star {
display: inline-block;
font-size: 1.35em;
diff --git a/addons/web/static/src/less/form_view.less b/addons/web/static/src/less/form_view.less
index 96e8bd2ac97..c36fe2d0d9a 100644
--- a/addons/web/static/src/less/form_view.less
+++ b/addons/web/static/src/less/form_view.less
@@ -249,6 +249,10 @@
border-left-color: @gray-lighter;
}
}
+
+ .caret {
+ margin-top: -2px;
+ }
}
.o-status-more {
diff --git a/addons/web/static/src/less/kanban_column_progressbar.less b/addons/web/static/src/less/kanban_column_progressbar.less
index 86fe0853de2..f4cffd6c097 100644
--- a/addons/web/static/src/less/kanban_column_progressbar.less
+++ b/addons/web/static/src/less/kanban_column_progressbar.less
@@ -91,6 +91,7 @@
margin-left: 3%;
color: @headings-color;
text-align: right;
+ white-space: nowrap;
.o-transform-origin(right, center);
&.o_kanban_grow {
diff --git a/addons/web/static/src/less/kanban_dashboard.less b/addons/web/static/src/less/kanban_dashboard.less
index da8f0adac72..83db9319166 100644
--- a/addons/web/static/src/less/kanban_dashboard.less
+++ b/addons/web/static/src/less/kanban_dashboard.less
@@ -231,6 +231,7 @@
}
.o_favorite {
+ top: 3px;
left: 0;
right: auto;
}
diff --git a/addons/web/static/src/less/list_view.less b/addons/web/static/src/less/list_view.less
index ddc530e540a..b5237d305cb 100644
--- a/addons/web/static/src/less/list_view.less
+++ b/addons/web/static/src/less/list_view.less
@@ -37,12 +37,14 @@
.user-select(none); // Prevent unwanted selection while sorting
&::after {
- margin-left: 6px;
- position: absolute;
font-family: FontAwesome;
content: "\f0d7";
opacity: 0;
}
+ &:not(:empty)::after {
+ margin-left: 6px;
+ position: absolute;
+ }
&.o-sort-up {
cursor: n-resize;
&::after {