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/keyboard_navigation_mixin.js16
-rw-r--r--addons/web/static/src/js/chrome/menu.js75
-rw-r--r--addons/web/static/src/js/chrome/web_client.js4
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js131
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js4
-rw-r--r--addons/web/static/src/js/services/notification_service.js5
-rw-r--r--addons/web/static/src/js/views/abstract_view.js52
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js72
-rw-r--r--addons/web/static/src/js/views/basic/basic_view.js6
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_renderer.js4
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js5
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js28
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_column.js3
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js13
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_model.js6
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_record.js6
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer.js6
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_view.js10
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js4
-rw-r--r--addons/web/static/src/scss/bootstrap_review.scss10
-rw-r--r--addons/web/static/src/scss/form_view.scss4
-rw-r--r--addons/web/static/src/scss/form_view_extra.scss7
-rw-r--r--addons/web/static/src/scss/layout_background.scss7
-rw-r--r--addons/web/static/src/scss/list_view.scss12
-rw-r--r--addons/web/static/src/xml/base.xml2
25 files changed, 337 insertions, 155 deletions
diff --git a/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js b/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
index dc65d2ea610..4913d5a6a80 100644
--- a/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
+++ b/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
@@ -45,13 +45,21 @@ odoo.define('web.KeyboardNavigationMixin', function (require) {
var accesskeyElements = $(document).find('[accesskey]').filter(':visible');
_.each(accesskeyElements, function (elem) {
var overlay = $(_.str.sprintf("<div class='o_web_accesskey_overlay'>%s</div>", $(elem).attr('accesskey').toUpperCase()));
+
+ var $overlayParent;
if (elem.tagName.toUpperCase() === "INPUT") {
- // special case for the search input that has an access key defined. We cannot set the overlay on the input itself, only on its parent
- overlay.appendTo($(elem).parent().css('position', 'relative'));
+ // special case for the search input that has an access key
+ // defined. We cannot set the overlay on the input itself,
+ // only on its parent.
+ $overlayParent = $(elem).parent();
+ } else {
+ $overlayParent = $(elem);
}
- else {
- overlay.appendTo($(elem).css('position', 'relative'));
+
+ if ($overlayParent.css('position') !== 'absolute') {
+ $overlayParent.css('position', 'relative');
}
+ overlay.appendTo($overlayParent);
});
},
/**
diff --git a/addons/web/static/src/js/chrome/menu.js b/addons/web/static/src/js/chrome/menu.js
index 7f60e2ea44f..8722724c9a6 100644
--- a/addons/web/static/src/js/chrome/menu.js
+++ b/addons/web/static/src/js/chrome/menu.js
@@ -38,7 +38,7 @@ var Menu = Widget.extend({
});
// Bus event
- core.bus.on('change_menu_section', this, this._onChangeMenuSection);
+ core.bus.on('change_menu_section', this, this.change_menu_section);
},
start: function () {
var self = this;
@@ -79,6 +79,33 @@ var Menu = Widget.extend({
return this._super.apply(this, arguments);
},
+ change_menu_section: function (primary_menu_id) {
+ if (!this.$menu_sections[primary_menu_id]) {
+ this._updateMenuBrand();
+ return; // unknown menu_id
+ }
+
+ if (this.current_primary_menu === primary_menu_id) {
+ return; // already in that menu
+ }
+
+ if (this.current_primary_menu) {
+ this.$menu_sections[this.current_primary_menu].detach();
+ }
+
+ // Get back the application name
+ for (var i = 0; i < this.menu_data.children.length; i++) {
+ if (this.menu_data.children[i].id === primary_menu_id) {
+ this._updateMenuBrand(this.menu_data.children[i].name);
+ break;
+ }
+ }
+
+ this.$menu_sections[primary_menu_id].appendTo(this.$section_placeholder);
+ this.current_primary_menu = primary_menu_id;
+
+ core.bus.trigger('resize');
+ },
_trigger_menu_clicked: function (menu_id, action_id) {
this.trigger_up('menu_clicked', {
id: menu_id,
@@ -86,6 +113,22 @@ var Menu = Widget.extend({
previous_menu_id: this.current_secondary_menu || this.current_primary_menu,
});
},
+ /**
+ * Updates the name of the app in the menu to the value of brandName.
+ * If brandName is falsy, hides the menu and its sections.
+ *
+ * @private
+ * @param {brandName} string
+ */
+ _updateMenuBrand: function (brandName) {
+ if (brandName) {
+ this.$menu_brand_placeholder.text(brandName).show();
+ this.$section_placeholder.show();
+ } else {
+ this.$menu_brand_placeholder.hide()
+ this.$section_placeholder.hide();
+ }
+ },
_on_secondary_menu_click: function (menu_id, action_id) {
var self = this;
@@ -172,36 +215,6 @@ var Menu = Widget.extend({
},
/**
* @private
- * @param {integer} primaryMenuID
- */
- _onChangeMenuSection: function (primaryMenuID) {
- if (!this.$menu_sections[primaryMenuID]) {
- return; // unknown menu_id
- }
-
- if (this.current_primary_menu === primaryMenuID) {
- return; // already in that menu
- }
-
- if (this.current_primary_menu) {
- this.$menu_sections[this.current_primary_menu].detach();
- }
-
- // Get back the application name
- for (var i = 0; i < this.menu_data.children.length; i++) {
- if (this.menu_data.children[i].id === primaryMenuID) {
- this.$menu_brand_placeholder.text(this.menu_data.children[i].name);
- break;
- }
- }
-
- this.$menu_sections[primaryMenuID].appendTo(this.$section_placeholder);
- this.current_primary_menu = primaryMenuID;
-
- core.bus.trigger('resize');
- },
- /**
- * @private
* @param {MouseEvent} ev
*/
_onMouseOverMenu: function (ev) {
diff --git a/addons/web/static/src/js/chrome/web_client.js b/addons/web/static/src/js/chrome/web_client.js
index 8b725423d0f..0688adc362f 100644
--- a/addons/web/static/src/js/chrome/web_client.js
+++ b/addons/web/static/src/js/chrome/web_client.js
@@ -151,9 +151,7 @@ return AbstractWebClient.extend({
var action = self.action_manager.getCurrentAction();
if (action) {
var menu_id = self.menu.action_id_to_primary_menu_id(action.id);
- if (menu_id) {
- core.bus.trigger('change_menu_section', menu_id);
- }
+ core.bus.trigger('change_menu_section', menu_id);
}
}
});
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 12b7b8f302c..b324d6f2efa 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -500,6 +500,10 @@ var FieldDate = InputField.extend({
this._super.apply(this, arguments);
// use the session timezone when formatting dates
this.formatOptions.timezone = true;
+ this.datepickerOptions = _.defaults(
+ this.nodeOptions.datepicker || {},
+ {defaultDate: this.value}
+ );
},
/**
* In edit mode, instantiates a DateWidget datepicker and listen to changes.
@@ -583,13 +587,7 @@ var FieldDate = InputField.extend({
* @private
*/
_makeDatePicker: function () {
- return new datepicker.DateWidget(
- this,
- _.defaults(
- this.nodeOptions.datepicker || {},
- {defaultDate: this.value}
- )
- );
+ return new datepicker.DateWidget(this, this.datepickerOptions);
},
/**
@@ -607,6 +605,17 @@ var FieldDate = InputField.extend({
var FieldDateTime = FieldDate.extend({
supportedFieldTypes: ['datetime'],
+ /**
+ * @override
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ if (this.value) {
+ var offset = this.getSession().getTZOffset(this.value);
+ var displayedValue = this.value.clone().add(offset, 'minutes');
+ this.datepickerOptions.defaultDate = displayedValue;
+ }
+ },
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -637,10 +646,8 @@ var FieldDateTime = FieldDate.extend({
* @private
*/
_makeDatePicker: function () {
- var value = this.value && this.value.clone().add(this.getSession().getTZOffset(this.value), 'minutes');
- return new datepicker.DateTimeWidget(this, {defaultDate: value});
+ return new datepicker.DateTimeWidget(this, this.datepickerOptions);
},
-
/**
* Set the datepicker to the right value rather than the default one.
*
@@ -1171,6 +1178,9 @@ var HandleWidget = AbstractField.extend({
var FieldEmail = InputField.extend({
className: 'o_field_email',
+ events: _.extend({}, InputField.prototype.events, {
+ 'click': '_onClick',
+ }),
prefix: 'mailto',
supportedFieldTypes: ['char'],
@@ -1211,7 +1221,21 @@ var FieldEmail = InputField.extend({
this.$el.text(this.value)
.addClass('o_form_uri o_text_overflow')
.attr('href', this.prefix + ':' + this.value);
- }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Prevent the URL click from opening the record (when used on a list).
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClick: function (ev) {
+ ev.stopPropagation();
+ },
});
var FieldPhone = FieldEmail.extend({
@@ -1238,6 +1262,9 @@ var FieldPhone = FieldEmail.extend({
var UrlWidget = InputField.extend({
className: 'o_field_url',
+ events: _.extend({}, InputField.prototype.events, {
+ 'click': '_onClick',
+ }),
supportedFieldTypes: ['char'],
/**
@@ -1279,7 +1306,21 @@ var UrlWidget = InputField.extend({
.addClass('o_form_uri o_text_overflow')
.attr('target', '_blank')
.attr('href', this.value);
- }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Prevent the URL click from opening the record (when used on a list).
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClick: function (ev) {
+ ev.stopPropagation();
+ },
});
var CopyClipboard = {
@@ -1363,7 +1404,7 @@ var AbstractFieldBinary = AbstractField.extend({
'click .o_select_file_button': function () {
this.$('.o_input_file').click();
},
- 'click .o_clear_file_button': 'on_clear',
+ 'click .o_clear_file_button': '_onClearClick',
}),
init: function (parent, name, record) {
this._super.apply(this, arguments);
@@ -1449,11 +1490,33 @@ var AbstractFieldBinary = AbstractField.extend({
});
}
},
- on_clear: function () {
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+ /**
+ * Clear the file input
+ *
+ * @private
+ */
+ _clearFile: function (){
this.set_filename('');
this._setValue(false);
this._render();
},
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ /**
+ * On "clear file" button click
+ *
+ * @param {MouseEvent} ev
+ * @private
+ */
+ _onClearClick: function (ev) {
+ this._clearFile();
+ },
});
var FieldBinaryImage = AbstractFieldBinary.extend({
@@ -1510,7 +1573,7 @@ var FieldBinaryImage = AbstractFieldBinary.extend({
this.$('> img').remove();
this.$el.prepend($img);
$img.on('error', function () {
- self.on_clear();
+ self._clearFile();
$img.attr('src', self.placeholder);
self.do_warn(_t("Image"), _t("Could not display the selected image."));
});
@@ -1623,19 +1686,23 @@ var FieldPdfViewer = FieldBinaryFile.extend({
},
/**
* @private
+ * @param {string} [fileURI] file URI if specified
* @returns {string} the pdf viewer URI
*/
- _getURI: function () {
- var queryObj = {
- model: this.model,
- field: this.name,
- id: this.res_id,
- };
+ _getURI: function (fileURI) {
var page = this.recordData[this.name + '_page'] || 1;
- var queryString = $.param(queryObj);
- var url = encodeURIComponent('/web/image?' + queryString);
+ if (!fileURI) {
+ var queryObj = {
+ model: this.model,
+ field: this.name,
+ id: this.res_id,
+ };
+ var queryString = $.param(queryObj);
+ fileURI = '/web/image?' + queryString
+ }
+ fileURI = encodeURIComponent(fileURI);
var viewerURL = '/web/static/lib/pdfjs/web/viewer.html?file=';
- return viewerURL + url + '#page=' + page;
+ return viewerURL + fileURI + '#page=' + page;
},
/**
* @private
@@ -1679,14 +1746,16 @@ var FieldPdfViewer = FieldBinaryFile.extend({
*/
on_file_change: function (ev) {
this._super.apply(this, arguments);
+ var files = ev.target.files;
+ if (!files || files.length === 0) {
+ return;
+ }
+ // TOCheck: is there requirement to fallback on FileReader if browser don't support URL
+ var fileURI = URL.createObjectURL(files[0]);
if (this.PDFViewerApplication) {
- var files = ev.target.files;
- if (!files || files.length === 0) {
- return;
- }
- var file = files[0];
- // TOCheck: is there requirement to fallback on FileReader if browser don't support URL
- this.PDFViewerApplication.open(URL.createObjectURL(file), 0);
+ this.PDFViewerApplication.open(fileURI, 0);
+ } else {
+ this.$('.o_pdfview_iframe').attr('src', this._getURI(fileURI));
}
},
/**
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index e27128a1e3b..bf46c538c23 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -2584,10 +2584,12 @@ var FieldSelectionBadge = FieldSelection.extend({
* a FieldMany2one for its value.
* Its intern representation is similar to the many2one (a datapoint with a
* `name_get` as data).
+ * Note that there is some logic to support char field because of one use in our
+ * codebase, but this use should be removed along with this note.
*/
var FieldReference = FieldMany2One.extend({
specialData: "_fetchSpecialReference",
- supportedFieldTypes: ['char', 'reference'],
+ supportedFieldTypes: ['reference'],
template: 'FieldReference',
events: _.extend({}, FieldMany2One.prototype.events, {
'change select': '_onSelectionChange',
diff --git a/addons/web/static/src/js/services/notification_service.js b/addons/web/static/src/js/services/notification_service.js
index cc87305a6a2..a0d2720604b 100644
--- a/addons/web/static/src/js/services/notification_service.js
+++ b/addons/web/static/src/js/services/notification_service.js
@@ -58,6 +58,8 @@ var NotificationService = AbstractService.extend({
* Note that this method does not wait for the appendTo method to complete.
*
* @param {Object} params
+ * @param {function} [params.Notification] javascript class of a notification
+ * to instantiate by default use 'web.Notification'
* @param {string} params.title notification title
* @param {string} params.message notification main message
* @param {string} params.type 'notification' or 'warning'
@@ -78,7 +80,8 @@ var NotificationService = AbstractService.extend({
this.$el = $('<div class="o_notification_manager"/>');
this.$el.prependTo('body');
}
- var notification = this.notifications[++id] = new Notification(this, params);
+ var NotificationWidget = params.Notification || Notification;
+ var notification = this.notifications[++id] = new NotificationWidget(this, params);
notification.appendTo(this.$el);
return id;
},
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index 7705ee5972e..f3277d394a3 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -308,3 +308,55 @@ var AbstractView = Class.extend({
return AbstractView;
});
+
+odoo.define('web.viewUtils', function () {
+"use strict";
+
+/**
+ * FIXME: move this module to its own file in master
+ */
+
+var utils = {
+ /**
+ * Returns the value of a group dataPoint, i.e. the value of the groupBy
+ * field for the records in that group.
+ *
+ * @param {Object} group dataPoint of type list, corresponding to a group
+ * @param {string} groupByField the name of the groupBy field
+ * @returns {string | integer | false}
+ */
+ getGroupValue: function (group, groupByField) {
+ var groupedByField = group.fields[groupByField];
+ switch (groupedByField.type) {
+ case 'many2one':
+ return group.res_id || false;
+ case 'selection':
+ var descriptor = _.findWhere(groupedByField.selection, group.value);
+ return descriptor && descriptor[0];
+ default:
+ return group.value;
+ }
+ },
+ /**
+ * States whether or not the quick create feature is available for the given
+ * datapoint, depending on its groupBy field.
+ *
+ * @param {Object} list dataPoint of type list
+ * @returns {Boolean} true iff the kanban quick create feature is available
+ */
+ isQuickCreateEnabled: function (list) {
+ var groupByField = list.groupedBy[0] && list.groupedBy[0].split(':')[0];
+ if (!groupByField) {
+ return false;
+ }
+ var availableTypes = ['char', 'boolean', 'many2one', 'selection'];
+ if (!_.contains(availableTypes, list.fields[groupByField].type)) {
+ return false;
+ }
+ return true;
+ },
+};
+
+return utils;
+
+});
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 f6bf46aed68..de879018ad5 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -398,8 +398,14 @@ var BasicModel = AbstractModel.extend({
// by 1 (at least until there is a gap in the numbering).
// We don't do it now because it's not an important case.
+ // However, we can for sure increment by 1 if we are on the last page.
+
var handleFieldValue = computedList.data[index].data[handleField];
- handleFieldValue = position !== 'top' ? handleFieldValue : handleFieldValue - 1;
+ if (position === 'top') {
+ handleFieldValue--;
+ } else if (list.count <= list.offset + list.limit - (list.tempLimitIncrement || 0)) {
+ handleFieldValue++;
+ }
return {
field: handleField,
@@ -1010,27 +1016,51 @@ var BasicModel = AbstractModel.extend({
route: '/web/dataset/resequence',
params: params,
})
- .then(function () {
- var offset = options.offset ? options.offset : 0;
- var old_data = data.data.slice();
- data.data = _.sortBy(data.data, function (d) {
- if (_.contains(resIDs, self.localData[d].res_id)) {
- return _.indexOf(resIDs, self.localData[d].res_id) + offset;
- } else {
- return _.indexOf(old_data, d);
- }
- });
- data.res_ids = [];
- _.each(data.data, function (d) {
- var dataPoint = self.localData[d];
- if (dataPoint.type === 'record') {
- data.res_ids.push(dataPoint.res_id);
- } else {
- data.res_ids = data.res_ids.concat(dataPoint.res_ids);
+ .then(function (wasResequenced) {
+ if (!wasResequenced) {
+ // the field on which the resequence was triggered does not
+ // exist, so no resequence happened server-side
+ return $.when();
+ }
+ var field = params.field ? params.field : 'sequence';
+
+ return self._rpc({
+ model: modelName,
+ method: 'read',
+ args: [resIDs, [field]],
+ }).then(function (records) {
+ if (data.data.length) {
+ var dataType = self.localData[data.data[0]].type;
+ if (dataType === 'record') {
+ _.each(data.data, function (dataPoint) {
+ var recordData = self.localData[dataPoint].data;
+ var inRecords = _.findWhere(records, {id: recordData.id});
+ if (inRecords) {
+ recordData[field] = inRecords[field];
+ }
+ });
+ data.data = _.sortBy(data.data, function (d) {
+ return self.localData[d].data[field];
+ });
+ }
+ if (dataType === 'list') {
+ data.data = _.sortBy(data.data, function (d) {
+ return _.indexOf(resIDs, self.localData[d].res_id)
+ });
+ }
}
- });
- self._updateParentResIDs(data);
- return parentID;
+ data.res_ids = [];
+ _.each(data.data, function (d) {
+ var dataPoint = self.localData[d];
+ if (dataPoint.type === 'record') {
+ data.res_ids.push(dataPoint.res_id);
+ } else {
+ data.res_ids = data.res_ids.concat(dataPoint.res_ids);
+ }
+ });
+ self._updateParentResIDs(data);
+ return parentID;
+ })
});
},
/**
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 7bc5e95bfc8..9e1cde2aada 100644
--- a/addons/web/static/src/js/views/basic/basic_view.js
+++ b/addons/web/static/src/js/views/basic/basic_view.js
@@ -225,7 +225,7 @@ var BasicView = AbstractView.extend({
attrs.options = attrs.options ? pyUtils.py_eval(attrs.options) : {};
}
- if (attrs.on_change && !field.onChange) {
+ if (attrs.on_change && attrs.on_change !== "0" && !field.onChange) {
field.onChange = "1";
}
@@ -378,6 +378,10 @@ var BasicView = AbstractView.extend({
if (!(dependency_name in fields)) {
fields[dependency_name] = dependency_dict;
}
+
+ if (fv.fields && !(dependency_name in fv.fields)) {
+ fv.fields[dependency_name] = dependency_dict;
+ }
}
}
return false;
diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js
index e76a487b73b..c2c90ea7e62 100644
--- a/addons/web/static/src/js/views/calendar/calendar_renderer.js
+++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js
@@ -77,9 +77,9 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, {
self.model.get(recordID),
{
mode: 'edit',
- can_create: false,
attrs: {
- 'placeholder': _.str.sprintf(_t("Add %s"), self.title),
+ placeholder: _.str.sprintf(_t("Add %s"), self.title),
+ can_create: false
},
});
});
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index 190fbe88ad2..c7a32b72ba9 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -995,10 +995,7 @@ var FormRenderer = BasicRenderer.extend({
* @param {OdooEvent} ev
*/
_onNavigationMove: function (ev) {
- if (ev.data.direction !== "cancel") {
- ev.stopPropagation();
- }
-
+ ev.stopPropagation();
var index;
if (ev.data.direction === "next") {
index = this.allFieldWidgets[this.state.id].indexOf(ev.data.target || ev.target);
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 f923ade149b..0517e14c876 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -341,7 +341,6 @@ return AbstractRenderer.extend({
var data = [];
var ticksLabels = [];
- var tickValues = [];
var measure = this.state.fields[this.state.measure].string;
var values;
@@ -363,18 +362,6 @@ return AbstractRenderer.extend({
key: measure + ' (compare)',
color: '#ff7f0e',
});
-
- if (this.state.comparisonData.length > graphData.length) {
- tickValues = this.state.comparisonData.map(function (d, i) {
- return i;
- });
- }
- }
-
- if (!tickValues.length) {
- tickValues = graphData.map(function (d, i) {
- return i;
- });
}
for (i = 0; i < graphData.length; i++) {
@@ -403,7 +390,6 @@ return AbstractRenderer.extend({
for (var i = 0; i < this.state.data.length; i++) {
if (graphData[i].labels[0] !== tickLabel) {
tickLabel = this.state.data[i].labels[0];
- tickValues.push(tick);
ticksLabels.push(tickLabel);
tick++;
}
@@ -438,7 +424,6 @@ return AbstractRenderer.extend({
});
chart.forceY([0]);
chart.xAxis
- .tickValues(tickValues)
.tickFormat(function (d) {
return ticksLabels[d];
});
@@ -491,19 +476,6 @@ return AbstractRenderer.extend({
$('.nv-y .tick > line').attr('x2', function (i, value) {
return Math.abs(value);
});
-
- // We don't need to show all labels
- $('.o_graph_svg_container svg .nv-x g.tick > text').show();
- var $ticksText = $('svg .nv-x g.tick:not(.zero) > text');
- var ticksLength = $ticksText.length;
- var tickTextMargin = 5;
- if (ticksLength) {
- var tickWidth = $ticksText[0].getBBox().width + tickTextMargin;
- var svgWidth = $('.o_graph_svg_container').width();
- var keepOneOf = Math.ceil(ticksLength / (svgWidth / tickWidth));
- // FIXME: should work with two line charts
- $('.o_graph_svg_container svg .nv-x g.tick:not(:nth-child(' + keepOneOf + 'n+1)) > text').hide();
- }
})
chartResize(chart);
diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js
index ef75caca8c0..ffb63ad9083 100644
--- a/addons/web/static/src/js/views/kanban/kanban_column.js
+++ b/addons/web/static/src/js/views/kanban/kanban_column.js
@@ -7,6 +7,7 @@ var Dialog = require('web.Dialog');
var KanbanRecord = require('web.KanbanRecord');
var RecordQuickCreate = require('web.kanban_record_quick_create');
var view_dialogs = require('web.view_dialogs');
+var viewUtils = require('web.viewUtils');
var Widget = require('web.Widget');
var KanbanColumnProgressBar = require('web.KanbanColumnProgressBar');
@@ -201,7 +202,7 @@ var KanbanColumn = Widget.extend({
this.trigger_up('close_quick_create'); // close other quick create widgets
this.trigger_up('start_quick_create');
var context = this.data.getContext();
- context['default_' + this.groupedBy] = this.id;
+ context['default_' + this.groupedBy] = viewUtils.getGroupValue(this.data, this.groupedBy);
this.quickCreateWidget = new RecordQuickCreate(this, {
context: context,
formViewRef: this.quickCreateView,
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 9599d29db26..e195b33068c 100644
--- a/addons/web/static/src/js/views/kanban/kanban_controller.js
+++ b/addons/web/static/src/js/views/kanban/kanban_controller.js
@@ -12,6 +12,7 @@ var Context = require('web.Context');
var core = require('web.core');
var Domain = require('web.Domain');
var view_dialogs = require('web.view_dialogs');
+var viewUtils = require('web.viewUtils');
var _t = core._t;
var qweb = core.qweb;
@@ -35,12 +36,15 @@ var KanbanController = BasicController.extend({
/**
* @override
* @param {Object} params
+ * @param {boolean} params.quickCreateEnabled set to false to disable the
+ * quick create feature
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
this.on_create = params.on_create;
this.hasButtons = params.hasButtons;
+ this.quickCreateEnabled = params.quickCreateEnabled;
},
//--------------------------------------------------------------------------
@@ -268,8 +272,8 @@ var KanbanController = BasicController.extend({
*/
_onButtonNew: function () {
var state = this.model.get(this.handle, {raw: true});
- var hasColumns = state.groupedBy.length > 0 && state.data.length > 0;
- if (hasColumns && this.on_create === 'quick_create') {
+ var quickCreateEnabled = this.quickCreateEnabled && viewUtils.isQuickCreateEnabled(state);
+ if (this.on_create === 'quick_create' && quickCreateEnabled) {
// Activate the quick create in the first column
this.renderer.addQuickCreate();
} else if (this.on_create && this.on_create !== 'quick_create') {
@@ -396,10 +400,11 @@ var KanbanController = BasicController.extend({
var columnState = self.model.get(column.db_id, {raw: true});
var context = columnState.getContext();
var state = self.model.get(self.handle, {raw: true});
- context['default_' + state.groupedBy[0]] = columnState.res_id;
+ var groupedBy = state.groupedBy[0];
+ context['default_' + groupedBy] = viewUtils.getGroupValue(columnState, groupedBy);
new view_dialogs.FormViewDialog(self, {
res_model: state.model,
- context: _.extend({default_name: name}, context),
+ context: _.extend({default_name: values.name || values.display_name}, context),
title: _t("Create"),
disable_multiple_selection: true,
on_saved: function (record) {
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 905ea05ac1e..fd21ed4df86 100644
--- a/addons/web/static/src/js/views/kanban/kanban_model.js
+++ b/addons/web/static/src/js/views/kanban/kanban_model.js
@@ -7,6 +7,7 @@ odoo.define('web.KanbanModel', function (require) {
*/
var BasicModel = require('web.BasicModel');
+var viewUtils = require('web.viewUtils');
var KanbanModel = BasicModel.extend({
@@ -105,7 +106,8 @@ var KanbanModel = BasicModel.extend({
var group = this.localData[groupID];
var context = this._getContext(group);
var parent = this.localData[group.parentID];
- context['default_' + parent.groupedBy[0]] = group.res_id;
+ var groupedBy = parent.groupedBy;
+ context['default_' + groupedBy] = viewUtils.getGroupValue(group, groupedBy);
var def;
if (Object.keys(values).length === 1 && 'display_name' in values) {
// only 'display_name is given, perform a 'name_create'
@@ -132,7 +134,7 @@ var KanbanModel = BasicModel.extend({
},
/**
* Add the following (kanban specific) keys when performing a `get`:
- *
+ *
* - tooltipData
* - progressBarValues
* - isGroupedByM2ONoColumn
diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js
index 7013464ea87..d9d7ac77fe2 100644
--- a/addons/web/static/src/js/views/kanban/kanban_record.js
+++ b/addons/web/static/src/js/views/kanban/kanban_record.js
@@ -509,10 +509,12 @@ var KanbanRecord = Widget.extend({
ischild = false;
}
var test_event = events && events.click && (events.click.length > 1 || events.click[0].namespace !== 'bs.tooltip');
+ var testLinkWithHref = elem.nodeName.toLowerCase() === 'a' && elem.href;
if (ischild) {
children.push(elem);
- if (test_event) {
- // do not trigger global click if one child has a click event registered
+ if (test_event || testLinkWithHref) {
+ // Do not trigger global click if one child has a click
+ // event registered (or it is a link with href)
trigger = false;
}
}
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 c51ac945de1..9356311d51c 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js
@@ -9,6 +9,7 @@ var ColumnQuickCreate = require('web.kanban_column_quick_create');
var QWeb = require('web.QWeb');
var session = require('web.session');
var utils = require('web.utils');
+var viewUtils = require('web.viewUtils');
var qweb = core.qweb;
@@ -98,6 +99,9 @@ var KanbanRenderer = BasicRenderer.extend({
/**
* @override
+ * @param {Object} params
+ * @param {boolean} params.quickCreateEnabled set to false to disable the
+ * quick create feature
*/
init: function (parent, state, params) {
this._super.apply(this, arguments);
@@ -116,6 +120,7 @@ var KanbanRenderer = BasicRenderer.extend({
if (this.columnOptions.hasProgressBar) {
this.columnOptions.progressBarStates = {};
}
+ this.quickCreateEnabled = params.quickCreateEnabled;
this._setState(state);
},
/**
@@ -453,6 +458,7 @@ var KanbanRenderer = BasicRenderer.extend({
groupedBy: groupByField,
grouped_by_m2o: this.groupedByM2O,
relation: relation,
+ quick_create: this.quickCreateEnabled && viewUtils.isQuickCreateEnabled(state),
});
this.createColumnEnabled = this.groupedByM2O && this.columnOptions.group_creatable;
},
diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js
index 791e3664668..523f0ef1c57 100644
--- a/addons/web/static/src/js/views/kanban/kanban_view.js
+++ b/addons/web/static/src/js/views/kanban/kanban_view.js
@@ -63,7 +63,6 @@ var KanbanView = BasicView.extend({
deletable: activeActions.group_delete,
archivable: this.arch.attrs.archivable ? JSON.parse(this.arch.attrs.archivable) : true,
group_creatable: activeActions.group_create && !config.device.isMobile,
- quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(),
quickCreateView: this.arch.attrs.quick_create_view || null,
hasProgressBar: !!progressBar,
};
@@ -72,6 +71,7 @@ var KanbanView = BasicView.extend({
deletable: activeActions.delete,
read_only_mode: params.readOnlyMode,
};
+ this.rendererParams.quickCreateEnabled = this._isQuickCreateEnabled();
var examples = this.arch.attrs.examples;
if (examples) {
this.rendererParams.examples = kanbanExamplesRegistry.get(examples);
@@ -80,6 +80,7 @@ var KanbanView = BasicView.extend({
this.controllerParams.on_create = this.arch.attrs.on_create;
this.controllerParams.readOnlyMode = false;
this.controllerParams.hasButtons = true;
+ this.controllerParams.quickCreateEnabled = this.rendererParams.quickCreateEnabled;
if (config.device.isMobile) {
this.jsLibs.push('/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js');
@@ -93,13 +94,10 @@ var KanbanView = BasicView.extend({
/**
* @private
* @param {Object} viewInfo
+ * @returns {boolean} true iff the quick create feature is not explicitely
+ * disabled (with create="False" or quick_create="False" in the arch)
*/
_isQuickCreateEnabled: function () {
- var groupBy = this.loadParams.groupBy[0];
- groupBy = groupBy !== undefined ? groupBy.split(':')[0] : undefined;
- if (groupBy !== undefined && !_.contains(['char', 'boolean', 'many2one'], this.fields[groupBy].type)) {
- return false;
- }
if (!this.controllerParams.activeActions.create) {
return false;
}
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 109a5c958c4..7ba3010a3b5 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -93,8 +93,6 @@ var DateWidget = Widget.extend({
}
}
if (hasChanged) {
- // The condition is strangely written; this is because the
- // values can be false/undefined
if (this.options.warn_future) {
this._warnFuture(newValue);
}
@@ -182,7 +180,7 @@ var DateWidget = Widget.extend({
this.$warning.attr('title', title);
this.$input.after(this.$warning);
}
- if (currentDate.isAfter(moment())) {
+ if (currentDate && currentDate.isAfter(moment())) {
this.$warning.show();
} else {
this.$warning.hide();
diff --git a/addons/web/static/src/scss/bootstrap_review.scss b/addons/web/static/src/scss/bootstrap_review.scss
new file mode 100644
index 00000000000..4efc59da2cf
--- /dev/null
+++ b/addons/web/static/src/scss/bootstrap_review.scss
@@ -0,0 +1,10 @@
+///
+/// This file regroups the CSS rules made to fix/extend bootstrap in all places
+/// where it is used in Odoo (backend / frontend / reports / ...)
+///
+
+.alert {
+ // Alerts are block elements with relative positioning.
+ // They would go over floating elements, which is never what we want.
+ clear: both;
+}
diff --git a/addons/web/static/src/scss/form_view.scss b/addons/web/static/src/scss/form_view.scss
index 0ebd5044806..6171762ed1f 100644
--- a/addons/web/static/src/scss/form_view.scss
+++ b/addons/web/static/src/scss/form_view.scss
@@ -207,7 +207,7 @@
border-left: 1px solid gray('300');
}
- &:before, &:not(:first-child):after {
+ &:not(:first-child):before, &:not(:first-child):after {
content: " ";
display: block;
@include o-position-absolute(0, -$o-statusbar-arrow-width + 1);
@@ -231,7 +231,7 @@
}
}
- &:before {
+ &:not(:first-child):before {
right: -$o-statusbar-arrow-width;
border-left-color: gray('300');
}
diff --git a/addons/web/static/src/scss/form_view_extra.scss b/addons/web/static/src/scss/form_view_extra.scss
index ad7e8600cb3..277537beab4 100644
--- a/addons/web/static/src/scss/form_view_extra.scss
+++ b/addons/web/static/src/scss/form_view_extra.scss
@@ -33,6 +33,7 @@
width: 400px;
text-align: right;
float: right;
+ margin-bottom: 4px;
&, & + .oe_avatar {
+ .oe_title {
@@ -40,6 +41,12 @@
}
}
+ // TODO remove me in master (this rule is a copy of the one in the
+ // bootstrap_review.scss file)
+ + .alert {
+ clear: both;
+ }
+
.oe_stat_button {
color: $o-main-text-color;
text-align: left;
diff --git a/addons/web/static/src/scss/layout_background.scss b/addons/web/static/src/scss/layout_background.scss
index 9f4aed173f4..ff2d0cef3b0 100644
--- a/addons/web/static/src/scss/layout_background.scss
+++ b/addons/web/static/src/scss/layout_background.scss
@@ -3,18 +3,17 @@
color: gray('700');
}
.o_background_header {
+ min-width: 900px;
border-bottom: 1px solid gray('200');
- .company_address {
- min-width: 300px;
- }
img {
max-height: 96px;
- max-width: 300px;
+ max-width: 200px;
margin-right: 16px;
}
h3 {
color: $o-brand-odoo;
font-weight: 700;
+ font-size: 1.25rem;
max-width: 300px;
}
}
diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss
index 4ccf7de9c2b..770508ac1b8 100644
--- a/addons/web/static/src/scss/list_view.scss
+++ b/addons/web/static/src/scss/list_view.scss
@@ -23,6 +23,9 @@
border-bottom: 1px solid #cacaca;
> tr > th {
border-left: 1px solid #dfdfdf;
+ @include media-breakpoint-down(sm) {
+ white-space: nowrap;
+ }
&:not(.o_column_sortable):hover {
cursor: default;
}
@@ -80,9 +83,10 @@
.o_list_record_remove button {
padding: 0px;
- background-style: none;
+ background: none;
border-style: none;
display: table-cell;
+ cursor: pointer;
}
// Contextual classes
@@ -93,8 +97,10 @@
// Grouped list views
tbody > tr.o_group_header {
- height: 31px;
-
+ > th, > td {
+ padding-top: ($table-cell-padding-sm*2);
+ padding-bottom: ($table-cell-padding-sm*2);
+ }
.o_group_name {
white-space: nowrap;
}
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 907a02c0237..1eef5047f46 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -810,7 +810,7 @@
<t t-name="FieldMany2One">
<t t-if="widget.mode === 'readonly'">
- <a t-if="!widget.nodeOptions.no_open" class="o_form_uri" href="#"/>
+ <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"/>
</t>
<div t-if="widget.mode === 'edit'" class="o_field_widget o_field_many2one" aria-atomic="true">