From ae583ba1736a09a7d4e62a3a0619cb0575b83822 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A9ry=20Debongnie?=
Date: Thu, 11 Jun 2015 09:54:13 +0200
Subject: [PATCH 01/17] [REF] web_kanban: new redesigned kanban view
This commit introduces an almost completely rewritten kanban view. It
was necessary in order to be able to render a different DOM to match
the new design.
It basically has the same features as before, but will render a
different DOM. Three main differences, API wise, should be noted:
* the way the kanban view components communicate has been changed. It now
uses events, and event bubbling.
* kanban records do not have access to the view, so every call to this.view
should be updated
* kanban templates are no longer wrapped in a div, so they should have a
single root element for better result.
---
addons/web_kanban/static/src/css/kanban.css | 8 +-
addons/web_kanban/static/src/css/kanban.sass | 2 +-
.../web_kanban/static/src/js/compatibility.js | 10 +-
.../web_kanban/static/src/js/kanban_column.js | 245 ++++
.../web_kanban/static/src/js/kanban_common.js | 928 -------------
.../static/src/js/kanban_quick_create.js | 127 ++
.../web_kanban/static/src/js/kanban_record.js | 275 ++++
.../web_kanban/static/src/js/kanban_view.js | 1187 +++++++++--------
.../static/src/js/kanban_widgets.js | 247 ++++
.../static/src/less/kanban_dashboard.less | 49 +-
.../static/src/less/kanban_view.less | 500 +++++++
.../web_kanban/static/src/xml/web_kanban.xml | 144 +-
addons/web_kanban/views/web_kanban.xml | 7 +-
13 files changed, 2140 insertions(+), 1589 deletions(-)
create mode 100644 addons/web_kanban/static/src/js/kanban_column.js
delete mode 100644 addons/web_kanban/static/src/js/kanban_common.js
create mode 100644 addons/web_kanban/static/src/js/kanban_quick_create.js
create mode 100644 addons/web_kanban/static/src/js/kanban_record.js
create mode 100644 addons/web_kanban/static/src/js/kanban_widgets.js
create mode 100644 addons/web_kanban/static/src/less/kanban_view.less
diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css
index 840597606dd..494fe5b10c6 100644
--- a/addons/web_kanban/static/src/css/kanban.css
+++ b/addons/web_kanban/static/src/css/kanban.css
@@ -577,11 +577,11 @@
background: #ffffff;
}
-.openerp .oe_popup_form .oe_kanban_buttons .oe_highlight {
+.openerp .o_popup_form .oe_kanban_buttons .oe_highlight {
color: #404040;
background: none;
}
-.openerp .oe_popup_form .oe_kanban_buttons button.oe_highlight {
+.openerp .o_popup_form .oe_kanban_buttons button.oe_highlight {
background-color: #efefef;
background-image: -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#d8d8d8));
background-image: -webkit-linear-gradient(top, #efefef, #d8d8d8);
@@ -593,7 +593,7 @@
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset;
}
-.openerp .oe_popup_form .oe_kanban_buttons button.oe_highlight:active {
+.openerp .o_popup_form .oe_kanban_buttons button.oe_highlight:active {
background-color: #e3e3e3;
background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6));
background-image: -webkit-linear-gradient(top, #e3e3e3, #f6f6f6);
@@ -605,7 +605,7 @@
-webkit-box-shadow: none;
-box-shadow: none;
}
-.openerp .oe_popup_form .oe_kanban_buttons button.oe_highlight:hover {
+.openerp .o_popup_form .oe_kanban_buttons button.oe_highlight:hover {
background-color: #f6f6f6;
background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3));
background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3);
diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass
index b229c8c9d41..5b7d187d9a1 100644
--- a/addons/web_kanban/static/src/css/kanban.sass
+++ b/addons/web_kanban/static/src/css/kanban.sass
@@ -514,7 +514,7 @@
padding: 0px
background: #ffffff
.openerp
- .oe_popup_form
+ .o_popup_form
.oe_kanban_buttons
.oe_highlight
color: #404040
diff --git a/addons/web_kanban/static/src/js/compatibility.js b/addons/web_kanban/static/src/js/compatibility.js
index 256086b9ef1..a8ecd06f8d6 100644
--- a/addons/web_kanban/static/src/js/compatibility.js
+++ b/addons/web_kanban/static/src/js/compatibility.js
@@ -1,15 +1,17 @@
odoo.define('web_kanban.compatibility', function (require) {
"use strict";
-var common = require('web_kanban.common');
+var kanban_widgets = require('web_kanban.widgets');
+var KanbanRecord = require('web_kanban.Record');
+var KanbanColumn = require('web_kanban.Column');
var KanbanView = require('web_kanban.KanbanView');
return;
openerp = window.openerp || {};
openerp.web_kanban = openerp.web_kanban || {};
-openerp.web_kanban.AbstractField = common.AbstractField;
-openerp.web_kanban.KanbanGroup = common.KanbanGroup;
-openerp.web_kanban.KanbanRecord = common.KanbanRecord;
+openerp.web_kanban.AbstractField = kanban_widgets.AbstractField;
+openerp.web_kanban.KanbanGroup = KanbanColumn;
+openerp.web_kanban.KanbanRecord = KanbanRecord;
openerp.web_kanban.KanbanView = KanbanView;
});
diff --git a/addons/web_kanban/static/src/js/kanban_column.js b/addons/web_kanban/static/src/js/kanban_column.js
new file mode 100644
index 00000000000..85333ffbe3f
--- /dev/null
+++ b/addons/web_kanban/static/src/js/kanban_column.js
@@ -0,0 +1,245 @@
+odoo.define('web_kanban.Column', function (require) {
+"use strict";
+
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var form_common = require('web.form_common');
+var Widget = require('web.Widget');
+var quick_create = require('web_kanban.quick_create');
+var KanbanRecord = require('web_kanban.Record');
+
+var _t = core._t;
+var QWeb = core.qweb;
+var RecordQuickCreate = quick_create.RecordQuickCreate;
+
+var KanbanColumn = Widget.extend({
+ template: "KanbanView.Group",
+
+ events: {
+ 'click .o_kanban_toggle_fold': function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.folded = true;
+ this.update_column();
+ },
+ 'click .o_column_edit': 'edit_column',
+ 'click .o_column_delete': 'delete_column',
+ 'click .o_kanban_quick_add': 'add_quick_create',
+ 'click .o_kanban_load_more': 'load_more',
+ },
+
+ custom_events: {
+ 'kanban_record_delete': 'delete_record',
+ 'cancel_quick_create': 'cancel_quick_create',
+ 'quick_create_add_record': 'quick_create_add_record',
+ },
+
+ init: function(parent, group_data, options, record_options) {
+ this._super(parent);
+ this.data_records = group_data.records;
+ this.dataset = group_data.dataset;
+ this.records = [];
+ this.title = group_data.title;
+ this.id = group_data.id;
+ this.folded = group_data.attributes.folded;
+ this.fields = options.fields;
+ this.values = group_data.values;
+
+ this.quick_create = options.quick_create;
+ this.grouped_by_m2o = options.grouped_by_m2o;
+ this.editable = options.editable;
+ this.deletable = options.deletable;
+ this.records_editable = options.records_editable;
+ this.records_deletable = options.records_deletable;
+ this.relation = options.relation;
+ this.offset = 0;
+ this.remaining = this.dataset.size() - this.data_records.length;
+
+ this.record_options = _.extend(_.clone(record_options), {
+ group_info: this.values,
+ });
+ },
+
+ start: function() {
+ var self = this;
+ this.$header = this.$('.o_kanban_header');
+
+ for (var i = 0; i < this.data_records.length; i++) {
+ this.add_record(this.data_records[i], {no_update: true});
+ }
+ this.$header.tooltip();
+
+ this.update_column();
+ this.$el.sortable({
+ connectWith: '.o_kanban_group',
+ revert: 150,
+ delay: 100,
+ items: '> .o_kanban_record',
+ helper: 'clone',
+ cursor: 'move',
+ over: function () {
+ self.folded = false;
+ self.update_column();
+ },
+ update: function (event, ui) {
+ var record = ui.item.data('record');
+ var index = self.records.indexOf(record);
+ var test2 = $.contains(self.$el[0], record.$el[0]);
+ if (index >= 0 && test2) {
+ // resequencing records
+ record.$el.removeAttr('style'); // jqueryui sortable add display:block inline
+ self.trigger_up('kanban_column_resequence');
+ } else if (index >= 0 && !test2) {
+ // removing record from this column
+ self.records.splice(self.records.indexOf(record), 1);
+ self.dataset.remove_ids([record.id]);
+ } else {
+ // adding record to this column
+ self.records.push(record);
+ record.setParent(self);
+ self.trigger_up('kanban_column_add_record', {record: record});
+ }
+ self.update_column();
+ }
+ });
+ this.$el.click(function (event) {
+ if (self.$el.hasClass('o_column_folded')) {
+ event.preventDefault();
+ self.folded = false;
+ self.update_column();
+ }
+ });
+ },
+
+ is_empty: function () {
+ return !this.records.length;
+ },
+
+ add_record: function (data, options) {
+ var record = new KanbanRecord(this, data, this.record_options);
+ this.records.push(record);
+ this.dataset.ids = _.uniq(this.dataset.ids.concat(record.id));
+ if (options.position === 'before') {
+ record.insertAfter(this.quick_create_widget ? this.quick_create_widget.$el : this.$header);
+ } else {
+ var $load_more = this.$('.o_kanban_load_more');
+ if ($load_more.length) {
+ record.insertBefore($load_more);
+ } else {
+ record.appendTo(this.$el);
+ }
+ }
+ if (!options.no_update) {
+ this.update_column();
+ }
+ },
+
+ get_ids: function () {
+ var ids = [];
+ this.$('.o_kanban_record').each(function (index, r) {
+ ids.push($(r).data('record').id);
+ });
+ return ids;
+ },
+
+ update_column: function () {
+ var title = this.folded ? this.title + ' (' + this.records.length + ')' : this.title;
+ this.$header.find('.o_column_title').text(title);
+ this.$header.find('.o-kanban-count').text(this.records.length);
+
+ this.$el.toggleClass('o_column_folded', this.folded);
+ var tooltip;
+ if (this.remaining) {
+ tooltip = this.records.length + '/' + this.dataset.size() + _t(' records');
+ } else {
+ tooltip = this.records.length + _t(' records');
+ }
+ this.$header.tooltip().attr('data-original-title', tooltip);
+ if (!this.remaining) {
+ this.$('.o_kanban_load_more').remove();
+ } else {
+ this.$('.o_kanban_load_more').html(QWeb.render('KanbanView.LoadMore', {widget:this}))
+ }
+ },
+
+ delete_column: function (event) {
+ event.preventDefault();
+ var buttons = [
+ {
+ text: _t("Ok"),
+ classes: 'btn-primary',
+ close: true,
+ click: this.trigger_up.bind(this, 'kanban_column_delete')
+ },
+ {text: _t("Cancel"), close: true}
+ ];
+ new Dialog(this, {
+ size: 'medium',
+ buttons: buttons,
+ $content: $('', {
+ text: _t("Are you sure that you want to remove this column ?")
+ }),
+ }).open();
+ },
+
+ edit_column: function (event) {
+ event.preventDefault();
+ var dialog = new form_common.FormViewDialog(this, {
+ res_model: this.relation,
+ res_id: this.id,
+ title: _t("Edit Column"),
+ }).open();
+
+ dialog.on('record_saved', this, function () {
+ this.trigger_up('kanban_reload');
+ });
+ },
+
+ delete_record: function (event) {
+ event.stopped = false;
+ var self = this;
+ event.data.after = function cleanup () {
+ var index = self.records.indexOf(event.data.record);
+ self.records.splice(index, 1);
+ self.update_column();
+ };
+ },
+
+ add_quick_create: function () {
+ if (this.quick_create_widget) {
+ return;
+ }
+ var self = this;
+ var width = this.records.length ? this.records[0].$el.innerWidth() : this.$el.width() - 8;
+ this.quick_create_widget = new RecordQuickCreate(this, width);
+ this.quick_create_widget.insertAfter(this.$header);
+ this.quick_create_widget.$el.focusout(function () {
+ setTimeout(function() {
+ var hasFocus = !! (self.quick_create_widget.$(':focus').length > 0);
+ if (! hasFocus && self.quick_create_widget) {
+ self.cancel_quick_create();
+ }
+ }, 10);
+ });
+
+ },
+
+ cancel_quick_create: function () {
+ this.quick_create_widget.destroy();
+ this.quick_create_widget = undefined;
+ },
+
+ quick_create_add_record: function (event) {
+ this.trigger_up('column_add_record', event.data);
+ },
+
+ load_more: function (event) {
+ event.preventDefault();
+ this.trigger_up('kanban_load_more');
+ },
+
+});
+
+return KanbanColumn;
+
+});
diff --git a/addons/web_kanban/static/src/js/kanban_common.js b/addons/web_kanban/static/src/js/kanban_common.js
deleted file mode 100644
index b63758aaa28..00000000000
--- a/addons/web_kanban/static/src/js/kanban_common.js
+++ /dev/null
@@ -1,928 +0,0 @@
-odoo.define('web_kanban.common', function (require) {
-"use strict";
-
-var core = require('web.core');
-var data = require('web.data');
-var form_common = require('web.form_common');
-var formats = require('web.formats');
-var framework = require('web.framework');
-var ProgressBar = require('web.ProgressBar');
-var pyeval = require('web.pyeval');
-var Registry = require('web.Registry');
-var session = require('web.session');
-var time = require('web.time');
-var utils = require('web.utils');
-var web_client = require('web.web_client');
-var Widget = require('web.Widget');
-
-var QWeb = core.qweb;
-var _t = core._t;
-/**
- * Interface to be implemented by kanban fields.
- *
- */
-var FieldInterface = {
- /**
- Constructor.
- - parent: The widget's parent.
- - field: A dictionary giving details about the field, including the current field's value in the
- raw_value field.
- - $node: The field
tag as it appears in the view, encapsulated in a jQuery object.
- */
- init: function(parent, field, $node) {},
-};
-
-/**
- * Abstract class for classes implementing FieldInterface.
- *
- * Properties:
- * - value: useful property to hold the value of the field. By default, the constructor
- * sets value property.
- *
- */
-var AbstractField = Widget.extend(FieldInterface, {
- /**
- Constructor that saves the field and $node parameters and sets the "value" property.
- */
- init: function(parent, field, $node) {
- this._super(parent);
- this.field = field;
- this.$node = $node;
- this.options = pyeval.py_eval(this.$node.attr("options") || '{}');
- this.set("value", field.raw_value);
- },
-});
-
-var Priority = AbstractField.extend({
- init: function(parent, field, $node) {
- this._super.apply(this, arguments);
- this.name = $node.attr('name');
- this.parent = parent;
- },
- prepare_priority: function() {
- var self = this;
- var selection = this.field.selection || [];
- var init_value = selection && selection[0][0] || 0;
- var data = _.map(selection.slice(1), function(element, index) {
- var value = {
- 'value': element[0],
- 'name': element[1],
- 'click_value': element[0],
- };
- if (index === 0 && self.get('value') == element[0]) {
- value.click_value = init_value;
- }
- return value;
- });
- return data;
- },
- renderElement: function() {
- this.record_id = this.parent.id;
- this.priorities = this.prepare_priority();
- var readonly = this.field && this.field.readonly;
- if (readonly){
- this.set('readonly', true);
- }
- this.$el = $(QWeb.render("Priority", {'widget': this}));
- if (!readonly){
- this.$el.find('li').click(this.do_action.bind(this));
- }
- },
- do_action: function(e) {
- var self = this;
- var li = $(e.target).closest( "li" );
- if (li.length) {
- var value = {};
- value[self.name] = String(li.data('value'));
- return self.parent.view.dataset._model.call('write', [[self.record_id], value, self.parent.view.dataset.get_context()]).done(self.reload_record.bind(self.parent));
- }
- },
- reload_record: function() {
- this.do_reload();
- },
-});
-
-var KanbanSelection = AbstractField.extend({
- init: function(parent, field, $node) {
- this._super.apply(this, arguments);
- this.name = $node.attr('name');
- this.parent = parent;
- },
- prepare_dropdown_selection: function() {
- var self = this;
- var data = [];
- _.map(this.field.selection || [], function(res) {
- var value = {
- 'name': res[0],
- 'tooltip': res[1],
- 'state_name': res[1],
- };
- var leg_opt = self.options && self.options.states_legend || null;
- if (leg_opt && leg_opt[res[0]] && self.parent.group.values && self.parent.group.values[leg_opt[res[0]]]) {
- value['state_name'] = self.parent.group.values[leg_opt[res[0]]];
- value['tooltip'] = self.parent.group.values[leg_opt[res[0]]];
- }
- if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
- else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
- else { value['state_class'] = 'oe_kanban_status oe_kanban_status_red'; }
- data.push(value);
- });
- return data;
- },
- renderElement: function() {
- var self = this;
- this.record_id = self.parent.id;
- this.states = self.prepare_dropdown_selection();
- this.$el = $(QWeb.render("KanbanSelection", {'widget': self}));
- this.$el.find('li').click(self.do_action.bind(self));
- },
- do_action: function(e) {
- var self = this;
- var li = $(e.target).closest( "li" );
- if (li.length) {
- var value = {};
- value[self.name] = String(li.data('value'));
- return self.parent.view.dataset._model.call('write', [[self.record_id], value, self.parent.view.dataset.get_context()]).done(self.reload_record.bind(self.parent));
- }
- },
- reload_record: function() {
- this.do_reload();
- },
-});
-
-
-/**
- * Quick creation view.
- *
- * Triggers a single event "added" with a single parameter "name", which is the
- * name entered by the user
- *
- * @class
- * @type {*}
- */
-var QuickCreate = Widget.extend({
- template: 'KanbanView.quick_create',
-
- /**
- * close_btn: If true, the widget will display a "Close" button able to trigger
- * a "close" event.
- */
- init: function(parent, dataset, context, buttons) {
- this._super(parent);
- this._dataset = dataset;
- this._buttons = buttons || false;
- this._context = context || {};
- },
- start: function () {
- var self = this;
- self.$input = this.$el.find('input');
- self.$input.keyup(function(event){
- if(event.keyCode == 13){
- self.quick_add();
- }
- });
- $(".oe_kanban_quick_create").focusout(function (e) {
- var val = self.$el.find('input').val();
- if (/^\s*$/.test(val)) { self.trigger('close'); }
- e.stopImmediatePropagation();
- });
- $(".oe_kanban_quick_create_add", this.$el).click(function () {
- self.quick_add();
- self.focus();
- });
- $(".oe_kanban_quick_create_close", this.$el).click(function (ev) {
- ev.preventDefault();
- self.trigger('close');
- });
- self.$input.keyup(function(e) {
- if (e.keyCode == 27 && self._buttons) {
- self.trigger('close');
- }
- });
- },
- focus: function() {
- this.$el.find('input').focus();
- },
- /**
- * Handles user event from nested quick creation view
- */
- quick_add: function () {
- var self = this;
- var val = this.$input.val();
- if (/^\s*$/.test(val)) { this.$el.remove(); return; }
- this._dataset.call(
- 'name_create', [val, new data.CompoundContext(
- this._dataset.get_context(), this._context)])
- .then(function(record) {
- self.$input.val("");
- self.trigger('added', record[0]);
- }, function(error, event) {
- event.preventDefault();
- return self.slow_create();
- });
- },
- slow_create: function() {
- var self = this;
- var popup = new form_common.SelectCreatePopup(this);
- popup.select_element(
- self._dataset.model,
- {
- title: _t("Create: ") + (this.string || this.name),
- initial_view: "form",
- disable_multiple_selection: true
- },
- [],
- {"default_name": self.$input.val()}
- );
- popup.on("elements_selected", self, function(element_ids) {
- self.$input.val("");
- self.trigger('added', element_ids[0]);
- });
- }
-});
-
-var KanbanRecord = Widget.extend({
- template: 'KanbanView.record',
-
- events: {
- 'click .o_kanban_manage_toggle_button': 'toggle_manage_pane',
- },
-
- init: function (parent, record) {
- this._super(parent);
- this.group = parent;
- this.view = parent.view;
- this.id = null;
- this.set_record(record);
- if (!this.view.state.records[this.id]) {
- this.view.state.records[this.id] = {
- folded: false
- };
- }
- this.state = this.view.state.records[this.id];
- this.fields = {};
- },
- set_record: function(record) {
- var self = this;
- this.id = record.id;
- this.values = {};
- _.each(record, function(v, k) {
- self.values[k] = {
- value: v
- };
- });
- this.record = this.transform_record(record);
- },
- start: function() {
- var self = this;
- this._super();
- this.init_content();
- },
- init_content: function() {
- var self = this;
- self.sub_widgets = [];
- this.$("[data-field_id]").each(function() {
- self.add_widget($(this));
- });
- this.$el.data('widget', this);
- this.bind_events();
- },
- transform_record: function(record) {
- var self = this,
- new_record = {};
- _.each(record, function(value, name) {
- var r = _.clone(self.view.fields_view.fields[name] || {});
- if ((r.type === 'date' || r.type === 'datetime') && value) {
- r.raw_value = time.auto_str_to_date(value);
- } else {
- r.raw_value = value;
- }
- r.value = formats.format_value(value, r);
- new_record[name] = r;
- });
- return new_record;
- },
- renderElement: function() {
- this.qweb_context = {
- record: this.record,
- widget: this,
- read_only_mode: this.view.options.read_only_mode,
- };
- for (var p in this) {
- if (_.str.startsWith(p, 'kanban_')) {
- this.qweb_context[p] = _.bind(this[p], this);
- }
- }
- var $el = QWeb.render(this.template, {
- 'widget': this,
- 'content': this.view.qweb.render('kanban-box', this.qweb_context)
- });
- this.replaceElement($el);
- this.replace_fields();
- },
- replace_fields: function() {
- var self = this;
- this.$("field").each(function() {
- var $field = $(this);
- var $nfield = $(" 0 && this.$el.find('.oe_kanban_global_click').data('routing')) {
- framework.redirect(this.$el.find('.oe_kanban_global_click').data('routing') + "/" + this.id);
- }
- else if (this.$el.find('.oe_kanban_global_click_edit').size()>0)
- this.do_action_edit();
- else
- this.do_action_open();
- },
- setup_color_picker: function() {
- var self = this;
- var $el = this.$el.find('ul.oe_kanban_colorpicker');
- if ($el.length) {
- $el.html(QWeb.render('KanbanColorPicker', {
- widget: this
- }));
- $el.on('click', 'a', function(ev) {
- ev.preventDefault();
- var color_field = $(this).parents('.oe_kanban_colorpicker').first().data('field') || 'color';
- var data = {};
- data[color_field] = $(this).data('color');
- self.view.dataset.write(self.id, data, {}).done(function() {
- self.record[color_field] = $(this).data('color');
- self.do_reload();
- });
- });
- }
- },
- toggle_manage_pane: function(){
- this.$('.o_kanban_card_content').toggleClass('o_visible o_invisible');
- this.$('.o_kanban_card_manage_pane').toggleClass('o_visible o_invisible');
- this.$('.o_kanban_manage_button_section').toggleClass(this.kanban_color((this.values['color'] && this.values['color'].value) || 0));
- },
-
- do_action_delete: function($action) {
- var self = this;
- function do_it() {
- return $.when(self.view.dataset.unlink([self.id])).done(function() {
- self.group.remove_record(self.id);
- self.destroy();
- });
- }
- if (this.view.options.confirm_on_delete) {
- if (confirm(_t("Are you sure you want to delete this record ?"))) {
- return do_it();
- }
- } else
- return do_it();
- },
- do_action_edit: function($action) {
- this.view.open_record(this.id, true);
- },
- do_action_open: function($action) {
- this.view.open_record(this.id);
- },
- do_action_object: function ($action) {
- var button_attrs = $action.data();
- var node_context = button_attrs.context || {};
- var context = new data.CompoundContext(node_context);
- context.set_eval_context({
- active_id: this.id,
- active_ids: [this.id],
- active_model: this.view.dataset.model
- });
- var action_data = _.extend({}, button_attrs, {context: context});
- this.view.do_execute_action(action_data, this.view.dataset, this.id, this.do_reload);
- },
- do_action_url: function($action) {
- return framework.redirect($action.attr("href"));
- },
- do_reload: function() {
- var self = this;
- this.view.dataset.read_ids([this.id], this.view.fields_keys.concat(['__last_update'])).done(function(records) {
- _.each(self.sub_widgets, function(el) {
- el.destroy();
- });
- self.sub_widgets = [];
- if (records.length) {
- self.set_record(records[0]);
- self.renderElement();
- self.init_content();
- self.group.compute_cards_auto_height();
- self.view.postprocess_m2m_tags();
- } else {
- self.destroy();
- }
- });
- },
- kanban_getcolor: function(variable) {
- var index = 0;
- switch (typeof(variable)) {
- case 'string':
- for (var i=0, ii=variable.length; i -1) {
- var new_sequence = _.pluck(this.records, 'id');
- self.view.dataset.resequence(new_sequence);
- }
- },
- /**
- * Handles a newly created record
- *
- * @param {id} id of the newly created record
- */
- quick_created: function (record) {
- var id = record, self = this;
- self.view.remove_no_result();
- self.trigger("add_record");
- this.dataset.read_ids([id], this.view.fields_keys)
- .done(function (records) {
- self.view.dataset.ids.push(id);
- self.do_add_records(records, true);
- });
- },
- highlight: function(show){
- if(show){
- this.$el.addClass('oe_kanban_column_higlight');
- this.$records.addClass('oe_kanban_column_higlight');
- }else{
- this.$el.removeClass('oe_kanban_column_higlight');
- this.$records.removeClass('oe_kanban_column_higlight');
- }
- }
-});
-
-
-/**
- * Kanban widgets: ProgressBar
- * options
- * - editable: boolean if current_value is editable
- * - current_value: get the current_value from the field that must be present in the view
- * - max_value: get the max_value from the field that must be present in the view
- * - title: title of the gauge, displayed on top of the gauge
- * - on_change: action to call when cliking and setting a value
- */
-var KanbanProgressBar = AbstractField.extend({
- events: {
- 'click': function() {
- if(!this.readonly && this.progressbar.readonly) {
- this.toggle_progressbar();
- }
- }
- },
-
- init: function (parent, field, node) {
- this._super(parent, field, node);
-
- var record = this.getParent().record;
- this.progressbar = new ProgressBar(this, {
- readonly: true,
- value: record[this.options.current_value].raw_value,
- max_value: record[this.options.max_value].raw_value,
- title: this.options.title,
- edit_max_value: this.options.edit_max_value,
- });
-
- this.readonly = !this.options.editable;
- this.on_change = this.options.on_change;
- },
-
- start: function () {
- var self = this;
-
- var def = this.progressbar.appendTo('').done(function() {
- self.progressbar.$el.addClass(self.$el.attr('class'));
- self.replaceElement(self.progressbar.$el);
- });
-
- return $.when(this._super(), def).then(function() {
- if(!self.readonly) {
- var parent = self.getParent();
- self.progressbar.on('change:value change:max_value', self, function(e) {
- var value = this.progressbar.get(this.progressbar.edit_max_value ? 'max_value' : 'value') || 0;
- if(!isNaN(value)) {
- parent.view.dataset.call(this.on_change, [parent.id, value]).then(function() {
- self.toggle_progressbar();
- });
- }
- });
- }
- });
- },
-
- toggle_progressbar: function() {
- this.progressbar.readonly = !this.progressbar.readonly;
- var $div = $('
').insertAfter(this.$el);
- this.progressbar.replace($div);
- this.setElement(this.progressbar.$el);
- },
-});
-
-var fields_registry = new Registry();
-
-fields_registry
- .add('priority', Priority)
- .add('kanban_state_selection', KanbanSelection)
- .add("progress", KanbanProgressBar);
-
-
-return {
- AbstractField: AbstractField,
- registry: fields_registry,
- QuickCreate: QuickCreate,
- KanbanRecord: KanbanRecord,
- KanbanGroup: KanbanGroup,
-};
-
-});
diff --git a/addons/web_kanban/static/src/js/kanban_quick_create.js b/addons/web_kanban/static/src/js/kanban_quick_create.js
new file mode 100644
index 00000000000..3268a2b1acd
--- /dev/null
+++ b/addons/web_kanban/static/src/js/kanban_quick_create.js
@@ -0,0 +1,127 @@
+odoo.define('web_kanban.quick_create', function (require) {
+"use strict";
+
+var Widget = require('web.Widget');
+
+var RecordQuickCreate = Widget.extend({
+ template: "KanbanView.QuickCreate",
+
+ events: {
+ 'click .o_kanban_cancel': 'cancel',
+ 'click .o_kanban_add': 'add_record',
+ 'keypress input': function (event) {
+ if (event.keyCode === 13) {
+ this.add_record();
+ }
+ },
+ 'keydown': function (event) {
+ if (event.keyCode === 27) {
+ this.trigger_up('cancel_quick_create');
+ }
+ },
+ },
+
+ init: function (parent, width) {
+ this._super(parent);
+ this.width = width;
+ },
+
+ start: function () {
+ this.$el.css({width: this.width});
+ this.$input = this.$('input');
+ this.$input.focus();
+ },
+
+ cancel: function () {
+ this.trigger_up('cancel_quick_create');
+ },
+
+ add_record: function () {
+ var value = this.$input.val();
+ this.$input.val('');
+ if (/^\s*$/.test(value)) { return; }
+ this.trigger_up('quick_create_add_record', {value: value});
+ this.$input.focus();
+ },
+});
+
+var ColumnQuickCreate = Widget.extend({
+ template: 'KanbanView.ColumnQuickCreate',
+
+ events: {
+ 'click .o_kanban_add': function (event) {
+ console.log('click okanbanadd');
+ event.stopPropagation();
+ this.add_column();
+ },
+ 'click .o_kanban_quick_create': function (event) {
+ console.log('click quickcreate');
+ event.stopPropagation();
+ },
+ 'keypress input': function (event) {
+ if (event.keyCode === 13) {
+ this.add_column();
+ }
+ },
+ 'click .o_kanban_cancel': function (event) {
+ this.folded = true;
+ this.update();
+ },
+ 'keydown': function (event) {
+ if (event.keyCode === 27) {
+ this.folded = true;
+ this.update();
+ }
+ },
+ },
+
+ init: function (parent) {
+ this._super(parent);
+ this.folded = true;
+ },
+
+ start: function () {
+ this.$header = this.$('.o_column_header');
+ this.$quick_create = this.$('.o_kanban_quick_create');
+ this.$input = this.$('input');
+ this.$el.click(this.proxy('toggle'));
+ var self = this;
+ this.$el.focusout(function () {
+ setTimeout(function() {
+ var hasFocus = !! (self.$(':focus').length > 0);
+ if (! hasFocus) {
+ self.folded = true;
+ self.update();
+ }
+ }, 10);
+ });
+ },
+
+ toggle: function () {
+ this.folded = !this.folded;
+ this.update();
+ if (!this.folded) {
+ this.$input.focus();
+ }
+ },
+
+ update: function () {
+ this.$header.toggle(this.folded);
+ this.$quick_create.toggle(!this.folded);
+ },
+
+ add_column: function () {
+ var name = this.$input.val();
+ this.$input.val('');
+ if (/^\s*$/.test(name)) { return; }
+ this.trigger_up('quick_create_add_column', {value: name});
+ this.$input.focus();
+ }
+});
+
+return {
+ RecordQuickCreate: RecordQuickCreate,
+ ColumnQuickCreate: ColumnQuickCreate,
+};
+
+});
diff --git a/addons/web_kanban/static/src/js/kanban_record.js b/addons/web_kanban/static/src/js/kanban_record.js
new file mode 100644
index 00000000000..9357978d20c
--- /dev/null
+++ b/addons/web_kanban/static/src/js/kanban_record.js
@@ -0,0 +1,275 @@
+odoo.define('web_kanban.Record', function (require) {
+"use strict";
+
+var core = require('web.core');
+var data = require('web.data');
+var formats = require('web.formats');
+var framework = require('web.framework');
+var session = require('web.session');
+var time = require('web.time');
+var utils = require('web.utils');
+var Widget = require('web.Widget');
+var kanban_widgets = require('web_kanban.widgets');
+
+var QWeb = core.qweb;
+var fields_registry = kanban_widgets.registry;
+
+var KanbanRecord = Widget.extend({
+ template: 'KanbanView.record',
+
+ events: {
+ 'click .oe_kanban_action': 'on_kanban_action_clicked',
+ 'click .o_kanban_manage_toggle_button': 'toggle_manage_pane',
+ },
+
+ custom_events: {
+ 'kanban_update_record': 'update_record',
+ },
+
+ init: function (parent, record, options) {
+ this._super(parent);
+
+ this.fields = options.fields;
+ this.editable = options.editable;
+ this.deletable = options.deletable;
+ this.draggable = options.draggable;
+ this.read_only_mode = options.read_only_mode;
+ this.model = options.model;
+ this.group_info = options.group_info;
+ this.qweb = options.qweb;
+ this.sub_widgets = [];
+
+ this.init_content(record);
+ },
+
+ init_content: function (record) {
+ var self = this;
+ this.id = record.id;
+ this.values = {};
+ _.each(record, function(v, k) {
+ self.values[k] = {
+ value: v
+ };
+ });
+ this.record = this.transform_record(record);
+ var qweb_context = {
+ record: this.record,
+ widget: this,
+ read_only_mode: this.read_only_mode,
+ };
+ for (var p in this) {
+ if (_.str.startsWith(p, 'kanban_')) {
+ qweb_context[p] = _.bind(this[p], this);
+ }
+ }
+ this.content = this.qweb.render('kanban-box', qweb_context);
+ },
+
+ add_widgets: function () {
+ var self = this;
+ this.$("field").each(function() {
+ var $field = $(this);
+ var field = self.record[$field.attr("name")];
+ var type = $field.attr("widget") || field.type;
+ var Widget = fields_registry.get(type);
+ var widget = new Widget(self, field, $field);
+ widget.replace($field);
+ self.sub_widgets.push(widget);
+ });
+ },
+
+ start: function() {
+ this.add_widgets();
+ },
+
+ renderElement: function () {
+ this._super();
+ this.setup_color_picker();
+ this.$el.addClass('o_kanban_record');
+ this.$el.data('record', this);
+ if (this.$el.hasClass('oe_kanban_global_click') || this.$el.hasClass('oe_kanban_global_click_edit')) {
+ this.$el.on('click', this.proxy('on_global_click'));
+ }
+ },
+ transform_record: function(record) {
+ var self = this;
+ var new_record = {};
+ _.each(record, function(value, name) {
+ var r = _.clone(self.fields[name] || {});
+ if ((r.type === 'date' || r.type === 'datetime') && value) {
+ r.raw_value = time.auto_str_to_date(value);
+ } else {
+ r.raw_value = value;
+ }
+ r.value = formats.format_value(value, r);
+ new_record[name] = r;
+ });
+ return new_record;
+ },
+
+ update: function (record) {
+ _.invoke(this.sub_widgets, 'destroy');
+ this.sub_widgets = [];
+ this.init_content(record);
+ this.renderElement();
+ this.add_widgets();
+ },
+
+ kanban_image: function(model, field, id, cache, options) {
+ options = options || {};
+ var url;
+ if (this.record[field] && this.record[field].value && !utils.is_bin_size(this.record[field].value)) {
+ url = 'data:image/png;base64,' + this.record[field].value;
+ } else if (this.record[field] && ! this.record[field].value) {
+ url = "/web/static/src/img/placeholder.png";
+ } else {
+ id = JSON.stringify(id);
+ if (options.preview_image)
+ field = options.preview_image;
+ url = session.url('/web/binary/image', {model: model, field: field, id: id});
+ if (cache !== undefined) {
+ // Set the cache duration in seconds.
+ url += '&cache=' + parseInt(cache, 10);
+ }
+ }
+ return url;
+ },
+
+ kanban_getcolor: function(variable) {
+ if (typeof(variable) === 'number') {
+ return Math.round(variable) % 10;
+ }
+ if (typeof(variable) === 'string') {
+ var index = 0;
+ for (var i=0; i
= 1) {
+ var undef_group = groups[undef_index];
+ groups.splice(undef_index, 1);
+ groups.unshift(undef_group);
+ }
+ return groups;
+ })
+ .then(function (groups) {
+ // load records for each group
+ var is_empty = true;
+ return $.when.apply(null, _.map(groups, function (group) {
var def = $.when([]);
var dataset = new data.DataSetSearch(self, self.dataset.model,
new data.CompoundContext(self.dataset.get_context(), group.model.context()), group.model.domain());
@@ -313,275 +235,273 @@ var KanbanView = View.extend({
if (group.attributes.length >= 1) {
def = dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit });
}
- return def.then(function(records) {
- self.nb_records += records.length;
- self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
- groups_array[index] = new KanbanGroup(self, records, group, dataset);
+ return def.then(function (records) {
+ self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
+ group.records = records;
+ group.dataset = dataset;
+ is_empty = is_empty && !records.length;
+ return group;
});
})).then(function () {
- if(!self.nb_records) {
- self.no_result();
- }
- if (self.dataset.index >= self.nb_records){
- self.dataset.index = self.dataset.size() ? 0 : null;
- }
- return self.do_add_groups(groups_array).done(function() {
- self.trigger('kanban_groups_processed');
- });
- });
- });});
- },
- do_process_dataset: function() {
- var self = this;
- this.$el.find('table:first').show();
- this.$el.removeClass('oe_kanban_grouped').addClass('oe_kanban_ungrouped');
- var def = $.Deferred();
- this.add_group_mutex.exec(function() {
- self.do_clear_groups();
- self.dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit }).done(function(records) {
- var kgroup = new KanbanGroup(self, records, null, self.dataset);
- if (!_.isEmpty(self.dataset.ids) && (self.dataset.index === null || self.dataset.index >= self.dataset.ids.length)) {
- self.dataset.index = 0;
- } else if (_.isEmpty(self.dataset.ids)){
- self.dataset.index = null;
- }
- self.do_add_groups([kgroup]).done(function() {
- if (_.isEmpty(records)) {
- self.no_result();
- }
- self.trigger('kanban_dataset_processed');
- def.resolve();
- });
- }).done(null, function() {
- def.reject();
+ return {
+ groups: Array.prototype.slice.call(arguments, 0),
+ is_empty: is_empty,
+ grouped: true,
+ };
});
return def;
});
- return def;
},
- do_reload: function() {
- this.do_search(this.search_domain, this.search_context, this.search_group_by);
- },
- do_clear_groups: function() {
- var groups = this.groups.slice(0);
- this.groups = [];
- _.each(groups, function(group) {
- group.destroy();
- });
- },
- do_add_groups: function(groups) {
- var self = this;
- var $parent = this.$el.parent();
- this.$el.detach();
- _.each(groups, function(group) {
- self.groups[group.undefined_title ? 'unshift' : 'push'](group);
- });
- var $last_td = self.$el.find('.oe_kanban_groups_headers td:last');
- var groups_started = _.map(this.groups, function(group) {
- if (!group.is_started) {
- group.on("add_record", self, function () {
- self.remove_no_result();
- });
- return group.insertBefore($last_td);
- }
- });
- return $.when.apply(null, groups_started).done(function () {
- self.on_groups_started();
- self.$el.appendTo($parent);
- _.each(self.groups, function(group) {
- group.compute_cards_auto_height();
- });
- });
- },
- on_groups_started: function() {
- var self = this;
- if (this.group_by || this.fields_keys.indexOf("sequence") !== -1) {
- // Kanban cards drag'n'drop
- var prev_widget, is_folded, record, $columns;
- if (this.group_by) {
- $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards, .oe_kanban_column .oe_kanban_folded_column_cards');
- } else {
- $columns = this.$el.find('.oe_kanban_column_cards');
- }
- $columns.sortable({
- handle : '.oe_kanban_draghandle',
- start: function(event, ui) {
- self.currently_dragging.index = ui.item.parent().children('.oe_kanban_record').index(ui.item);
- self.currently_dragging.group = prev_widget = ui.item.parents('.oe_kanban_column:first').data('widget');
- ui.item.find('*').on('click.prevent', function(ev) {
- return false;
- });
- record = ui.item.data('widget');
- record.$el.bind('mouseup',function(ev,ui){
- if (is_folded) {
- record.$el.hide();
- }
- record.$el.unbind('mouseup');
- });
- ui.placeholder.height(ui.item.height());
- },
- over: function(event, ui) {
- var parent = $(event.target).parent();
- prev_widget.highlight(false);
- is_folded = parent.hasClass('oe_kanban_group_folded');
- if (is_folded) {
- var widget = parent.data('widget');
- widget.highlight(true);
- prev_widget = widget;
- }
- },
- revert: 150,
- stop: function(event, ui) {
- prev_widget.highlight(false);
- var old_index = self.currently_dragging.index;
- var new_index = ui.item.parent().children('.oe_kanban_record').index(ui.item);
- var old_group = self.currently_dragging.group;
- var new_group = ui.item.parents('.oe_kanban_column:first').data('widget');
- if (!(old_group.title === new_group.title && old_group.value === new_group.value && old_index == new_index)) {
- self.on_record_moved(record, old_group, old_index, new_group, new_index);
- }
- setTimeout(function() {
- // A bit hacky but could not find a better solution for Firefox (problem not present in chrome)
- // http://stackoverflow.com/questions/274843/preventing-javascript-click-event-with-scriptaculous-drag-and-drop
- ui.item.find('*').off('click.prevent');
- }, 0);
- },
- scroll: false
- });
- // Keep connectWith out of the sortable initialization for performance sake:
- // http://www.planbox.com/blog/development/coding/jquery-ui-sortable-slow-to-bind.html
- $columns.sortable({ connectWith: $columns });
- // Kanban groups drag'n'drop
- var start_index;
- if (this.grouped_by_m2o) {
- this.$('.oe_kanban_groups_headers').sortable({
- items: '.oe_kanban_group_header',
- helper: 'clone',
- axis: 'x',
- opacity: 0.5,
- scroll: false,
- start: function(event, ui) {
- start_index = ui.item.index();
- self.$('.oe_kanban_record, .oe_kanban_quick_create').css({ visibility: 'hidden' });
- },
- stop: function(event, ui) {
- var stop_index = ui.item.index();
- if (start_index !== stop_index) {
- var $start_column = self.$('.oe_kanban_groups_records .oe_kanban_column').eq(start_index);
- var $stop_column = self.$('.oe_kanban_groups_records .oe_kanban_column').eq(stop_index);
- var method = (start_index > stop_index) ? 'insertBefore' : 'insertAfter';
- $start_column[method]($stop_column);
- var tmp_group = self.groups.splice(start_index, 1)[0];
- self.groups.splice(stop_index, 0, tmp_group);
- var new_sequence = _.pluck(self.groups, 'value');
- (new data.DataSet(self, self.group_by_field.relation)).resequence(new_sequence).done(function(r) {
- if (r === false) {
- console.error("Kanban: could not resequence model '%s'. Probably no 'sequence' field.", self.group_by_field.relation);
- }
- });
- }
- self.$('.oe_kanban_record, .oe_kanban_quick_create').css({ visibility: 'visible' });
- }
- });
+ is_action_enabled: function(action) {
+ if (action === 'create' && !this.options.creatable) {
+ return false;
+ }
+ return this._super(action);
+ },
+ _is_quick_create_enabled: function() {
+ if (!this.quick_creatable || !this.is_action_enabled('create'))
+ return false;
+ if (this.fields_view.arch.attrs.quick_create !== undefined)
+ return JSON.parse(this.fields_view.arch.attrs.quick_create);
+ return !!this.grouped;
+ },
+ /**
+ * Render the buttons according to the KanbanView.buttons template and
+ * add listeners on it.
+ * Set this.$buttons with the produced jQuery element
+ * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted
+ * $node may be undefined, in which case the ListView inserts them into this.options.$buttons
+ */
+ render_buttons: function($node) {
+ var display = false;
+ if (this.options.action_buttons !== false) {
+ display = this.is_action_enabled('create');
+ } else if (!this.view_id && !this.options.read_only_mode) {
+ display = this.is_action_enabled('write') || this.is_action_enabled('create');
+ }
+ this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this, display: display}));
+ this.$buttons.on('click', 'button.o-kanban-button-new', this.add_record.bind(this));
+
+ $node = $node || this.options.$buttons;
+ this.$buttons.appendTo($node);
+ },
+
+ render_pager: function($node, options) {
+ var self = this;
+ this.pager = new Pager(this, this.dataset.size(), 1, this.limit, options);
+ this.pager.appendTo($node);
+ this.pager.on('pager_changed', this, function (state) {
+ self.load_records(state.current_min - 1)
+ .then(function (data) {
+ self.data = data;
+ })
+ .done(this.proxy('render'));
+ });
+ this.update_pager();
+ },
+
+ update_pager: function() {
+ if (this.pager) {
+ if (this.grouped) {
+ this.pager.do_hide();
+ } else {
+ this.pager.set_state({size: this.dataset.size(), current_min: 1});
}
+ }
+ },
+
+ render: function () {
+ // cleanup
+ this.$el.css({display:'-webkit-flex'});
+ this.$el.css({display:'flex'});
+ this.$el.removeClass('o_kanban_ungrouped o_kanban_grouped');
+ _.invoke(this.widgets, 'destroy');
+ this.$el.empty();
+ this.widgets = [];
+ if (this.column_quick_create) {
+ this.column_quick_create.destroy();
+ this.column_quick_create = undefined;
+ }
+
+ this.record_options = {
+ editable: this.is_action_enabled('edit'),
+ deletable: this.is_action_enabled('delete'),
+ fields: this.fields_view.fields,
+ qweb: this.qweb,
+ model: this.model,
+ read_only_mode: this.options.read_only_mode,
+ };
+
+ // actual rendering
+ var fragment = document.createDocumentFragment();
+ if (this.data.grouped) {
+ this.$el.addClass('o_kanban_grouped');
+ this.render_grouped(fragment);
+ } else if (this.data.is_empty) {
+ this.render_no_content(fragment);
} else {
- this.$el.find('.oe_kanban_draghandle').removeClass('oe_kanban_draghandle');
+ this.$el.addClass('o_kanban_ungrouped');
+ this.render_ungrouped(fragment);
+ }
+ this.$el.append(fragment);
+ },
+
+ render_no_content: function (fragment) {
+ var content = QWeb.render('KanbanView.nocontent', {content: this.no_content_msg});
+ $(content).appendTo(fragment);
+ },
+
+ render_ungrouped: function (fragment) {
+ var self = this;
+ var options = _.clone(this.record_options);
+ _.each(this.data.records, function (record) {
+ var kanban_record = new KanbanRecord(self, record, options);
+ self.widgets.push(kanban_record);
+ kanban_record.appendTo(fragment);
+ });
+
+ // add empty invisible divs to make sure that all kanban records are left aligned
+ for (var i = 0, ghost_div; i < 6; i++) {
+ ghost_div = $("").addClass("o_kanban_record o_kanban_ghost");
+ ghost_div.appendTo(fragment);
}
this.postprocess_m2m_tags();
},
- on_record_moved : function(record, old_group, old_index, new_group, new_index) {
+
+ get_column_options: function () {
+ return {
+ editable: this.is_action_enabled('group_edit'),
+ deletable: this.is_action_enabled('group_delete'),
+ grouped_by_m2o: this.grouped_by_m2o,
+ relation: this.relation,
+ qweb: this.qweb,
+ fields: this.fields_view.fields,
+ quick_create: this._is_quick_create_enabled(),
+ };
+ },
+
+ render_grouped: function (fragment) {
var self = this;
- record.$el.find('[title]').tooltip('destroy');
- $(old_group.$el).add(new_group.$el).find('.oe_kanban_group_length').hide();
- if (old_group === new_group) {
- new_group.records.splice(old_index, 1);
- new_group.records.splice(new_index, 0, record);
- new_group.do_save_sequences();
+ var record_options = _.extend(this.record_options, {
+ draggable: true,
+ });
+
+ var column_options = this.get_column_options();
+
+ _.each(this.data.groups, function (group) {
+ var column = new KanbanColumn(self, group, column_options, record_options);
+ column.appendTo(fragment);
+ self.widgets.push(column);
+ });
+ this.$el.sortable({
+ axis: 'x',
+ items: '> .o_kanban_group',
+ cursor: 'move',
+ revert: 150,
+ delay: 100,
+ tolerance: 'pointer',
+ forcePlaceholderSize: true,
+ stop: function () {
+ var ids = [];
+ self.$('.o_kanban_group').each(function (index, u) {
+ ids.push($(u).data('id'));
+ });
+ self.resequence(ids);
+ },
+ });
+ if (this.is_action_enabled('group_create')) {
+ this.column_quick_create = new ColumnQuickCreate(this);
+ this.column_quick_create.appendTo(fragment);
+ }
+ this.postprocess_m2m_tags();
+ },
+
+ add_record: function() {
+ this.dataset.index = null;
+ this.do_switch_view('form');
+ },
+
+ open_record: function (event, options) {
+ if (this.dataset.select_id(event.data.id)) {
+ this.do_switch_view('form', null, options); //, null, { mode: "edit" });
} else {
- old_group.records.splice(old_index, 1);
- new_group.records.splice(new_index, 0, record);
- record.group = new_group;
- var data = {};
- data[this.group_by] = new_group.value;
- this.dataset.write(record.id, data, {}).done(function() {
- record.do_reload();
- new_group.do_save_sequences();
- if (new_group.state.folded) {
- new_group.do_action_toggle_fold();
- record.prependTo(new_group.$records.find('.oe_kanban_column_cards'));
- }
- }).fail(function(error, evt) {
- evt.preventDefault();
- alert(_t("An error has occured while moving the record to this group: ") + error.data.message);
- self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
- });
+ this.do_warn("Kanban: could not find id#" + event.data.id);
}
},
- do_show: function() {
- this.do_push_state({});
- return this._super();
+ edit_record: function (event) {
+ this.open_record(event, {mode: 'edit'});
},
- open_record: function(id, editable) {
- if (this.dataset.select_id(id)) {
- this.do_switch_view('form', null, { mode: editable ? "edit" : undefined });
- } else {
- this.do_warn("Kanban: could not find id#" + id);
- }
- },
- no_result: function() {
+
+ delete_record: function (event) {
var self = this;
- if (this.groups.group_by
- || !this.options.action
- || (!this.options.action.help && !this.options.action.get_empty_list_help)) {
- return;
+ var record = event.data.record;
+ function do_it() {
+ return $.when(self.dataset.unlink([record.id])).done(function() {
+ record.destroy();
+ if (event.data.after) {
+ event.data.after();
+ }
+ });
+ }
+ if (this.options.confirm_on_delete) {
+ Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), do_it);
+ } else {
+ do_it();
}
- this.$el.css("position", "relative");
- $(QWeb.render('KanbanView.nocontent', { content : this.options.action.get_empty_list_help || this.options.action.help})).insertBefore(this.$('table:first'));
- this.$el.find('.oe_view_nocontent').click(function() {
- self.$buttons.openerpBounce();
- });
},
- remove_no_result: function() {
- this.$el.css("position", "");
- this.$el.find('.oe_view_nocontent').remove();
+
+ open_action: function (event) {
+ var self = this;
+ var node_context = event.data.context || {};
+ var context = new data.CompoundContext(node_context);
+ context.set_eval_context({
+ active_id: event.target.id,
+ active_ids: [event.target.id],
+ active_model: this.dataset.model,
+ });
+ this.do_execute_action(event.data, this.dataset, event.target.id).then(function () {
+ self.reload_record(event.target);
+ });
},
/*
* postprocessing of fields type many2many
* make the rpc request for all ids/model and insert value inside .oe_tags fields
*/
- postprocess_m2m_tags: function() {
+ postprocess_m2m_tags: function(records) {
var self = this;
if (!this.many2manys.length) {
return;
}
var relations = {};
- this.groups.forEach(function(group) {
- group.records.forEach(function(record) {
- self.many2manys.forEach(function(name) {
- var field = record.record[name];
- var $el = record.$('.oe_form_field.oe_tags[name=' + name + ']').empty();
- // fields declared in the kanban view may not be used directly
- // in the template declaration, for example fields for which the
- // raw value is used -> $el[0] is undefined, leading to errors
- // in the following process. Preventing to add push the id here
- // prevents to make unnecessary calls to name_get
- if (! $el[0]) {
- return;
+ records = records ? (records instanceof Array ? records : [records]) :
+ this.grouped ? Array.prototype.concat.apply([], _.pluck(this.widgets, 'records')) :
+ this.widgets;
+
+ records.forEach(function(record) {
+ self.many2manys.forEach(function(name) {
+ var field = record.record[name];
+ var $el = record.$('.oe_form_field.oe_tags[name=' + name + ']');
+ // fields declared in the kanban view may not be used directly
+ // in the template declaration, for example fields for which the
+ // raw value is used -> $el[0] is undefined, leading to errors
+ // in the following process. Preventing to add push the id here
+ // prevents to make unnecessary calls to name_get
+ if (! $el[0]) {
+ return;
+ }
+ if (!relations[field.relation]) {
+ relations[field.relation] = { ids: [], elements: {}, context: self.m2m_context[name]};
+ }
+ var rel = relations[field.relation];
+ field.raw_value.forEach(function(id) {
+ rel.ids.push(id);
+ if (!rel.elements[id]) {
+ rel.elements[id] = [];
}
- if (!relations[field.relation]) {
- relations[field.relation] = { ids: [], elements: {}, context: self.m2m_context[name]};
- }
- var rel = relations[field.relation];
- field.raw_value.forEach(function(id) {
- rel.ids.push(id);
- if (!rel.elements[id]) {
- rel.elements[id] = [];
- }
- rel.elements[id].push($el[0]);
- });
+ rel.elements[id].push($el[0]);
});
});
});
@@ -589,68 +509,253 @@ var KanbanView = View.extend({
var dataset = new data.DataSetSearch(self, rel_name, self.dataset.get_context(rel.context));
dataset.name_get(_.uniq(rel.ids)).done(function(result) {
result.forEach(function(nameget) {
- $(rel.elements[nameget[0]]).append('
' + _.str.escapeHTML(nameget[1]) + ' ');
+ // white color (0) should not be selected, 1 instead
+ var color = KanbanRecord.prototype.kanban_getcolor(nameget[1]) + 1;
+ var $tag = $('
')
+ .addClass('o_tag oe_kanban_color_' + color)
+ .attr('title', _.str.escapeHTML(nameget[1]));
+ $(rel.elements[nameget[0]]).append($tag);
});
+ // we use boostrap tooltips for better and faster display
+ self.$('span.o_tag').tooltip();
});
});
+ },
+ resequence: function (ids) {
+ if ((ids.length <= 1) || !this.relation) {
+ return;
+ }
+ new data.DataSet(this, this.relation).resequence(ids).done(function (r) {
+ if (!r) {
+ console.warn('Resequence could not be complete. ' +
+ 'Maybe the model does not have a "sequence" field?');
+ }
+ });
+ },
+
+ resequence_column: function (col) {
+ if (_.indexOf(this.fields_keys, 'sequence') > -1) {
+ this.dataset.resequence(col.get_ids());
+ }
+ },
+
+ delete_column: function (event) {
+ var self = this;
+ var column = event.target;
+ (new data.DataSet(this, this.relation)).unlink([column.id]).done(function() {
+ if (column.is_empty()) {
+ var index = self.widgets.indexOf(column);
+ self.widgets.splice(index,1);
+ column.destroy();
+ } else {
+ self.do_reload();
+ }
+ });
+ },
+
+ reload_record: function (record) {
+ var self = this;
+ this.dataset.read_ids([record.id], this.fields_keys.concat(['__last_update'])).done(function(records) {
+ if (records.length) {
+ record.update(records[0]);
+ self.postprocess_m2m_tags(record);
+ } else {
+ record.destroy();
+ }
+ });
+ },
+
+ add_record_to_column: function (event) {
+ var self = this;
+ var column = event.target;
+ var record = event.data.record;
+ var data = {};
+ data[this.group_by_field] = event.target.id;
+ this.dataset.write(record.id, data, {}).done(function () {
+ if (!self.isDestroyed()) {
+ self.reload_record(record);
+ self.resequence_column(column);
+ }
+ });
+ },
+
+ update_record: function(event) {
+ var self = this;
+ var record = event.target;
+ return this.dataset.write(record.id, event.data)
+ .done(function () {
+ if (!self.isDestroyed()) {
+ self.reload_record(record);
+ }
+ });
+ },
+
+ column_add_record: function (event) {
+ var self = this;
+ var column = event.target;
+ var context = {};
+ context['default_' + this.group_by_field] = column.value;
+ var name = event.data.value;
+ this.dataset.name_create(name, context).then(function on_success (data) {
+ add_record(data[0]);
+ }, function on_fail (event) {
+ event.preventDefault();
+ var popup = new form_common.SelectCreatePopup(this);
+ popup.select_element(
+ self.dataset.model,
+ {
+ title: _t("Create: "),
+ initial_view: "form",
+ disable_multiple_selection: true
+ },
+ [],
+ {"default_name": name}
+ );
+ popup.on("elements_selected", self, function(element_ids) {
+ add_record(element_ids[0]);
+ });
+ });
+
+ function add_record(id) {
+ self.dataset.add_ids([id], -1);
+ self.dataset.read_ids([id], self.fields_keys.concat(['__last_update'])).done(function(record) {
+ column.add_record(record[0], {position: 'before'});
+ });
+ }
+ },
+
+ add_new_column: function (event) {
+ var self = this;
+ var model = new Model(this.relation, this.search_context);
+ model.call('create', [{name: event.data.value}], {
+ context: this.search_context,
+ }).then(function (id) {
+ var dataset = new data.DataSetSearch(self, self.dataset.model, self.dataset.get_context(), []);
+ var group_data = {
+ records: [],
+ title: event.data.value,
+ id: id,
+ attributes: {folded: false},
+ dataset: dataset,
+ values: {},
+ };
+ var options = self.get_column_options();
+ var record_options = _.clone(self.record_options);
+ var column = new KanbanColumn(self, group_data, options, record_options);
+ column.insertBefore(self.$('.o_column_quick_create'));
+ self.widgets.push(column);
+ });
+ },
+
+ load_more: function (event) {
+ var self = this;
+ var column = event.target;
+ var offset = column.offset + this.limit;
+ return this.load_records(offset, column.dataset).then(function (result) {
+ _.each(result.records, function (r) {
+ column.add_record(r, {no_update: true});
+ });
+ column.offset += self.limit;
+ column.remaining = Math.max(column.remaining - self.limit, 0);
+ column.update_column();
+ self.postprocess_m2m_tags(column.records);
+ });
+ },
+
+ call_method: function (event) {
+ var data = event.data;
+ this.dataset.call(data.method, data.params).then(function() {
+ if (data.callback) {
+ data.callback();
+ }
+ });
}
});
+function qweb_add_if(node, condition) {
+ if (node.attrs[QWeb.prefix + '-if']) {
+ condition = _.str.sprintf("(%s) and (%s)", node.attrs[QWeb.prefix + '-if'], condition);
+ }
+ node.attrs[QWeb.prefix + '-if'] = condition;
+}
+
+function transform_qweb_template (node, fvg, many2manys) {
+ // Process modifiers
+ if (node.tag && node.attrs.modifiers) {
+ var modifiers = JSON.parse(node.attrs.modifiers || '{}');
+ if (modifiers.invisible) {
+ qweb_add_if(node, _.str.sprintf("!kanban_compute_domain(%s)", JSON.stringify(modifiers.invisible)));
+ }
+ }
+ switch (node.tag) {
+ case 'field':
+ var ftype = fvg.fields[node.attrs.name].type;
+ ftype = node.attrs.widget ? node.attrs.widget : ftype;
+ if (ftype === 'many2many') {
+ if (_.indexOf(many2manys, node.attrs.name) < 0) {
+ many2manys.push(node.attrs.name);
+ }
+ node.tag = 'div';
+ node.attrs['class'] = (node.attrs['class'] || '') + ' oe_form_field oe_tags o_kanban_tags';
+ } else if (fields_registry.contains(ftype)) {
+ // do nothing, the kanban record will handle it
+ } else {
+ node.tag = QWeb.prefix;
+ node.attrs[QWeb.prefix + '-esc'] = 'record.' + node.attrs.name + '.value';
+ }
+ break;
+ case 'button':
+ case 'a':
+ var type = node.attrs.type || '';
+ if (_.indexOf('action,object,edit,open,delete,url'.split(','), type) !== -1) {
+ _.each(node.attrs, function(v, k) {
+ if (_.indexOf('icon,type,name,args,string,context,states,kanban_states'.split(','), k) != -1) {
+ node.attrs['data-' + k] = v;
+ delete(node.attrs[k]);
+ }
+ });
+ if (node.attrs['data-string']) {
+ node.attrs.title = node.attrs['data-string'];
+ }
+ if (node.attrs['data-icon']) {
+ node.children = [{
+ tag: 'img',
+ attrs: {
+ src: session.prefix + '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png',
+ width: '16',
+ height: '16'
+ }
+ }];
+ }
+ if (node.tag == 'a' && node.attrs['data-type'] != "url") {
+ node.attrs.href = '#';
+ } else {
+ node.attrs.type = 'button';
+ }
+ node.attrs['class'] = (node.attrs['class'] || '') + ' oe_kanban_action oe_kanban_action_' + node.tag;
+ }
+ break;
+ }
+ if (node.children) {
+ for (var i = 0, ii = node.children.length; i < ii; i++) {
+ transform_qweb_template(node.children[i], fvg, many2manys);
+ }
+ }
+}
+
+var One2ManyKanbanView = KanbanView.extend({
+ render_pager: function($node, options) {
+ options = _.extend(options || {}, {
+ single_page_hidden: true,
+ });
+ this._super($node, options);
+ },
+});
+
core.view_registry.add('kanban', KanbanView);
+core.one2many_view_registry.add('kanban', One2ManyKanbanView);
return KanbanView;
});
-
-odoo.define(function (require) {
-
-var core = require('web.core');
-var data = require('web.data');
-var web_client = require('web.web_client');
-var kanban_common = require('web_kanban.common');
-var KanbanView = require('web_kanban.KanbanView');
-
-var KanbanGroup = kanban_common.KanbanGroup;
-var _t = core._t;
-
-KanbanView.include({
- do_add_group: function() {
- var self = this;
- self.do_action({
- name: _t("Add column"),
- res_model: self.group_by_field.relation,
- views: [[false, 'form']],
- type: 'ir.actions.act_window',
- target: "new",
- context: self.dataset.get_context(),
- flags: {
- action_buttons: true,
- }
- });
- var am = web_client.action_manager;
- var form = am.dialog_widget.views.form.controller;
- form.on("on_button_cancel", am.dialog, am.dialog.close);
- form.on('record_created', self, function(r) {
- (new data.DataSet(self, self.group_by_field.relation)).name_get([r]).done(function(new_record) {
- am.dialog.close();
- var domain = self.dataset.domain.slice(0);
- domain.push([self.group_by, '=', new_record[0][0]]);
- var dataset = new data.DataSetSearch(self, self.dataset.model, self.dataset.get_context(), domain);
- var datagroup = {
- get: function(key) {
- return this[key];
- },
- value: new_record[0],
- length: 0,
- };
- var new_group = new KanbanGroup(self, [], datagroup, dataset);
- self.do_add_groups([new_group]).done(function() {
- $(window).scrollTo(self.groups.slice(-1)[0].$el, { axis: 'x' });
- });
- });
- });
- },
-});
-
-});
diff --git a/addons/web_kanban/static/src/js/kanban_widgets.js b/addons/web_kanban/static/src/js/kanban_widgets.js
new file mode 100644
index 00000000000..29942075e79
--- /dev/null
+++ b/addons/web_kanban/static/src/js/kanban_widgets.js
@@ -0,0 +1,247 @@
+odoo.define('web_kanban.widgets', function (require) {
+"use strict";
+
+var core = require('web.core');
+var ProgressBar = require('web.ProgressBar');
+var pyeval = require('web.pyeval');
+var Registry = require('web.Registry');
+var session = require('web.session');
+var Widget = require('web.Widget');
+
+var QWeb = core.qweb;
+/**
+ * Interface to be implemented by kanban fields.
+ *
+ */
+var FieldInterface = {
+ /**
+ Constructor.
+ - parent: The widget's parent.
+ - field: A dictionary giving details about the field, including the current field's value in the
+ raw_value field.
+ - $node: The field tag as it appears in the view, encapsulated in a jQuery object.
+ */
+ init: function(parent, field, $node) {},
+};
+
+/**
+ * Abstract class for classes implementing FieldInterface.
+ *
+ * Properties:
+ * - value: useful property to hold the value of the field. By default, the constructor
+ * sets value property.
+ *
+ */
+var AbstractField = Widget.extend(FieldInterface, {
+ /**
+ Constructor that saves the field and $node parameters and sets the "value" property.
+ */
+ init: function(parent, field, $node) {
+ this._super(parent);
+ this.field = field;
+ this.$node = $node;
+ this.options = pyeval.py_eval(this.$node.attr("options") || '{}');
+ this.set("value", field.raw_value);
+ },
+});
+
+var Priority = AbstractField.extend({
+ init: function(parent, field, $node) {
+ this._super.apply(this, arguments);
+ this.name = $node.attr('name');
+ },
+ prepare_priority: function() {
+ var self = this;
+ var selection = this.field.selection || [];
+ var init_value = selection && selection[0][0] || 0;
+ var data = _.map(selection.slice(1), function(element, index) {
+ var value = {
+ 'value': element[0],
+ 'name': element[1],
+ 'click_value': element[0],
+ };
+ if (index === 0 && self.get('value') == element[0]) {
+ value.click_value = init_value;
+ }
+ return value;
+ });
+ return data;
+ },
+ renderElement: function() {
+ this.priorities = this.prepare_priority();
+ var readonly = this.field && this.field.readonly;
+ if (readonly){
+ this.set('readonly', true);
+ }
+ this.$el = $(QWeb.render("Priority", {'widget': this}));
+ if (!readonly){
+ this.$el.on('click', 'a', this.do_action.bind(this));
+ }
+ },
+ do_action: function(event) {
+ event.preventDefault();
+ var $a = $(event.currentTarget);
+ var data = {};
+ data[this.name] = String($a.data('value'));
+ this.trigger_up('kanban_update_record', data);
+ },
+});
+
+var KanbanSelection = AbstractField.extend({
+
+ init: function(parent, field, $node) {
+ this._super.apply(this, arguments);
+ this.name = $node.attr('name');
+ this.parent = parent;
+ },
+ prepare_dropdown_selection: function() {
+ var self = this;
+ var data = [];
+ _.map(this.field.selection || [], function(res) {
+ var value = {
+ 'name': res[0],
+ 'tooltip': res[1],
+ 'state_name': res[1],
+ };
+ var leg_opt = self.options && self.options.states_legend || null;
+ if (leg_opt) {
+ var key = leg_opt[value.name];
+ var legend = self.parent.group_info[key];
+ if (legend) {
+ value.state_name = legend;
+ value.tooltip = legend;
+ }
+ }
+ if (res[0] == 'normal') { value.state_class = 'oe_kanban_status'; }
+ else if (res[0] == 'done') { value.state_class = 'oe_kanban_status oe_kanban_status_green'; }
+ else { value.state_class = 'oe_kanban_status oe_kanban_status_red'; }
+ data.push(value);
+ });
+ return data;
+ },
+ renderElement: function() {
+ var self = this;
+ this.states = this.prepare_dropdown_selection();
+
+ var state;
+ for(var i = 0 ; i < this.states.length ; i++) {
+ if(this.states[i].name === this.get('value')) {
+ state = this.states[i];
+ }
+ }
+
+ this.$el = $(QWeb.render("KanbanSelection", {'widget': this}));
+ this.$('a').first().find('span').removeClass().addClass(state.state_class);
+ this.$('ul li').show().filter(function() {
+ return ($(this).data('value') === state.name);
+ }).hide();
+
+ this.$('a').click(function (ev) {
+ ev.preventDefault();
+ });
+
+ this.$('li').click(this.set_kanban_selection.bind(this));
+ },
+ set_kanban_selection: function(e) {
+ e.preventDefault();
+ var self = this;
+ var $li = $(e.target).closest( "li" );
+ if ($li.length) {
+ var value = {};
+ value[self.name] = String($li.data('value'));
+ this.trigger_up('kanban_update_record', value, this);
+ }
+ },
+});
+
+var KanbanAttachmentImage = AbstractField.extend({
+ template: 'KanbanAttachmentImage',
+});
+
+
+/**
+ * Kanban widgets: ProgressBar
+ * options
+ * - editable: boolean if current_value is editable
+ * - current_value: get the current_value from the field that must be present in the view
+ * - max_value: get the max_value from the field that must be present in the view
+ * - title: title of the gauge, displayed on top of the gauge
+ * - on_change: action to call when cliking and setting a value
+ */
+var KanbanProgressBar = AbstractField.extend({
+ events: {
+ 'click': function() {
+ if(!this.readonly && this.progressbar.readonly) {
+ this.toggle_progressbar();
+ }
+ }
+ },
+
+ init: function (parent, field, node) {
+ this._super(parent, field, node);
+
+ var record = this.getParent().record;
+ this.progressbar = new ProgressBar(this, {
+ readonly: true,
+ value: record[this.options.current_value].raw_value,
+ max_value: record[this.options.max_value].raw_value,
+ title: this.options.title,
+ edit_max_value: this.options.edit_max_value,
+ });
+
+ this.readonly = !this.options.editable;
+ this.on_change = this.options.on_change;
+ },
+
+ start: function () {
+ var self = this;
+
+ var def = this.progressbar.appendTo('').done(function() {
+ self.progressbar.$el.addClass(self.$el.attr('class'));
+ self.replaceElement(self.progressbar.$el);
+ });
+
+ return $.when(this._super(), def).then(function() {
+ if(!self.readonly) {
+ var parent = self.getParent();
+ self.progressbar.on('change:value change:max_value', self, function(e) {
+ var value = this.progressbar.get(this.progressbar.edit_max_value ? 'max_value' : 'value') || 0;
+ if(!isNaN(value)) {
+ var data = {
+ method: this.on_change,
+ params: [parent.id, value],
+ callback: self.proxy('toggle_progressbar'),
+ };
+ self.trigger_up('kanban_call_method', data);
+
+ // parent.view.dataset.call(this.on_change, [parent.id, value]).then(function() {
+ // self.toggle_progressbar();
+ // });
+ }
+ });
+ }
+ });
+ },
+
+ toggle_progressbar: function() {
+ this.progressbar.readonly = !this.progressbar.readonly;
+ var $div = $('
').insertAfter(this.$el);
+ this.progressbar.replace($div);
+ this.setElement(this.progressbar.$el);
+ },
+});
+
+var fields_registry = new Registry();
+
+fields_registry
+ .add('priority', Priority)
+ .add('kanban_state_selection', KanbanSelection)
+ .add("attachment_image", KanbanAttachmentImage)
+ .add('progress', KanbanProgressBar);
+
+return {
+ AbstractField: AbstractField,
+ registry: fields_registry,
+};
+
+});
diff --git a/addons/web_kanban/static/src/less/kanban_dashboard.less b/addons/web_kanban/static/src/less/kanban_dashboard.less
index 3315beef9eb..61df77edcd0 100644
--- a/addons/web_kanban/static/src/less/kanban_dashboard.less
+++ b/addons/web_kanban/static/src/less/kanban_dashboard.less
@@ -1,7 +1,7 @@
-.oe_kanban_view {
+.o_kanban_view {
&.o_kanban_mobile {
- .oe_kanban_record {
+ .o_kanban_record {
width: 400px;
}
span {
@@ -14,8 +14,6 @@
&.o_kanban_dashboard {
- @odoo-brand-optional: #21b799;
-
.o_visible {
width: 100%;
}
@@ -43,8 +41,8 @@
margin-right: 0px !important;
}
+
.oe_kanban_colorpicker {
- min-height: 21px;
li {
height: 0px;
@@ -52,45 +50,37 @@
}
}
- .oe_kanban_record {
- padding: 0;
- position: relative;
- margin: 10px;
-
- // @media (max-width: @grid-float-breakpoint) {
- // margin: 10px !important;
- // }
- }
-
- // if invisible, it will no take more than its original height
+ // If invisible, elem will no take more than its original height
a, span {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
position: relative;
- width: 100%;
display: block;
}
+ .o_kanban_record {
+ padding: 0;
+ position: relative;
+ margin: 4px 8px;
+ }
+
a {
- // color: @odoo-brand-optional;
+ color: @odoo-brand-optional;
}
.o_kanban_card_header {
- display: flex;
- display: -webkit-flex;
+ .o-flex-display();
background-color: lightgrey;
position: relative;
.o_kanban_card_header_title {
- -webkit-flex: 3 0 0;
- flex: 3 0 0;
- padding: 8px;
+ .o-flex(3, 0, 0);
+ padding: 8px 16px;
.o_primary {
- // color: @odoo-brand-optional;
- font-variant: small-caps;
- font-size: large;
+ color: @odoo-brand-optional;
+ font-weight: 700;
}
.o_secondary {
@@ -98,12 +88,11 @@
}
.o_kanban_manage_button_section {
- -webkit-flex: 1 0 0;
- flex: 1 0 0;
+ .o-flex(1, 0, 0);
padding: 16px;
.o_kanban_manage_toggle_button {
- text-align: center;
+ text-align: right;
}
}
}
@@ -129,6 +118,7 @@
.o_kanban_card_manage_pane {
display: inline-block;
vertical-align: top;
+ padding: 0 8px;
}
.o_kanban_card_manage_section {
@@ -145,6 +135,7 @@
}
.o_kanban_card_manage_settings {
+ text-align: right;
padding: 8px;
border-color: lightgrey;
diff --git a/addons/web_kanban/static/src/less/kanban_view.less b/addons/web_kanban/static/src/less/kanban_view.less
new file mode 100644
index 00000000000..4d7a6221dbf
--- /dev/null
+++ b/addons/web_kanban/static/src/less/kanban_view.less
@@ -0,0 +1,500 @@
+@o-kanban-record-margin: 4px;
+
+.o_kanban_view {
+
+ .o-flex-display();
+ .o-align-content(stretch);
+ @media (max-width: @grid-float-breakpoint) {
+ padding: 0px;
+ }
+
+ .o_kanban_quick_create {
+ background-color: white;
+ margin: @o-kanban-record-margin;
+ box-shadow: 0px 0px 1px 1px lighten(@odoo-main-text-color, 10%);
+ padding: 3px;
+ input {
+ width: 100%;
+ margin: 4px 0;
+ }
+ }
+
+ .o_kanban_record {
+ min-width: 150px;
+ background-color: white;
+ margin: 4px 4px 8px 4px;
+ box-shadow: 0px 0px 1px 1px lighten(@odoo-main-text-color, 10%);
+ padding: 8px;
+
+ &.o_record_dragged {
+ background-color: @odoo-main-text-color;
+ }
+
+ &.oe_kanban_global_click, &.oe_kanban_global_click_edit {
+ cursor: pointer;
+ }
+
+ ul {
+ list-style-type: none;
+ padding: 0;
+ }
+
+ &.o_kanban_two_column {
+ .o-flex-display();
+
+ .oe_kanban_details {
+ position: relative;
+ display: inline-block;
+ .o-flex(1, 1, auto);
+ width: 0;
+ }
+ }
+
+ &.o_kanban_two_column_tags {
+ .o-flex-display();
+ .o-flex-flow(row, wrap);
+
+ .o_kanban_tags_section {
+ .o-flex(1, 1, 100%);
+ }
+ .o_kanban_image {
+ .o-flex(1, 1, 25%);
+ margin-right: 0;
+ > img {
+ box-shadow: none;
+ }
+ }
+ .oe_kanban_details {
+ position: relative;
+ .o-flex(1, 1, 75%);
+ ul {
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ .o_kanban_title {
+ font-weight: bold;
+ margin-bottom: 3px;
+ }
+
+ .o_kanban_image {
+ .o-flex(0, 0, auto);
+ margin-right: 8px;
+ text-align: center;
+
+ img {
+ width: 64px;
+ height: 64px;
+ box-shadow: none;
+ }
+
+ }
+
+ .o_kanban_button {
+ margin-top: 15px;
+
+ > button, > a {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ }
+ }
+
+ &.ui-sortable-helper {
+ transform: rotate(3deg);
+ opacity: 0.9,
+ }
+
+ .o_dropdown_kanban {
+ float: right;
+
+ // Open the dropdown a the left side of the dropdown toggle
+ > .dropdown-menu {
+ left: auto;
+ right: 0;
+ padding: 3px;
+ }
+
+ > a {
+ padding: 3px;
+ color: @odoo-main-text-color;
+ }
+ &.open {
+ visibility: visible;
+ }
+ }
+
+ .o_kanban_footer {
+ .o-flex-display();
+ .o-justify-content(space-between);
+ > * {
+ flex: none;
+ }
+ }
+ &:hover {
+ .o_dropdown_kanban {
+ visibility: visible;
+ }
+ }
+ .o_dropdown_kanban {
+ @media (max-width: @grid-float-breakpoint) {
+ visibility: visible;
+ }
+ }
+ }
+
+ &.o_kanban_ungrouped {
+ .o-flex-flow(row, wrap);
+ .o-justify-content(flex-start);
+ padding: @o-kanban-record-margin @odoo-horizontal-padding - @o-kanban-record-margin;
+
+ @media (max-width: @grid-float-breakpoint) {
+ padding: 0;
+ }
+
+ .o_kanban_record {
+ .o-flex(1, 1, auto);
+ width: 320px;
+ @media (max-width: @grid-float-breakpoint) {
+ margin: 0 0 1px 0;
+ width: 100vw;
+ box-shadow: none;
+ }
+ }
+
+ .o_kanban_ghost {
+ height: 0;
+ min-height: 0 !important; // to prevent view writers to override this height
+ visibility: hidden;
+ margin-top: 0;
+ margin-bottom: 0;
+ padding: 0;
+ }
+
+ }
+
+ &.o_kanban_grouped {
+ min-height: 100%;
+ padding: 0 @odoo-horizontal-padding - @o-kanban-record-margin;
+ .o_kanban_record {
+ width: 300px;
+
+ &.o_kanban_small {
+ width: 240px;
+ }
+ }
+ }
+
+ .o_kanban_group {
+ .o-flex(0, 0, auto);
+ margin: 0 4px;
+
+ &.o_kanban_dragged {
+ background-color: @odoo-main-text-color;
+ .o_kanban_record {
+ visibility: hidden;
+ }
+ }
+
+ .o_kanban_header + .o_kanban_record {
+ margin-top: 0; // no margin top from the first card after kanban_header
+ }
+
+ .o_kanban_header {
+ color: white;
+ padding: 0 4px;
+ height: 50px;
+ line-height: 2.2;
+ cursor: move;
+ .o-flex-display();
+ .o_column_unfold {
+ display: none;
+ }
+ .o_column_title {
+ font-size: 18px;
+ font-weight: 300;
+ .o-flex(1, 1, auto);
+ white-space: nowrap;
+ padding-top: 5px;
+ }
+
+ .o-kanban-icon() {
+ // Share the style of `.o_kanban_quick_add` and `o_kanban_config`.
+ height: 50px;
+ width: 50px;
+ line-height: 1.8;
+ text-align: center;
+ color: white;
+ padding: 8px;
+ font-size: 22px;
+ cursor: pointer;
+ }
+
+ .o_kanban_quick_add i {
+ .o-kanban-icon;
+ &:hover {
+ background-color: @odoo-brand-optional;
+ }
+ }
+ .o_kanban_config i {
+ .o-kanban-icon;
+ &:hover {
+ background-color: darken(@gray-light, 10%);
+ }
+ }
+
+ .o_kanban_number {
+ color: @odoo-main-text-color;
+ text-align: center;
+ }
+ }
+
+ .o_kanban_record {
+ &.o_kanban_dragged {
+ > * {
+ visibility: hidden;
+ }
+ background-color: @odoo-main-text-color;
+ }
+ }
+
+ &.o_column_folded {
+ position: relative;
+ border-right: 1px solid @odoo-main-text-color;
+ margin: 0;
+
+ .o_column_title {
+ line-height: 1.3;
+ text-transform: uppercase;
+ transform-origin: left bottom;
+ transform: rotate(90deg);
+ top: 40px;
+ left: 15px;
+ position: absolute;
+ }
+ > .o_kanban_record, .o_kanban_quick_add, .o_kanban_config, .o_kanban_load_more {
+ display: none;
+ }
+ &:hover {
+ cursor: pointer;
+ background-color: darken(@gray-light, 10%);
+ }
+ .o_column_unfold {
+ display: block;
+ padding: 0 10px;
+
+ > i {
+ font-size: 22px;
+ padding-top: 15px;
+ }
+
+ }
+ background-color: @gray-light;
+ }
+
+ .o_kanban_load_more {
+ text-align: center;
+ a {
+ color: white;
+ font-weight: 300;
+ }
+ }
+ }
+
+ .oe_kanban_color_0 {
+ background-color: #FFFFFF;
+ color: #5a5a5a;
+ }
+ .oe_kanban_color_1 {
+ background-color: #d8d8d8;
+ color: #5e5e5e;
+ }
+ .oe_kanban_color_2 {
+ background-color: #e5d8e3;
+ color: #7a3737;
+ }
+ .oe_kanban_color_3 {
+ background-color: #ece9c2;
+ color: #756832;
+ }
+ .oe_kanban_color_4 {
+ background-color: #dee180;
+ color: #5d6937;
+ }
+ .oe_kanban_color_5 {
+ background-color: #abd4cc;
+ color: #1a7759;
+ }
+ .oe_kanban_color_6 {
+ background-color: #b8e3e8;
+ color: #1a5d83;
+ }
+ .oe_kanban_color_7 {
+ background-color: #f4ddab;
+ color: #5e5e5e;
+ }
+ .oe_kanban_color_8 {
+ background-color: #7f637d;
+ color: #a5a5a5;
+ }
+ .oe_kanban_color_9 {
+ background-color: #d8c5d5;
+ color: #5e5e5e;
+ }
+ .oe_kanban_color_10 {
+ background-color: #46C265;
+ color: #639B89;
+ }
+
+ .oe_tags {
+ display: block;
+ }
+ .o_kanban_tags {
+
+ margin-left: -8px;
+
+ .o_tag{
+ width: 50px;
+ height: 9px;
+ display: inline-block;
+ vertical-align: top;
+ }
+ }
+ .o_kanban_footer {
+ width: 100%;
+ }
+ .oe_kanban_avatar {
+ border-radius: 12px;
+ border: 1px solid @odoo-main-color-muted;
+ }
+
+ .o_progressbar {
+ .o_progressbar_title {
+ width: 20%;
+ }
+ .o_progress {
+ width: 60%;
+ height: 15px;
+ }
+ .o_progressbar_value {
+ width: 20%;
+ text-align: right;
+ }
+ }
+
+ .o_kanban_attachment_image {
+ // The image should take the hole card
+ margin: -8px;
+ > img {
+ width: 100%;
+ }
+ }
+}
+
+.oe_kanban_colorpicker {
+ li {
+ float: left;
+ &:first-child a {
+ border: 1px solid #ccc;
+ }
+
+ a {
+ width: 15px;
+ height: 16px;
+ display: inline-block;
+ border: 1px solid white;
+ }
+ }
+}
+
+.o_dropdown_kanban {
+ visibility: hidden;
+}
+
+.oe_kanban_avatar_smallbox {
+ height: 40px;
+ width: 40px;
+ box-shadow: 0 1px 4px @gray-light;
+ // border: 1px solid
+ // border-color: #e5e5e5 #dbdbdb #d2d2d2
+ // @include radius(3px)
+ // @include box-shadow(0 1px 2px rgba(0,0,0,0.2))
+}
+
+.o_kanban_shadow {
+ background-color: @odoo-main-text-color;
+ miw-width: 100px;
+}
+
+.o_column_quick_create {
+ .o-flex(0, 0, auto);
+ margin: 0;
+ padding: 8px;
+ position: relative;
+ background-color: @odoo-main-text-color;
+ cursor: pointer;
+
+ &:hover {
+ background-color: darken(@gray-light, 10%);
+ }
+ .o_column_header {
+ .o_kanban_add_column {
+ padding: 0 10px;
+ color: white;
+
+ > i {
+ font-size: 22px;
+ padding-top: 7px;
+ }
+ }
+ .o_kanban_title {
+ font-size: 18px;
+ font-weight: 300;
+ white-space: nowrap;
+ color: @gray-lighter;
+ text-transform: uppercase;
+ transform-origin: left bottom;
+ transform: rotate(90deg);
+ top: 40px;
+ left: 15px;
+ position: absolute;
+ }
+ }
+ .o_kanban_quick_create {
+ display: none;
+ width: 180px;
+ }
+}
+
+.oe_kanban_footer_left {
+ float: left;
+}
+
+.oe_kanban_bottom_right {
+ float: right;
+}
+
+.oe_kanban_bottom_left {
+ float: left;
+}
+
+.oe_kanban_status {
+ display: inline-block;
+ height: 12px;
+ width: 12px;
+ border-radius: 6px;
+
+ background-color: @odoo-brand-secondary;
+
+ &.oe_kanban_status_green {
+ background-color: @brand-success;
+ }
+ &.oe_kanban_status_red {
+ background-color: @brand-danger;
+ }
+}
+
+.o_form_field_one2many .oe_view_nocontent {
+ display: none;
+}
\ No newline at end of file
diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml
index 0a7bbcdcffb..497b0696025 100644
--- a/addons/web_kanban/static/src/xml/web_kanban.xml
+++ b/addons/web_kanban/static/src/xml/web_kanban.xml
@@ -1,90 +1,72 @@
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/addons/web_kanban/views/web_kanban.xml b/addons/web_kanban/views/web_kanban.xml
index b9fb5dff577..f383254636d 100644
--- a/addons/web_kanban/views/web_kanban.xml
+++ b/addons/web_kanban/views/web_kanban.xml
@@ -3,11 +3,14 @@
-
+
+
+
+
-
+
From 01c3dfa7d25633d764ff99495271a054e75a7af2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A9ry=20Debongnie?=
Date: Thu, 11 Jun 2015 11:05:27 +0200
Subject: [PATCH 02/17] [IMP] web: add pager widget to web client
This pager is used by the kanban view. It could be used by the list
view, the form view and some others (and it is, in the enterprise
edition)
---
addons/web/static/src/js/widgets/pager.js | 111 ++++++++++++++++++++++
addons/web/static/src/xml/base.xml | 9 ++
2 files changed, 120 insertions(+)
create mode 100644 addons/web/static/src/js/widgets/pager.js
diff --git a/addons/web/static/src/js/widgets/pager.js b/addons/web/static/src/js/widgets/pager.js
new file mode 100644
index 00000000000..c195aaf530d
--- /dev/null
+++ b/addons/web/static/src/js/widgets/pager.js
@@ -0,0 +1,111 @@
+odoo.define('web.Pager', function (require) {
+"use strict";
+
+var Widget = require('web.Widget');
+
+var direction = {
+ previous: -1,
+ next: 1,
+};
+
+var Pager = Widget.extend({
+ template: "Pager",
+
+ events: {
+ 'click .o-pager-previous': 'previous',
+ 'click .o-pager-next': 'next',
+ },
+
+ // pager goes from 1 to size (included).
+ // current value is current_min (if limit === 1)
+ // or the interval [current_min, current_min + limit[ if limit > 1
+ init: function (parent, size, current_min, limit, options) {
+ this.state = {
+ size: size,
+ current_min: current_min,
+ limit: limit,
+ };
+ Object.defineProperty(this.state, 'current_max', {
+ get: function() {
+ return Math.min(this.current_min + this.limit - 1, this.size);
+ }
+ });
+ this.options = options || {};
+ this._super(parent);
+ },
+
+ start: function () {
+ this.$content = this.$('.o-pager-value');
+ this._render();
+ return this._super();
+ },
+
+ set_state: function (options) {
+ _.extend(this.state, options);
+ this._render();
+ },
+
+ previous: function(e) {
+ this._change_selection(direction.previous);
+ },
+
+ next: function() {
+ this._change_selection(direction.next);
+ },
+
+ disable: function() {
+ this.$('button').prop("disabled", true);
+ },
+
+ enable: function() {
+ this.$('button').prop("disabled", false);
+ },
+
+ _render: function () {
+ var state;
+ var size = this.state.size;
+ var current_min = this.state.current_min;
+ var current_max = this.state.current_max;
+ var single_page = 1 === current_min && current_max === size;
+
+ if (size === 0 || (single_page && this.options.single_page_hidden)) {
+ this.$el.hide();
+ } else {
+ this.$el.show();
+
+ if (single_page) {
+ this.disable();
+ } else {
+ this.enable();
+ }
+
+ if (this.state.limit === 1) {
+ state = "" + current_min + " / " + size;
+ } else {
+ state = "" + current_min + "-" + current_max + " / " + size;
+ }
+ this.$content.html(state);
+ }
+ },
+
+ _change_selection: function (direction) {
+ var size = this.state.size;
+ var current_min = this.state.current_min;
+ var limit = this.state.limit;
+ current_min = (current_min + limit*direction);
+ if (current_min > size) {
+ current_min = 1;
+ } else if ((current_min < 1) && (limit === 1)) {
+ current_min = size;
+ } else if ((current_min < 1) && (limit > 1)) {
+ current_min = size - ((size % limit) || limit) + 1;
+ }
+ this.state.current_min = current_min;
+ this.trigger('pager_changed', _.clone(this.state));
+ this._render();
+ },
+});
+
+return Pager;
+
+});
\ No newline at end of file
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 00406837a47..27ce7dae6d4 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -1946,6 +1946,15 @@
+
+
+
+
+
+
+
+
+
From c530c0d1ee179a9660e539a6e5249db0e4a49714 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A9ry=20Debongnie?=
Date: Thu, 11 Jun 2015 11:05:46 +0200
Subject: [PATCH 03/17] [FIX] web: various tweaks to make web_kanban view work
---
addons/web/static/src/js/framework/core.js | 5 +
.../web/static/src/js/views/form_widgets.js | 3 +-
.../src/less/enterprise_compatibility.less | 195 ++++++++++++++++++
addons/web/static/src/less/utils.less | 189 +++++++++++++++++
addons/web/static/src/xml/base.xml | 14 +-
addons/web/views/webclient_templates.xml | 3 +
6 files changed, 406 insertions(+), 3 deletions(-)
create mode 100644 addons/web/static/src/less/enterprise_compatibility.less
create mode 100644 addons/web/static/src/less/utils.less
diff --git a/addons/web/static/src/js/framework/core.js b/addons/web/static/src/js/framework/core.js
index 7bd4800ec98..30138102663 100644
--- a/addons/web/static/src/js/framework/core.js
+++ b/addons/web/static/src/js/framework/core.js
@@ -186,6 +186,11 @@ return {
list_widget_registry: new Registry(),
search_widgets_registry: new Registry(),
search_filters_registry: new Registry(),
+
+ // necessary to make the kanban view compatible between
+ // community and enterprise edition
+ one2many_view_registry: new Registry(),
+
};
diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js
index ee04f8376b4..760ea703d35 100644
--- a/addons/web/static/src/js/views/form_widgets.js
+++ b/addons/web/static/src/js/views/form_widgets.js
@@ -285,7 +285,7 @@ var Priority = FieldChar.extend({
},
render_value: function() {
this.priorities = this.prepare_priority();
- this.$el.html(QWeb.render("Priority", {'widget': this}));
+ this.$el.html(QWeb.render("FormPriority", {'widget': this}));
if (!this.get('readonly')){
this.$el.find('li').on('click', this.set_priority.bind(this));
}
@@ -1584,6 +1584,7 @@ var StatInfo = common.AbstractField.extend({
}
}
this.$el.html(QWeb.render("StatInfo", options));
+ this.$el.addClass('o_stat_info');
},
});
diff --git a/addons/web/static/src/less/enterprise_compatibility.less b/addons/web/static/src/less/enterprise_compatibility.less
new file mode 100644
index 00000000000..1cfd5120c34
--- /dev/null
+++ b/addons/web/static/src/less/enterprise_compatibility.less
@@ -0,0 +1,195 @@
+// ------------------------------------------------------------------
+// Odoo
+// ------------------------------------------------------------------
+
+// This file is necessary to make the community edition of the Odoo web
+// client compatible with the enterprise edition.
+
+// The problem is that we have two web clients in parallel, and every
+// addons should behave properly with both.
+
+@odoo-brand-optional-disabled: #6ED0BC; // Specifically used for status bar status arrows
+@odoo-brand-primary: #337ab7;
+@odoo-brand-optional: @odoo-brand-primary;
+@odoo-brand-secondary: #8f8f8f;
+@odoo-form-group-cols: 12;
+@odoo-horizontal-padding: 16px;
+@odoo-list-grey: #e2e2e0; // FIXME: listview thead background color
+@odoo-main-color-muted: #a8a8a8;
+@odoo-main-text-color: #666666;
+@odoo-webclient-background-color: @odoo-brand-secondary;
+@odoo-view-background-color: red;
+@odoo-shadow-color: #303030;
+@brand-primary: @odoo-brand-primary;
+
+.oe-view-manager {
+ background-color: #f0eeee !important;
+}
+
+.o_kanban_view {
+ background-color: #f0eeee;
+ .o_kanban_header {
+ color: black !important;
+ }
+
+ .o_kanban_config i, .o_kanban_quick_add i {
+ color: #555 !important;
+ &:hover {
+ background-color: darken(#f0eeee, 10%) !important;
+ }
+ }
+
+ .o_column_folded {
+ background-color: #f0eeee !important;
+ border: none !important;
+ span.o_column_title, span.o_column_unfold {
+ // color: white !important;
+ }
+ }
+
+ .o_column_quick_create {
+ background-color: #f0eeee !important;
+ .o_kanban_add_column, .o_kanban_title {
+ color: black !important;
+ }
+
+ }
+}
+
+.oe_list {
+ background-color: white !important;
+}
+
+.btn-icon {
+ .btn-default;
+}
+
+.openerp .oe_avatar {
+ float: left;
+}
+
+.openerp .oe_button_box {
+ float: right;
+}
+
+@grid-gutter-width: 0;
+
+.oe-control-panel .row {
+ margin-left: 15px;
+ margin-right: 15px;
+}
+
+.oe_form_field_one2many {
+ .oe-view-manager {
+ background-color: white;
+
+ .o_kanban_view {
+ background-color: white;
+
+ .o_kanban_record {
+ box-shadow: none;
+ }
+ }
+ }
+}
+
+// Buttons containing only an icon
+.o_icon_button {
+ background-color: transparent;
+ border: 0;
+ padding: 0;
+ outline: none;
+}
+
+// Stars
+.o_priority {
+ display: inline-block;
+ padding: 0;
+ margin: 0;
+ > a {
+ display: inline-block;
+ }
+ .o_star_on, .o_star_off {
+ color: @odoo-main-color-muted !important;
+ &:hover {
+ text-decoration: none;
+ }
+ }
+ .o_star_on {
+ color: gold !important;
+ }
+}
+
+.o_text_overflow {
+ display: block;
+ max-width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: top; // To update display context changed by overflow:hidden
+}
+
+.oe-o2m-control-panel {
+ background-color: white;
+}
+
+.oe_formview {
+ .oe_button_box {
+ .oe_stat_button {
+
+ .stat_button_icon {
+ display: inline-block;
+ }
+
+ // padding: @odoo-statbutton-vpadding @odoo-statbutton-spacing @odoo-statbutton-vpadding 0px;
+ text-align: left;
+
+ .o_button_icon {
+ // margin-left: @odoo-statbutton-spacing; // To create the button padding left (firefox bug)
+ display: inline-block;
+ vertical-align: middle;
+ &:before {
+ font-size: x-large;
+ vertical-align: middle;
+ }
+
+ width: 30%;
+ }
+
+ // .o_form_field_percent_pie {
+ // margin-left: @odoo-statbutton-spacing; // To create the button padding left (firefox bug)
+ // }
+
+ .o_stat_info { // contains the value and text
+ display: inline-block;
+ vertical-align: middle;
+
+ max-width: 70%;
+ // padding-right: @odoo-statbutton-spacing;
+ line-height: 1.2;
+
+ .o_stat_value {
+ .o_text_overflow;
+ font-weight: 700;
+ color: @odoo-brand-primary;
+ }
+ .o_stat_text {
+ .o_text_overflow;
+ font-size: small;
+
+ .o_stat_value {
+ display: inline;
+ }
+ }
+ }
+
+
+ // Some buttons only display text and directly use the WidgetButton template instead of StatInfo
+ > span {
+ .o_stat_info; // layout
+ font-size: small;
+ white-space: normal; // text on several lines if needed
+ }
+ }
+ }
+}
diff --git a/addons/web/static/src/less/utils.less b/addons/web/static/src/less/utils.less
new file mode 100644
index 00000000000..8b8d02898e6
--- /dev/null
+++ b/addons/web/static/src/less/utils.less
@@ -0,0 +1,189 @@
+// ------------------------------------------------------------------
+// Margin
+// ------------------------------------------------------------------
+.utils-margin-loop(@index) when (@index < 256) {
+ .o-mt@{index} {margin-top: @index px;}
+ .o-mb@{index} {margin-bottom: @index px;}
+ .o-mr@{index} {margin-right: @index px;}
+ .o-ml@{index} {margin-left: @index px;}
+
+ .utils-margin-loop(@index * 2);
+}
+.utils-margin-loop(2);
+
+// ------------------------------------------------------------------
+// Padding
+// ------------------------------------------------------------------
+.o-webclient-padding(@top: 0px, @right: @odoo-horizontal-padding, @bottom: 0px, @left: @odoo-horizontal-padding) {
+ padding-top: @top;
+ padding-right: @right;
+ padding-bottom: @bottom;
+ padding-left: @left;
+}
+
+// ------------------------------------------------------------------
+// Caret
+// ------------------------------------------------------------------
+.utils-caret-boilerplate() {
+ content: "";
+ display: inline-block;
+ width: 0;
+ height: 0;
+ vertical-align: middle;
+}
+.o-caret-down(@caret-width: @caret-width-base) {
+ .utils-caret-boilerplate;
+ border-bottom: 0;
+ border-left: @caret-width solid transparent;
+ border-right: @caret-width solid transparent;
+ border-top: @caret-width solid;
+ -moz-transform: scale(0.9999); // Smooth the caret on firefox
+}
+.o-caret-up(@caret-width: @caret-width-base) {
+ .utils-caret-boilerplate;
+ border-bottom: @caret-width solid;
+ border-left: @caret-width solid transparent;
+ border-right: @caret-width solid transparent;
+ border-top: 0;
+ -moz-transform: scale(0.9999); // Smooth the caret on firefox
+}
+.o-caret-left(@caret-width: @caret-width-base) {
+ .utils-caret-boilerplate;
+ border-bottom: @caret-width solid transparent;
+ border-left: 0;
+ border-right: @caret-width solid;
+ border-top: @caret-width solid transparent;
+ -moz-transform: scale(0.9999); // Smooth the caret on firefox
+}
+.o-caret-right(@caret-width: @caret-width-base) {
+ .utils-caret-boilerplate;
+ border-bottom: @caret-width solid transparent;
+ border-left: @caret-width solid;
+ border-right: 0;
+ border-top: @caret-width solid transparent;
+ -moz-transform: scale(0.9999); // Smooth the caret on firefox
+}
+
+// ------------------------------------------------------------------
+// Vendor prefixes
+// ------------------------------------------------------------------
+// Flex
+.o-flex-display() {
+ display: -ms-flexbox;
+ display: -moz-box;
+ display: -webkit-box;
+ display: -webkit-flex;
+
+ display: flex;
+}
+.o-inline-flex-display() {
+ display: -ms-inline-flexbox;
+ display: -moz-inline-box;
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+
+ display: inline-flex;
+}
+.o-flex-flow(@direction, @wrap) {
+ -ms-flex-flow: @direction @wrap;
+ -moz-flex-flow: @direction @wrap;
+ -webkit-flex-flow: @direction @wrap;
+
+ flex-flow: @direction @wrap;
+}
+.o-flex(@grow, @schrink, @basis) {
+ -ms-flex: @grow @schrink @basis;
+ -moz-flex: @grow @schrink @basis;
+ -webkit-box-flex: @grow;
+ -webkit-flex: @grow @schrink @basis;
+
+ flex: @grow @schrink @basis;
+}
+.o-justify-content(@justify) {
+ -moz-justify-content: @justify;
+ -webkit-justify-content: @justify;
+
+ justify-content: @justify;
+}
+.o-align-content(@align) {
+ -webkit-align-content: @align;
+
+ align-content: @align;
+}
+.o-align-items(@align) {
+ -webkit-align-items: @align;
+
+ align-items: @align;
+}
+.o-align-self(@align) {
+ -webkit-align-self: @align;
+
+ align-self: @align;
+}
+
+// Background-size
+// https://css-tricks.com/perfect-full-page-background-image/
+.o-background-size(@size) {
+ -webkit-background-size: @size;
+ -moz-background-size: @size;
+ -o-background-size: @size;
+ background-size: @size;
+}
+
+// ------------------------------------------------------------------
+// Dropdowns
+// ------------------------------------------------------------------
+.o-selected-li() {
+ li {
+ position: relative;
+ > a {
+ padding: 3px 25px;
+ color: @odoo-main-text-color;
+ &:hover {
+ color: darken(@odoo-main-text-color, 10%);
+ }
+ }
+ &.selected {
+ > a {
+ color: darken(@odoo-main-text-color, 10%);
+ font-weight: bold;
+ &:before {
+ font-family: FontAwesome;
+ position: absolute;
+ left: 6px;
+ content: "\f00c";
+ }
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------
+// Select Appearance
+// ------------------------------------------------------------------
+.o-appearance(@value) {
+ -webkit-appearance: @value;
+ -moz-appearance: @value;
+ appearance: @value;
+}
+
+// ------------------------------------------------------------------
+// Transform
+// ------------------------------------------------------------------
+.o-transform-origin(@x: 50%, @y: 50%, @z: 0) {
+ -ms-transform-origin: @x @y @z;
+ -webkit-transform-origin: @x @y @z;
+ -moz-transform-origin: @x @y @z;
+ transform-origin: @x @y @z;
+}
+
+// ------------------------------------------------------------------
+// Position absolute
+// ------------------------------------------------------------------
+.o-position-absolute(@top: auto, @right: auto, @bottom: auto, @left: auto) {
+ position: absolute;
+ top: @top;
+ left: @left;
+ bottom: @bottom;
+ right: @right;
+}
\ No newline at end of file
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 27ce7dae6d4..52a7c8130a6 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -1104,7 +1104,7 @@
-
+
+
+
+
@@ -1938,8 +1946,10 @@
-
+
+
+
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 3b935733230..b99979f2b04 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -73,6 +73,8 @@
+
+
@@ -138,6 +140,7 @@
+
From 4072e336a296a8da873b2b69cfb00d379a41c67c Mon Sep 17 00:00:00 2001
From: Aaron Bohy
Date: Fri, 15 May 2015 11:14:46 +0200
Subject: [PATCH 04/17] [FIX] Cross-addons: Kanban templates: records
editable/deletable
for the sake of compatibility with the communication logic in the
new Kanban implementation.
---
.../account/views/account_journal_dashboard_view.xml | 2 +-
addons/crm/crm_lead_view.xml | 4 ++--
addons/event/event_view.xml | 2 +-
addons/hr/hr_dashboard.xml | 2 +-
addons/hr_equipment/views/hr_equipment_views.xml | 8 ++++----
addons/hr_recruitment/hr_recruitment_dashboard.xml | 2 +-
addons/hr_recruitment/hr_recruitment_view.xml | 2 +-
addons/mass_mailing/views/email_template.xml | 6 +++---
addons/mass_mailing/views/mass_mailing.xml | 6 +++---
addons/project/project_dashboard.xml | 2 +-
addons/project/project_view.xml | 4 ++--
addons/project_issue/project_issue_view.xml | 4 ++--
addons/sale_crm/sale_crm_view.xml | 8 ++++----
addons/sales_team/sales_team_dashboard.xml | 2 +-
addons/stock/stock_dashboard.xml | 2 +-
addons/survey/views/survey_views.xml | 4 ++--
addons/website/static/src/css/website.backend.css | 6 +++---
addons/website/static/src/js/website.backend.js | 9 ++++++---
addons/website/static/src/xml/website.backend.xml | 10 ++++------
addons/website_event_track/views/event_backend.xml | 4 ++--
addons/website_forum_doc/views/doc.xml | 4 ++--
21 files changed, 47 insertions(+), 46 deletions(-)
diff --git a/addons/account/views/account_journal_dashboard_view.xml b/addons/account/views/account_journal_dashboard_view.xml
index 78ccd40bcb7..5ebe9e36659 100644
--- a/addons/account/views/account_journal_dashboard_view.xml
+++ b/addons/account/views/account_journal_dashboard_view.xml
@@ -194,7 +194,7 @@
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index 42b20752a91..e1a17a245d0 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -307,8 +307,8 @@
-
+
diff --git a/addons/hr_equipment/views/hr_equipment_views.xml b/addons/hr_equipment/views/hr_equipment_views.xml
index ef61997f586..9797223194d 100644
--- a/addons/hr_equipment/views/hr_equipment_views.xml
+++ b/addons/hr_equipment/views/hr_equipment_views.xml
@@ -92,8 +92,8 @@
@@ -265,8 +265,8 @@
diff --git a/addons/hr_recruitment/hr_recruitment_dashboard.xml b/addons/hr_recruitment/hr_recruitment_dashboard.xml
index 75d4a4564a1..e0965de2039 100644
--- a/addons/hr_recruitment/hr_recruitment_dashboard.xml
+++ b/addons/hr_recruitment/hr_recruitment_dashboard.xml
@@ -123,7 +123,7 @@
-
+
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml
index c3685c5f880..e0b9bcbbba4 100644
--- a/addons/hr_recruitment/hr_recruitment_view.xml
+++ b/addons/hr_recruitment/hr_recruitment_view.xml
@@ -268,7 +268,7 @@
diff --git a/addons/mass_mailing/views/email_template.xml b/addons/mass_mailing/views/email_template.xml
index 73fb508b44b..5367e043383 100644
--- a/addons/mass_mailing/views/email_template.xml
+++ b/addons/mass_mailing/views/email_template.xml
@@ -50,11 +50,11 @@
-
-
+
\ No newline at end of file
diff --git a/addons/sale/res_partner_view.xml b/addons/sale/res_partner_view.xml
index 2f08c015d2a..3c6fb2468f4 100644
--- a/addons/sale/res_partner_view.xml
+++ b/addons/sale/res_partner_view.xml
@@ -34,9 +34,7 @@
-
- Sales
-
+
@@ -48,16 +46,15 @@
-
-
+
-
+
-
diff --git a/addons/website_partner/views/res_partner_view.xml b/addons/website_partner/views/res_partner_view.xml
index 10f0dd0e9de..343733faa7c 100644
--- a/addons/website_partner/views/res_partner_view.xml
+++ b/addons/website_partner/views/res_partner_view.xml
@@ -10,15 +10,15 @@
-
-
+
-
+
-
+
\ No newline at end of file
diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml
index 3f94301bfd6..e4a0df2f374 100644
--- a/openerp/addons/base/res/res_partner_view.xml
+++ b/openerp/addons/base/res/res_partner_view.xml
@@ -114,8 +114,8 @@
-
-
+
+
@@ -128,23 +128,25 @@
-
diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml
index e4e9a752fc6..36ff5bccf03 100644
--- a/addons/project/project_view.xml
+++ b/addons/project/project_view.xml
@@ -7,6 +7,7 @@
id="base.menu_main_pm"
groups="group_project_manager,group_project_user"
icon="fa-calendar"
+ web_icon="project,static/description/icon.png"
sequence="50"/>
+
+
+
+
+
+
+
+
@@ -102,15 +112,6 @@
-
-
-
-
-
-
-
-
@@ -254,7 +255,6 @@
-
project.task.form
project.task
@@ -266,17 +266,15 @@
options="{'fold_field': 'fold'}"/>
-
@@ -357,29 +355,35 @@
+
-
-
-
@@ -571,7 +577,7 @@
You can also give a tooltip about the use of the stars available in the kanban and form views.
-
+
@@ -685,4 +691,4 @@
-
+
\ No newline at end of file
diff --git a/addons/project/static/src/less/project_dashboard.less b/addons/project/static/src/less/project_dashboard.less
index 9ce363aea0a..e47f36a4f0a 100644
--- a/addons/project/static/src/less/project_dashboard.less
+++ b/addons/project/static/src/less/project_dashboard.less
@@ -1,45 +1,37 @@
-.oe_kanban_view.o_kanban_dashboard.o_project_kanban {
-
- @odoo-brand-optional: #21b799;
+.o_kanban_view.o_kanban_dashboard.o_project_kanban {
.o_kanban_flexbox {
- display: flex;
+ .o-flex-display();
position: relative;
}
- .oe_kanban_record {
+ .o_kanban_record {
min-width: 300px;
- max-width: 350px;
}
- /*
- .oe_kanban_record:hover .o_project_kanban_manage {
- visibility: visible;
- }
- */
-
.o_project_kanban_main {
- margin-bottom: 40px; // give space for manage section at the bottom
- width: 75%;
+ // give space for manage section at the bottom
+ margin-bottom: 40px;
+ width: 70%;
- .o_primary {
- font-size: larger;
+ .o_kanban_card_content {
+ font-size: 12px;
+ .o_primary {
+ font-size: larger;
+ }
}
.o_project_kanban_manage {
visibility: visible;
position: absolute;
bottom: 0;
- margin-left: 10px;
+ margin-left: 16px;
margin-bottom: 10px;
}
.o_project_kanban_colorpicker {
padding: 8px;
}
.o_kanban_card_manage_pane {
- border-color: lightgrey;
- border-bottom-style: solid;
- border-bottom-width: 1px;
.o_kanban_card_manage_title {
margin: 0;
@@ -48,12 +40,16 @@
}
.o_project_kanban_boxes {
- display: flex;
- flex-direction: column;
- justify-content: flex-end;
- align-content: stretch;
+ .o-flex-display();
+ .o-flex-flow(column, nowrap);
+ .o-justify-content(flex-end);
+ .o-align-content(stretch);
- width: 25%;
+ width: 30%;
+
+ > a:hover {
+ text-decoration: none;
+ }
.o_value {
font-size: x-large;
@@ -67,11 +63,11 @@
.o_project_kanban_box {
text-align: center;
padding: 15px 0 15px 0;
- flex: 1 1 auto;
- display: flex;
- align-items: center;
- flex-direction: column;
- justify-content: center;
+ .o-flex(1, 1, auto);
+ .o-flex-display();
+ .o-align-items(center);
+ .o-flex-flow(column, nowrap);
+ .o-justify-content(center);
}
.o_project_kanban_box:nth-child(even) {
background-color: grey;
@@ -80,4 +76,4 @@
background-color: @odoo-brand-optional;
}
}
-}
+}
\ No newline at end of file
diff --git a/addons/rating_project/views/project_view.xml b/addons/rating_project/views/project_view.xml
index 8322dd0b096..8f56709d83f 100644
--- a/addons/rating_project/views/project_view.xml
+++ b/addons/rating_project/views/project_view.xml
@@ -8,7 +8,7 @@
-
+
-
+
@@ -38,4 +38,4 @@
-
+
\ No newline at end of file
From 461e3d6784962f666b32ab3f1d71ff6eec530d54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A9ry=20Debongnie?=
Date: Fri, 12 Jun 2015 13:45:31 +0200
Subject: [PATCH 10/17] [FIX] various: update some js modules to new kanban
The kanban view has been split in various files, and js modules
using them need to correctly import the classes.
---
addons/hr/static/src/js/hr.js | 8 --------
addons/hr/views/hr.xml | 1 -
addons/hr_gamification/static/src/js/gamification.js | 6 +++---
addons/hr_recruitment/static/src/js/job_position.js | 6 +++---
addons/mass_mailing/static/src/js/mass_mailing.js | 6 +++---
addons/project/static/src/js/project.js | 6 +++---
addons/rating/static/src/js/rating.js | 6 +++---
addons/website_crm_score/static/src/js/display_filters.js | 6 +++---
8 files changed, 18 insertions(+), 27 deletions(-)
delete mode 100644 addons/hr/static/src/js/hr.js
diff --git a/addons/hr/static/src/js/hr.js b/addons/hr/static/src/js/hr.js
deleted file mode 100644
index 9fda01cef95..00000000000
--- a/addons/hr/static/src/js/hr.js
+++ /dev/null
@@ -1,8 +0,0 @@
-odoo.define('hr.hr', function(require) {
-"use strict";
-
-var common = require('web_kanban.common');
-
-
-});
-
diff --git a/addons/hr/views/hr.xml b/addons/hr/views/hr.xml
index 44fb6d8655e..e76ec3cc4ce 100644
--- a/addons/hr/views/hr.xml
+++ b/addons/hr/views/hr.xml
@@ -6,7 +6,6 @@
-
diff --git a/addons/hr_gamification/static/src/js/gamification.js b/addons/hr_gamification/static/src/js/gamification.js
index 765acb257e4..a7a0201900a 100644
--- a/addons/hr_gamification/static/src/js/gamification.js
+++ b/addons/hr_gamification/static/src/js/gamification.js
@@ -1,11 +1,11 @@
odoo.define('hr_gamification.hr_gamification', function(require) {
"use strict";
-var common = require('web_kanban.common');
+var KanbanRecord = require('web_kanban.Record');
-common.KanbanRecord.include({
+KanbanRecord.include({
on_card_clicked: function() {
- if (this.view.dataset.model === 'gamification.badge.user') {
+ if (this.model === 'gamification.badge.user') {
var action = {
type: 'ir.actions.act_window',
res_model: 'gamification.badge',
diff --git a/addons/hr_recruitment/static/src/js/job_position.js b/addons/hr_recruitment/static/src/js/job_position.js
index 9361c66cf7d..214f161ad99 100644
--- a/addons/hr_recruitment/static/src/js/job_position.js
+++ b/addons/hr_recruitment/static/src/js/job_position.js
@@ -1,11 +1,11 @@
odoo.define('hr_recruitment.hr_recruitment', function(require) {
"use strict";
-var common = require('web_kanban.common');
+var KanbanRecord = require('web_kanban.Record');
-common.KanbanRecord.include({
+KanbanRecord.include({
on_card_clicked: function() {
- if (this.view.dataset.model === 'hr.job') {
+ if (this.model === 'hr.job') {
this.$('.oe_applications a').first().click();
} else {
this._super.apply(this, arguments);
diff --git a/addons/mass_mailing/static/src/js/mass_mailing.js b/addons/mass_mailing/static/src/js/mass_mailing.js
index b43a138f2c2..3785cf87070 100644
--- a/addons/mass_mailing/static/src/js/mass_mailing.js
+++ b/addons/mass_mailing/static/src/js/mass_mailing.js
@@ -1,14 +1,14 @@
odoo.define('mass_mailing.mass_mailing', function (require) {
var core = require('web.core');
-var common = require('web_kanban.common');
+var KanbanRecord = require('web_kanban.Record');
var KanbanView = require('web_kanban.KanbanView');
var _t = core._t;
-common.KanbanRecord.include({
+KanbanRecord.include({
on_card_clicked: function (event) {
- if (this.view.dataset.model === 'mail.mass_mailing.campaign') {
+ if (this.model === 'mail.mass_mailing.campaign') {
this.$('.oe_mailings').click();
} else {
this._super.apply(this, arguments);
diff --git a/addons/project/static/src/js/project.js b/addons/project/static/src/js/project.js
index 20927cebc2c..515918df0b0 100644
--- a/addons/project/static/src/js/project.js
+++ b/addons/project/static/src/js/project.js
@@ -1,14 +1,14 @@
odoo.define('project.update_kanban', function (require) {
-var kanban_common = require('web_kanban.common');
+var KanbanRecord = require('web_kanban.Record');
var KanbanView = require('web_kanban.KanbanView');
var data = require('web.data');
var session = require('web.session');
-kanban_common.KanbanRecord.include({
+KanbanRecord.include({
on_card_clicked: function() {
- if (this.view.dataset.model === 'project.project') {
+ if (this.model === 'project.project') {
this.$('.o_project_kanban_boxes a').first().click();
} else {
this._super.apply(this, arguments);
diff --git a/addons/rating/static/src/js/rating.js b/addons/rating/static/src/js/rating.js
index 87f16cd1a50..91447bd1be7 100644
--- a/addons/rating/static/src/js/rating.js
+++ b/addons/rating/static/src/js/rating.js
@@ -1,11 +1,11 @@
odoo.define('rating.kanban', function(require) {
"use strict";
-var common = require('web_kanban.common');
+var KanbanRecord = require('web_kanban.Record');
-common.KanbanRecord.include({
+KanbanRecord.include({
on_card_clicked: function() {
- if (this.view.dataset.model === 'rating.rating') {
+ if (this.model === 'rating.rating') {
this.$('a').first().click();
} else {
this._super.apply(this, arguments);
diff --git a/addons/website_crm_score/static/src/js/display_filters.js b/addons/website_crm_score/static/src/js/display_filters.js
index 859ec6fd218..fc0eea59dda 100644
--- a/addons/website_crm_score/static/src/js/display_filters.js
+++ b/addons/website_crm_score/static/src/js/display_filters.js
@@ -1,10 +1,10 @@
odoo.define('website_crm_score.filters', function (require) {
"use strict";
-var common = require('web_kanban.common');
+var kanban_widgets = require('web_kanban.widgets');
-var Filters = common.AbstractField.extend({
+var Filters = kanban_widgets.AbstractField.extend({
/**
bnf grammar of a filter:
::=
@@ -95,6 +95,6 @@ var Filters = common.AbstractField.extend({
}
});
-common.registry.add('filters', Filters);
+kanban_widgets.registry.add('filters', Filters);
});
From d6fa3e3709e07b99e703ed207d2b2a58ae0da925 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A9ry=20Debongnie?=
Date: Fri, 12 Jun 2015 13:47:31 +0200
Subject: [PATCH 11/17] [IMP] base: improve module view template
---
openerp/addons/base/module/module_view.xml | 18 ++++++++--------
.../addons/base/static/src/css/modules.css | 21 +++++++++++--------
2 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/openerp/addons/base/module/module_view.xml b/openerp/addons/base/module/module_view.xml
index 99b29647fe8..00716c073d8 100644
--- a/openerp/addons/base/module/module_view.xml
+++ b/openerp/addons/base/module/module_view.xml
@@ -80,12 +80,12 @@
By
-
-
-
-
-
-
+
+
+
+
+
+
@@ -170,8 +170,8 @@
- Install
- Installed
+ Install
+ Installed
@@ -209,4 +209,4 @@
-
+
\ No newline at end of file
diff --git a/openerp/addons/base/static/src/css/modules.css b/openerp/addons/base/static/src/css/modules.css
index 9d31e8cf7b1..3df2e344dc9 100644
--- a/openerp/addons/base/static/src/css/modules.css
+++ b/openerp/addons/base/static/src/css/modules.css
@@ -1,19 +1,26 @@
.oe_module_vignette {
padding: 4px;
+ display: flex;
}
-.oe_module_icon, .oe_module_desc {
- display: inline-block;
- vertical-align: top;
-}
+
.oe_module_icon {
width: 80px;
height: 80px;
+ flex-shrink: 0;
+ flex-grow: 0;
}
.oe_module_desc {
- width: 210px;
font-size: 13px;
padding: 0px 5px;
+ flex-grow: 1;
+ position: relative;
+}
+
+.oe_module_desc > button {
+ position: absolute;
+ bottom: 0;
+ right: 0;
}
.oe_module_desc h4 {
@@ -33,7 +40,3 @@
margin: 3px 0 5px;
}
-.oe_module_desc .oe_button {
- min-width: 70px;
-}
-
From efe9e0241f9497aad9b6fd9ec605a3809b202ae4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A9ry=20Debongnie?=
Date: Fri, 12 Jun 2015 14:13:20 +0200
Subject: [PATCH 12/17] [IMP] hr: update some templates to new design
---
addons/hr/hr_view.xml | 84 +++++++-------
addons/hr/static/src/less/hr_dashboard.less | 16 ++-
addons/hr_recruitment/hr_recruitment_view.xml | 103 +++++++++---------
3 files changed, 105 insertions(+), 98 deletions(-)
diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml
index be7dfecdfdf..3c350723453 100644
--- a/addons/hr/hr_view.xml
+++ b/addons/hr/hr_view.xml
@@ -23,6 +23,7 @@