summaryrefslogtreecommitdiff
path: root/addons/web_kanban/static/src
diff options
context:
space:
mode:
authorGéry Debongnie <ged@odoo.com>2015-06-15 16:31:27 +0200
committerGéry Debongnie <ged@odoo.com>2015-06-15 16:31:27 +0200
commit2c7367a8cc8d9e200b1fa2083ce0d80f8f4e4bd3 (patch)
treeaf916cff4d94554b1e9667913e3feb331ec81a13 /addons/web_kanban/static/src
parent5cb10d4fc9594f0c6825757c24d66d63664ce026 (diff)
parent82186a93bb3f93c36667835df68fa31dcacf97a2 (diff)
[MERGE] new kanban view
This branch introduces the new kanban view. It is not completely compatible with the previous version. Here are a few importants points to be aware of: * the kanban view template is rendered directly, not put inside a div. It means that if a template has more than one root, chaos will ensue * kanban view has been split into several files, and various js modules have changed. For example, the KanbanRecord class is now exported by the 'web_kanban.Record' module. So, require statements need to be updated * records do not have access to the view (i.e., record.view.is_action_enabled('edit')) You can use the 'editable' and 'deletable' key instead
Diffstat (limited to 'addons/web_kanban/static/src')
-rw-r--r--addons/web_kanban/static/src/css/kanban.css8
-rw-r--r--addons/web_kanban/static/src/css/kanban.sass2
-rw-r--r--addons/web_kanban/static/src/js/compatibility.js10
-rw-r--r--addons/web_kanban/static/src/js/kanban_column.js245
-rw-r--r--addons/web_kanban/static/src/js/kanban_common.js928
-rw-r--r--addons/web_kanban/static/src/js/kanban_quick_create.js127
-rw-r--r--addons/web_kanban/static/src/js/kanban_record.js275
-rw-r--r--addons/web_kanban/static/src/js/kanban_view.js1153
-rw-r--r--addons/web_kanban/static/src/js/kanban_widgets.js247
-rw-r--r--addons/web_kanban/static/src/less/kanban_dashboard.less49
-rw-r--r--addons/web_kanban/static/src/less/kanban_view.less500
-rw-r--r--addons/web_kanban/static/src/xml/web_kanban.xml138
12 files changed, 2115 insertions, 1567 deletions
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: $('<div>', {
+ 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 <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 = $("<span></span");
- var id = _.uniqueId("kanbanfield");
- self.fields[id] = $field;
- $nfield.attr("data-field_id", id);
- $field.replaceWith($nfield);
- });
- },
- add_widget: function($node) {
- var $orig = this.fields[$node.data("field_id")];
- var field = this.record[$orig.attr("name")];
- var type = field.type;
- type = $orig.attr("widget") ? $orig.attr("widget") : type;
- var obj = fields_registry.get(type);
- var widget = new obj(this, field, $orig);
- this.sub_widgets.push(widget);
- widget.replace($node);
- },
- bind_events: function() {
- var self = this;
- this.setup_color_picker();
- this.$el.find('[title]').each(function(){
- $(this).tooltip({
- delay: { show: 500, hide: 0},
- title: function() {
- var template = $(this).attr('tooltip');
- if (!self.view.qweb.has_template(template)) {
- return false;
- }
- return self.view.qweb.render(template, self.qweb_context);
- },
- });
- });
-
- // If no draghandle is found, make the whole card as draghandle (provided one can edit)
- if (!this.$el.find('.oe_kanban_draghandle').length) {
- this.$el.children(':first')
- .toggleClass('oe_kanban_draghandle', this.view.is_action_enabled('edit'));
- }
-
- this.$el.find('.oe_kanban_action').click(function(ev) {
- ev.preventDefault();
- var $action = $(this),
- type = $action.data('type') || 'button',
- method = 'do_action_' + (type === 'action' ? 'object' : type);
- if ((type === 'edit' || type === 'delete') && ! self.view.is_action_enabled(type)) {
- self.view.open_record(self.id, true);
- } else if (_.str.startsWith(type, 'switch_')) {
- self.view.do_switch_view(type.substr(7));
- } else if (typeof self[method] === 'function') {
- self[method]($action);
- } else {
- self.do_warn("Kanban: no action for type : " + type);
- }
- });
-
- if (this.$el.find('.oe_kanban_global_click,.oe_kanban_global_click_edit').length) {
- this.$el.on('click', function(ev) {
- if (!ev.isTrigger && !$._data(ev.target, 'events')) {
- var trigger = true;
- var elem = ev.target;
- var ischild = true;
- var children = [];
- while (elem) {
- var events = $._data(elem, 'events');
- if (elem == ev.currentTarget) {
- ischild = false;
- }
- if (ischild) {
- children.push(elem);
- if (events && events.click) {
- // do not trigger global click if one child has a click event registered
- trigger = false;
- }
- }
- if (trigger && events && events.click) {
- _.each(events.click, function(click_event) {
- if (click_event.selector) {
- // For each parent of original target, check if a
- // delegated click is bound to any previously found children
- _.each(children, function(child) {
- if ($(child).is(click_event.selector)) {
- trigger = false;
- }
- });
- }
- });
- }
- elem = elem.parentElement;
- }
- if (trigger) {
- self.on_card_clicked(ev);
- }
- }
- });
- }
- },
- /* actions when user click on the block with a specific class
- * open on normal view : oe_kanban_global_click
- * open on form/edit view : oe_kanban_global_click_edit
- */
- on_card_clicked: function(ev) {
- if (this.$el.find('.oe_kanban_global_click').size() > 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<ii; i++) {
- index += variable.charCodeAt(i);
- }
- break;
- case 'number':
- index = Math.round(variable);
- break;
- default:
- return '';
- }
- var color = (index % this.view.number_of_color_schemes);
- return color;
- },
- kanban_color: function(variable) {
- var color = this.kanban_getcolor(variable);
- return color === '' ? '' : 'oe_kanban_color_' + color;
- },
- 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_text_ellipsis: function(s, size) {
- size = size || 160;
- if (!s) {
- return '';
- } else if (s.length <= size) {
- return s;
- } else {
- return s.substr(0, size) + '...';
- }
- },
- kanban_compute_domain: function(domain) {
- return data.compute_domain(domain, this.values);
- }
-});
-
-
-var KanbanGroup = Widget.extend({
- template: 'KanbanView.group_header',
- init: function (parent, records, group, dataset) {
- var self = this;
- this._super(parent);
- this.$has_been_started = $.Deferred();
- this.view = parent;
- this.group = group;
- this.dataset = dataset;
- this.dataset_offset = 0;
- this.value = this.title = this.values = null;
- if (this.group) {
- this.values = group.values;
- this.value = group.get('value');
- this.title = group.get('value');
- if (this.value instanceof Array) {
- this.title = this.value[1];
- this.value = this.value[0];
- }
- var field = this.view.group_by_field;
- if (!_.isEmpty(field)) {
- try {
- this.title = formats.format_value(group.get('value'), field, false);
- } catch(e) {}
- }
- }
-
- if (this.title === false) {
- this.title = _t('Undefined');
- this.undefined_title = true;
- }
- var key = this.view.group_by + '-' + this.value;
- if (!this.view.state.groups[key]) {
- this.view.state.groups[key] = {
- folded: group ? group.get('folded') : false
- };
- }
- this.state = this.view.state.groups[key];
- this.$records = null;
-
- this.records = [];
- this.$has_been_started.done(function() {
- self.do_add_records(records);
- });
- },
- start: function() {
- var self = this,
- def = this._super();
- if (! self.view.group_by) {
- self.$el.addClass("oe_kanban_no_group");
- var QuickCreateClass = self.view.get_quick_create_class();
- self.quick = (new QuickCreateClass(this, self.dataset, {}, false))
- .on('added', self, self.proxy('quick_created'));
- self.quick.replace($(".oe_kanban_no_group_qc_placeholder"));
- }
- this.$records = $(QWeb.render('KanbanView.group_records_container', { widget : this}));
- this.$records.insertBefore(this.view.$el.find('.oe_kanban_groups_records td:last'));
-
- this.$el.on('click', '.oe_kanban_group_dropdown li a', function(ev) {
- var fn = 'do_action_' + $(ev.target).data().action;
- if (typeof(self[fn]) === 'function') {
- self[fn]($(ev.target));
- }
- });
-
- this.$el.find('.oe_kanban_add').click(function () {
- if (self.view.quick) {
- self.view.quick.trigger('close');
- }
- if (self.quick) {
- return false;
- }
- self.view.$el.find('.oe_view_nocontent').hide();
- var ctx = {};
- ctx['default_' + self.view.group_by] = self.value;
- self.quick = new (self.view.get_quick_create_class())(this, self.dataset, ctx, true)
- .on('added', self, self.proxy('quick_created'))
- .on('close', self, function() {
- self.view.$el.find('.oe_view_nocontent').show();
- this.quick.destroy();
- delete self.view.quick;
- delete this.quick;
- });
- self.quick.appendTo($(".oe_kanban_group_list_header", self.$records));
- self.quick.focus();
- self.view.quick = self.quick;
- });
- // Add bounce effect on image '+' of kanban header when click on empty space of kanban grouped column.
- this.$records.on('click', '.oe_kanban_show_more', this.do_show_more);
- if (this.state.folded) {
- this.do_toggle_fold();
- }
- this.$el.data('widget', this);
- this.$records.data('widget', this);
- this.$has_been_started.resolve();
- var add_btn = this.$el.find('.oe_kanban_add');
- add_btn.tooltip({delay: { show: 500, hide:1000 }});
- this.$records.find(".oe_kanban_column_cards").click(function (ev) {
- if (ev.target == ev.currentTarget) {
- if (!self.state.folded) {
- add_btn.openerpBounce();
- }
- }
- });
- this.is_started = true;
- this.fetch_tooltip();
- return def;
- },
- /*
- * Form the tooltip, based on optional group_by_tooltip on the grouping field.
- * This function goes through the arch of the view, finding the declaration
- * of the field used to group. If group_by_tooltip is defined, use the previously
- * computed values of the group to form the tooltip. */
- fetch_tooltip: function() {
- var self = this;
- if (! this.group)
- return;
- var options = null;
- var recurse = function(node) {
- if (node.tag === "field" && node.attrs.name == self.view.group_by) {
- options = pyeval.py_eval(node.attrs.options || '{}');
- return;
- }
- _.each(node.children, function(child) {
- recurse(child);
- });
- };
- recurse(this.view.fields_view.arch);
- if (options && options.group_by_tooltip) {
- this.tooltip = _.compact(
- _.union(
- [this.title],
- _.map(
- options.group_by_tooltip,
- function (key, value, list) { return self.values && self.values[value] && (key + "\n" + self.values[value]) || ''; }))
- ).join('\n\n');
- this.$(".oe_kanban_group_title_text").attr("title", this.tooltip || this.title || "");
- }
- },
- compute_cards_auto_height: function() {
- // oe_kanban_no_auto_height is an empty class used to disable this feature
- if (!this.view.group_by) {
- var min_height = 0;
- var els = [];
- _.each(this.records, function(r) {
- var $e = r.$el.children(':first:not(.oe_kanban_no_auto_height)').css('min-height', 0);
- if ($e.length) {
- els.push($e[0]);
- min_height = Math.max(min_height, $e.outerHeight());
- }
- });
- $(els).css('min-height', min_height);
- }
- },
- destroy: function() {
- this._super();
- if (this.$records) {
- this.$records.remove();
- }
- },
- do_show_more: function(evt) {
- var self = this;
- var ids = self.view.dataset.ids.slice(0);
- return this.dataset.read_slice(this.view.fields_keys.concat(['__last_update']), {
- 'limit': self.view.limit,
- 'offset': self.dataset_offset += self.view.limit
- }).then(function(records) {
- self.view.dataset.ids = ids.concat(_.difference(self.dataset.ids, ids));
- self.do_add_records(records);
- self.compute_cards_auto_height();
- self.view.postprocess_m2m_tags();
- return records;
- });
- },
- do_add_records: function(records, prepend) {
- var self = this;
- var $list_header = this.$records.find('.oe_kanban_group_list_header');
- var $show_more = this.$records.find('.oe_kanban_show_more');
- var $cards = this.$records.find('.oe_kanban_column_cards');
-
- _.each(records, function(record) {
- var rec = new KanbanRecord(self, record);
- if (!prepend) {
- rec.appendTo($cards);
- self.records.push(rec);
- } else {
- rec.prependTo($cards);
- self.records.unshift(rec);
- }
- });
- if ($show_more.length) {
- var size = this.dataset.size();
- $show_more.toggle(this.records.length < size).find('.oe_kanban_remaining').text(size - this.records.length);
- }
- },
- remove_record: function(id, remove_from_dataset) {
- for (var i = 0; i < this.records.length; i++) {
- if (this.records[i]['id'] === id) {
- this.records.splice(i, 1);
- i--;
- }
- }
- },
- do_toggle_fold: function(compute_width) {
- this.$el.add(this.$records).toggleClass('oe_kanban_group_folded');
- this.state.folded = this.$el.is('.oe_kanban_group_folded');
- this.$("ul.oe_kanban_group_dropdown li a[data-action=toggle_fold]").text((this.state.folded) ? _t("Unfold") : _t("Fold"));
- },
- do_action_toggle_fold: function() {
- this.do_toggle_fold();
- },
- do_action_edit: function() {
- var self = this;
- self.do_action({
- res_id: this.value,
- name: _t("Edit column"),
- res_model: self.view.group_by_field.relation,
- views: [[false, 'form']],
- type: 'ir.actions.act_window',
- target: "new",
- flags: {
- action_buttons: true,
- headless: true,
- }
- });
- // to do : remove this horrible hack
- var am = web_client.action_manager;
- var form = am.dialog_widget.views.form.controller;
- form.on("on_button_cancel", am.dialog, function() { return am.dialog.$dialog_box.modal('hide'); });
- form.on('record_saved', self, function() {
- am.dialog.$dialog_box.modal('hide');
- self.view.do_reload();
- });
- },
- do_action_delete: function() {
- var self = this;
- if (confirm(_t("Are you sure to remove this column ?"))) {
- (new data.DataSet(self, self.view.group_by_field.relation)).unlink([self.value]).done(function(r) {
- self.view.do_reload();
- });
- }
- },
- do_save_sequences: function() {
- var self = this;
- if (_.indexOf(this.view.fields_keys, 'sequence') > -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('<div>').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 = $('<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<variable.length; i++) {
+ index += variable.charCodeAt(i);
+ }
+ return index % 10;
+ }
+ return 0;
+ },
+
+ kanban_color: function(variable) {
+ var color = this.kanban_getcolor(variable);
+ return 'oe_kanban_color_' + color;
+ },
+
+ on_global_click: function (ev) {
+ if ($(ev.target).parents('.o_dropdown_kanban').length) {
+ return;
+ }
+ if (!ev.isTrigger) {
+ var trigger = true;
+ var elem = ev.target;
+ var ischild = true;
+ var children = [];
+ while (elem) {
+ var events = $._data(elem, 'events');
+ if (elem == ev.currentTarget) {
+ ischild = false;
+ }
+ if (ischild) {
+ children.push(elem);
+ if (events && events.click) {
+ // do not trigger global click if one child has a click event registered
+ trigger = false;
+ }
+ }
+ if (trigger && events && events.click) {
+ _.each(events.click, function(click_event) {
+ if (click_event.selector) {
+ // For each parent of original target, check if a
+ // delegated click is bound to any previously found children
+ _.each(children, function(child) {
+ if ($(child).is(click_event.selector)) {
+ trigger = false;
+ }
+ });
+ }
+ });
+ }
+ elem = elem.parentElement;
+ }
+ if (trigger) {
+ this.on_card_clicked(ev);
+ }
+ }
+ },
+
+ /* actions when user click on the block with a specific class
+ * open on normal view : oe_kanban_global_click
+ * open on form/edit view : oe_kanban_global_click_edit
+ */
+ on_card_clicked: function() {
+ if (this.$el.hasClass('oe_kanban_global_click_edit') && this.$el.data('routing')) {
+ framework.redirect(this.$el.data('routing') + "/" + this.id);
+ } else if (this.$el.hasClass('oe_kanban_global_click_edit')) {
+ this.trigger_up('kanban_record_edit', {id: this.id});
+ } else {
+ this.trigger_up('kanban_record_open', {id: this.id});
+ }
+ },
+
+ toggle_manage_pane: function(event){
+ event.preventDefault();
+ 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));
+ },
+
+ on_kanban_action_clicked: function (ev) {
+ ev.preventDefault();
+
+ var $action = $(ev.currentTarget);
+ var type = $action.data('type') || 'button';
+
+ switch (type) {
+ case 'edit':
+ this.trigger_up('kanban_record_edit', {id: this.id});
+ break;
+ case 'open':
+ this.trigger_up('kanban_record_open', {id: this.id});
+ break;
+ case 'delete':
+ this.trigger_up('kanban_record_delete', {record: this});
+ break;
+ case 'action':
+ case 'object':
+ this.trigger_up('kanban_do_action', $action.data());
+ break;
+ default:
+ this.do_warn("Kanban: no action for type : " + type);
+ }
+ },
+
+ kanban_compute_domain: function(domain) {
+ return data.compute_domain(domain, this.values);
+ },
+
+ update_record: function (event) {
+ this.trigger_up('kanban_record_update', event.data);
+ },
+
+ setup_color_picker: function() {
+ var self = this;
+ var $colorpicker = this.$('ul.oe_kanban_colorpicker');
+ if (!$colorpicker.length) {
+ return;
+ }
+ $colorpicker.html(QWeb.render('KanbanColorPicker', {
+ widget: this
+ }));
+ $colorpicker.on('click', 'a', function(ev) {
+ ev.preventDefault();
+ var color_field = $colorpicker.data('field') || 'color';
+ var data = {};
+ data[color_field] = $(this).data('color');
+ self.trigger_up('kanban_record_update', data);
+ });
+ },
+
+});
+
+return KanbanRecord;
+
+});
diff --git a/addons/web_kanban/static/src/js/kanban_view.js b/addons/web_kanban/static/src/js/kanban_view.js
index a488267752a..5d60fc1aeb8 100644
--- a/addons/web_kanban/static/src/js/kanban_view.js
+++ b/addons/web_kanban/static/src/js/kanban_view.js
@@ -4,24 +4,49 @@ odoo.define('web_kanban.KanbanView', function (require) {
var core = require('web.core');
var data = require('web.data');
var Model = require('web.Model');
+var Dialog = require('web.Dialog');
+var form_common = require('web.form_common');
+var Pager = require('web.Pager');
var pyeval = require('web.pyeval');
var session = require('web.session');
var utils = require('web.utils');
var View = require('web.View');
-var kanban_common = require('web_kanban.common');
+
+var KanbanColumn = require('web_kanban.Column');
+var quick_create = require('web_kanban.quick_create');
+var KanbanRecord = require('web_kanban.Record');
+var kanban_widgets = require('web_kanban.widgets');
var QWeb = core.qweb;
-var _t = core._t;
var _lt = core._lt;
-var fields_registry = kanban_common.registry;
-var KanbanGroup = kanban_common.KanbanGroup;
+var _t = core._t;
+var ColumnQuickCreate = quick_create.ColumnQuickCreate;
+var fields_registry = kanban_widgets.registry;
var KanbanView = View.extend({
- template: "KanbanView",
- display_name: _lt('Kanban'),
- default_nr_columns: 1,
+ display_name: _lt("Kanban"),
view_type: "kanban",
- number_of_color_schemes: 10,
+ className: "o_kanban_view",
+ mobile_friendly: true,
+
+ custom_events: {
+ 'kanban_record_open': 'open_record',
+ 'kanban_record_edit': 'edit_record',
+ 'kanban_record_delete': 'delete_record',
+ 'kanban_do_action': 'open_action',
+ 'kanban_reload': 'do_reload',
+ 'kanban_column_resequence': function (event) {
+ this.resequence_column(event.target);
+ },
+ 'kanban_record_update': 'update_record',
+ 'kanban_column_add_record': 'add_record_to_column',
+ 'kanban_column_delete': 'delete_column',
+ 'column_add_record': 'column_add_record',
+ 'quick_create_add_column': 'add_new_column',
+ 'kanban_load_more': 'load_more',
+ 'kanban_call_method': 'call_method',
+ },
+
init: function (parent, dataset, view_id, options) {
this._super(parent, dataset, view_id, options);
_.defaults(this.options, {
@@ -31,111 +56,42 @@ var KanbanView = View.extend({
"read_only_mode": false,
"confirm_on_delete": true,
});
- this.fields_view = {};
- this.fields_keys = [];
- this.group_by = null;
- this.group_by_field = {};
- this.grouped_by_m2o = false;
- this.many2manys = [];
- this.m2m_context = {};
- this.state = {
- groups : {},
- records : {}
- };
- this.groups = [];
+
+ // qweb setup
this.qweb = new QWeb2.Engine();
this.qweb.debug = session.debug;
this.qweb.default_dict = _.clone(QWeb.default_dict);
- this.has_been_loaded = $.Deferred();
- this.search_domain = this.search_context = this.search_group_by = null;
- this.currently_dragging = {};
+
+ this.model = this.dataset.model;
this.limit = options.limit || 40;
- this.add_group_mutex = new utils.Mutex();
- },
- view_loading: function(r) {
- return this.load_kanban(r);
- },
- start: function() {
- var self = this;
- this._super.apply(this, arguments);
- this.$el.on('click', '.oe_kanban_dummy_cell', function() {
- if (self.$buttons) {
- self.$buttons.find('.oe_kanban_add_column').openerpBounce();
- }
- });
- },
- destroy: function() {
- this._super.apply(this, arguments);
- $('html').off('click.kanban');
- },
- get_quick_create_class: function () {
- return kanban_common.QuickCreate;
+ this.grouped = undefined;
+ this.group_by_field = undefined;
+ this.default_group_by = undefined;
+ this.grouped_by_m2o = undefined;
+ this.relation = undefined;
+ this.is_empty = undefined;
+ this.many2manys = [];
+ this.m2m_context = {};
+ this.widgets = [];
+ this.data = undefined;
+ this.model = dataset.model;
+ this.quick_creatable = options.quick_creatable;
+ this.no_content_msg = options.action && (options.action.get_empty_list_help || options.action.help);
+
},
- load_kanban: function(data) {
- this.fields_view = data;
- // use default order if defined in xml description
- var default_order = this.fields_view.arch.attrs.default_order,
- unsorted = !this.dataset._sort.length;
- if (unsorted && default_order) {
- this.dataset.set_sort(default_order.split(','));
- }
- this.$el.addClass(this.fields_view.arch.attrs['class']);
- this.$groups = this.$el.find('.oe_kanban_groups tr');
+ view_loading: function(fvg) {
+ this.$el.addClass(fvg.arch.attrs.class);
+ this.fields_view = fvg;
+ this.default_group_by = fvg.arch.attrs.default_group_by;
+
this.fields_keys = _.keys(this.fields_view.fields);
- this.add_qweb_template();
- this.has_been_loaded.resolve();
- this.trigger('kanban_view_loaded', data);
- return $.when();
- },
- /**
- * 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
- * or into a div of its template
- */
- 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.oe_kanban_button_new', this.do_add_record)
- .on('click', '.oe_kanban_add_column', this.do_add_group);
- // Important: This should be done after do_search is finished so that
- // this.grouped_by_m2o is set
- this.$buttons.find('.oe_alternative').toggle(this.grouped_by_m2o);
- $node = $node || this.options.$buttons;
- if ($node) {
- this.$buttons.appendTo($node);
- } else {
- this.$('.oe_kanban_buttons').replaceWith(this.$buttons);
- }
- },
- _is_quick_create_enabled: function() {
- if (!this.options.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.group_by;
- },
- is_action_enabled: function(action) {
- if (action === 'create' && !this.options.creatable)
- return false;
- return this._super(action);
- },
- /* add_qweb_template */
- add_qweb_template: function() {
+ // add qweb templates
for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) {
var child = this.fields_view.arch.children[i];
if (child.tag === "templates") {
- this.transform_qweb_template(child);
+ transform_qweb_template(child, fvg, this.many2manys);
this.qweb.add_template(utils.json_node_to_xml(child));
break;
} else if (child.tag === 'field') {
@@ -146,164 +102,130 @@ var KanbanView = View.extend({
}
}
},
- transform_qweb_template: function(node) {
- var qweb_add_if = function(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;
- };
- // 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 = this.fields_view.fields[node.attrs.name].type;
- ftype = node.attrs.widget ? node.attrs.widget : ftype;
- if (ftype === 'many2many') {
- if (_.indexOf(this.many2manys, node.attrs.name) < 0) {
- this.many2manys.push(node.attrs.name);
- }
- node.tag = 'div';
- node.attrs['class'] = (node.attrs['class'] || '') + ' oe_form_field oe_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++) {
- this.transform_qweb_template(node.children[i]);
- }
- }
+
+ do_search: function(domain, context, group_by) {
+ this.search_domain = domain;
+ this.search_context = context;
+ this.group_by_field = group_by[0] || this.default_group_by;
+ this.grouped = group_by.length || this.default_group_by;
+
+ var field = this.fields_view.fields[this.group_by_field];
+ this.grouped_by_m2o = field && (field.type === 'many2one');
+ this.relation = this.grouped_by_m2o ? field.relation : undefined;
+
+ return this.load_data()
+ .then(this.proxy('render'))
+ .then(this.proxy('update_pager'));
},
- do_add_record: function() {
- this.dataset.index = null;
- this.do_switch_view('form');
+
+ do_show: function() {
+ this.do_push_state({});
+ return this._super();
},
- do_add_group: function() {
+
+ do_reload: function() {
+ this.do_search(this.search_domain, this.search_context, [this.group_by_field]);
},
- do_search: function(domain, context, group_by) {
+
+ load_data: function () {
var self = this;
- this.search_domain = domain;
- this.search_context = context;
- this.search_group_by = group_by;
- return $.when(this.has_been_loaded).then(function() {
- self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by;
- self.group_by_field = self.fields_view.fields[self.group_by] || {};
- self.grouped_by_m2o = (self.group_by_field.type === 'many2one');
- self.$el.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o);
- var grouping_fields = self.group_by ? [self.group_by] : undefined;
- if (!_.isEmpty(grouping_fields)) {
- // ensure group_by fields are read.
- self.fields_keys = _.unique(self.fields_keys.concat(grouping_fields));
- }
- var grouping = new Model(self.dataset.model, context, domain).query(self.fields_keys).group_by(grouping_fields);
- return self.alive($.when(grouping)).then(function(groups) {
- self.remove_no_result();
- if (groups) {
- return self.do_process_groups(groups);
- } else {
- return self.do_process_dataset();
- }
- });
+ var deferred = this.grouped ? this.load_groups() : this.load_records();
+ return deferred.then(function (data) {
+ self.data = data;
});
},
- do_process_groups: function(groups) {
- var self = this;
- // Check in the arch the fields to fetch on the stage to get tooltips data.
- // Fetching data is done in batch for all stages, to avoid doing multiple
- // calls. The first naive implementation of group_by_tooltip made a call
- // for each displayed stage and was quite limited.
- // Data for the group tooltip (group_by_tooltip) and to display stage-related
- // legends for kanban state management (states_legend) are fetched in
- // one call.
- var group_by_fields_to_read = [];
- var recurse = function(node) {
- if (node.tag === "field" && node.attrs && node.attrs.options) {
- var options = pyeval.py_eval(node.attrs.options);
- var states_fields_to_read = _.map(
- options && options.states_legend || {},
- function (value, key, list) { return value; });
- var tooltip_fields_to_read = _.map(
- options && options.group_by_tooltip || {},
- function (value, key, list) { return key; });
- group_by_fields_to_read = _.union(
- group_by_fields_to_read,
- states_fields_to_read,
- tooltip_fields_to_read);
- }
- _.each(node.children, function(child) {
- recurse(child);
- });
+ load_records: function (offset, dataset) {
+ var options = {
+ 'limit': this.limit,
+ 'offset': offset,
};
- recurse(this.fields_view.arch);
- var group_ids = _.without(_.map(groups, function (elem) { return elem.attributes.value[0];}), undefined);
- if (this.grouped_by_m2o && group_ids.length && group_by_fields_to_read.length) {
- var group_data = new data.DataSet(
- this,
- this.group_by_field.relation).read_ids(group_ids, _.union(['display_name'], group_by_fields_to_read));
- }
- else { var group_data = $.Deferred().resolve({}); }
-
- this.$el.find('table:first').show();
- this.$el.removeClass('oe_kanban_ungrouped').addClass('oe_kanban_grouped');
- return $.when(group_data).then(function (results) {
- _.each(results, function (group_by_data) {
- var group = _.find(groups, function (elem) {return elem.attributes.value[0] == group_by_data.id;});
- if (group) {
- group.values = group_by_data;
- }
+ dataset = dataset || this.dataset;
+ return dataset
+ .read_slice(this.fields_keys.concat(['__last_update']), options)
+ .then(function(records) {
+ return {
+ records: records,
+ is_empty: !records.length,
+ grouped: false,
+ };
});
- }).done( function () {return self.add_group_mutex.exec(function() {
- self.do_clear_groups();
- self.dataset.ids = [];
- if (!groups.length) {
- self.no_result();
- return $.when();
+ },
+
+ load_groups: function () {
+ var self = this;
+ var group_by_field = this.group_by_field || this.default_group_by;
+ this.fields_keys = _.uniq(this.fields_keys.concat(group_by_field));
+
+ return new Model(this.model, this.search_context, this.search_domain)
+ .query(this.fields_keys)
+ .group_by([group_by_field])
+ .then(function (groups) {
+
+ // Check in the arch the fields to fetch on the stage to get tooltips data.
+ // Fetching data is done in batch for all stages, to avoid doing multiple
+ // calls. The first naive implementation of group_by_tooltip made a call
+ // for each displayed stage and was quite limited.
+ // Data for the group tooltip (group_by_tooltip) and to display stage-related
+ // legends for kanban state management (states_legend) are fetched in
+ // one call.
+ var group_by_fields_to_read = [];
+ var recurse = function(node) {
+ if (node.tag === "field" && node.attrs && node.attrs.options) {
+ var options = pyeval.py_eval(node.attrs.options);
+ var states_fields_to_read = _.map(
+ options && options.states_legend || {},
+ function (value, key, list) { return value; });
+ var tooltip_fields_to_read = _.map(
+ options && options.group_by_tooltip || {},
+ function (value, key, list) { return key; });
+ group_by_fields_to_read = _.union(
+ group_by_fields_to_read,
+ states_fields_to_read,
+ tooltip_fields_to_read);
+ }
+ _.each(node.children, function(child) {
+ recurse(child);
+ });
+ };
+ recurse(self.fields_view.arch);
+
+ // fetch group data (display information)
+ var group_ids = _.without(_.map(groups, function (elem) { return elem.attributes.value[0];}), undefined);
+ if (self.grouped_by_m2o && group_ids.length) {
+ return new data.DataSet(self, self.relation)
+ .read_ids(group_ids, _.union(['display_name'], group_by_fields_to_read))
+ .then(function(results) {
+ _.each(groups, function (group) {
+ var group_id = group.attributes.value[0];
+ var result = _.find(results, function (data) {return group_id === data.id;});
+ group.title = result ? result.display_name : _t("Undefined");
+ group.values = result;
+ group.id = group_id;
+ });
+ return groups;
+ });
+ } else {
+ _.each(groups, function (group) {
+ var value = group.attributes.value;
+ group.title = (value instanceof Array ? value[1] : value) || _t("Undefined");
+ });
+ return $.when(groups);
}
- self.nb_records = 0;
- var groups_array = [];
- return $.when.apply(null, _.map(groups, function (group, index) {
+ })
+ .then(function (groups) {
+ var undef_index = _.findIndex(groups, function (g) { return g.title === _t("Undefined");});
+ if (undef_index >= 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);
+
+ is_action_enabled: function(action) {
+ if (action === 'create' && !this.options.creatable) {
+ return false;
+ }
+ return this._super(action);
},
- do_clear_groups: function() {
- var groups = this.groups.slice(0);
- this.groups = [];
- _.each(groups, function(group) {
- group.destroy();
- });
+ _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;
},
- do_add_groups: function(groups) {
+ /**
+ * 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;
- 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();
- });
+ 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();
},
- 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');
+
+ update_pager: function() {
+ if (this.pager) {
+ if (this.grouped) {
+ this.pager.do_hide();
} 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' });
- }
- });
+ 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 = $("<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();
- } 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
- });
+ 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();
},
- do_show: function() {
- this.do_push_state({});
- return this._super();
+ add_record: function() {
+ this.dataset.index = null;
+ this.do_switch_view('form');
},
- open_record: function(id, editable) {
- if (this.dataset.select_id(id)) {
- this.do_switch_view('form', null, { mode: editable ? "edit" : undefined });
+
+ open_record: function (event, options) {
+ if (this.dataset.select_id(event.data.id)) {
+ this.do_switch_view('form', null, options); //, null, { mode: "edit" });
} else {
- this.do_warn("Kanban: could not find id#" + id);
+ this.do_warn("Kanban: could not find id#" + event.data.id);
}
},
- no_result: function() {
+
+ edit_record: function (event) {
+ this.open_record(event, {mode: 'edit'});
+ },
+
+ 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;
- }
- if (!relations[field.relation]) {
- relations[field.relation] = { ids: [], elements: {}, context: self.m2m_context[name]};
+ 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] = [];
}
- 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('<span class="oe_tag">' + _.str.escapeHTML(nameget[1]) + '</span>');
+ // white color (0) should not be selected, 1 instead
+ var color = KanbanRecord.prototype.kanban_getcolor(nameget[1]) + 1;
+ var $tag = $('<span>')
+ .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?');
+ }
+ });
+ },
-core.view_registry.add('kanban', KanbanView);
+ resequence_column: function (col) {
+ if (_.indexOf(this.fields_keys, 'sequence') > -1) {
+ this.dataset.resequence(col.get_ids());
+ }
+ },
-return KanbanView;
+ 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);
+ }
+ });
+ },
-odoo.define(function (require) {
+ 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);
+ }
+ });
+ },
-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');
+ 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]);
+ });
+ });
-var KanbanGroup = kanban_common.KanbanGroup;
-var _t = core._t;
+ 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);
+ });
+ },
-KanbanView.include({
- do_add_group: function() {
+ load_more: function (event) {
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 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();
}
});
- 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' });
+ }
+});
+
+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;
+
});
+
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 <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('<div>').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 = $('<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 @@
<template>
-<t t-name="KanbanView">
- <div class="oe_kanban_view">
- <div class="oe_kanban_buttons"/>
- <table class="oe_kanban_groups">
- <tr class="oe_kanban_groups_headers">
- <td class="oe_kanban_dummy_cell"><div/></td>
- </tr>
- <tr class="oe_kanban_groups_records">
- <td class="oe_kanban_dummy_cell"><div/></td>
- </tr>
- </table>
- </div>
-</t>
+
<t t-name="KanbanView.buttons">
<t t-if="display">
- <div class="oe_kanban_buttons">
- <button type="button" class="oe_kanban_button_new btn btn-primary btn-sm">
+ <div>
+ <button type="button" class="btn btn-primary btn-sm o-kanban-button-new">
<t t-esc="widget.options.create_text || _t('Create')"/>
</button>
- <span t-if="widget.is_action_enabled('group_create')" class="oe_alternative" style="display: none">
- <span class="oe_fade">or</span>
- <div class="oe_bounce_container">
- <a href="#" class="oe_kanban_add_column text-info">Add a new column</a>
- </div>
- </span>
</div>
</t>
</t>
-<t t-name="KanbanView.group_header">
- <td class="oe_kanban_group_header">
- <t t-if="widget.view.group_by">
- <t t-set="parent" t-value="(widget.getParent())"/>
- <div class="oe_kanban_header oe_semantic_html_override">
- <t t-if="widget.view._is_quick_create_enabled()">
- <div><i class="oe_kanban_add fa fa-plus" title="Quick create"/></div>
- </t>
- <div class="oe_dropdown_toggle oe_dropdown_kanban">
- <div class="oe_kanban_group_length">
- <span class="oe_tag">
- <t t-if="widget.group.get('length') &gt; 99"> 99+ </t><t t-if="widget.group.get('length') &lt;= 99"> <t t-esc="widget.group.get('length')"/> </t>
- </span>
- </div>
- <i class="fa fa-bars fa-lg"/>
- <ul class="oe_dropdown_menu oe_kanban_group_dropdown">
- <li><a data-action="toggle_fold" href="#">Fold</a></li>
- <t t-if="widget.view.grouped_by_m2o and widget.value">
- <li t-if="parent &amp;&amp; parent.is_action_enabled('group_edit')"><a data-action="edit" href="#">Edit</a></li>
- <li t-if="parent &amp;&amp; parent.is_action_enabled('group_delete')"><a data-action="delete" href="#">Delete</a></li>
- </t>
- </ul>
- </div>
- <div class="oe_fold_column">
- <div t-attf-class="oe_kanban_group_title #{widget.undefined_title ? 'oe_kanban_group_title_undefined' : ''}">
- <div class="oe_kanban_group_title_text" t-att-title="widget.title"><t t-esc="widget.title"/></div>
- </div>
- <div class="oe_kanban_group_length oe_tag">
- <t t-if="widget.group.get('length') &gt; 99"> 99+ </t><t t-if="widget.group.get('length') &lt;= 99"> <t t-esc="widget.group.get('length')"/> </t>
- </div>
- </div>
- <span t-if="widget.title" class="oe_kanban_group_title_vertical">
- <t t-esc="widget.title"/>
- </span>
- </div>
- </t>
- <t t-if="! widget.view.group_by &amp;&amp; widget.view._is_quick_create_enabled()">
- <span class="oe_kanban_no_group_qc_placeholder"/>
- </t>
- </td>
+
+<t t-name="KanbanView.Group">
+ <div class="o_kanban_group" t-att-data-id="widget.id">
+ <div class="o_kanban_header" t-att-title="widget.data_records.length + ' records'">
+ <span class="o_column_title"><t t-esc="widget.title"/></span>
+ <span class="o_column_unfold"><i class="fa fa-arrows-h"/></span>
+ <span class="o_kanban_config dropdown">
+ <a class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="fa fa-gear"/></a>
+ <ul class="dropdown-menu" role="menu">
+ <li><a class="o_kanban_toggle_fold" href="#">Fold</a></li>
+ <t t-if="widget.grouped_by_m2o">
+ <li t-if="widget.editable and widget.id"><a class="o_column_edit" href="#">Edit</a></li>
+ <li t-if="widget.deletable and widget.id"><a class="o_column_delete" href="#">Delete</a></li>
+ </t>
+ </ul>
+ </span>
+ <t t-if="widget.quick_create">
+ <span class="o_kanban_quick_add"><i class="fa fa-plus"/></span>
+ </t>
+ </div>
+ <div t-if="widget.remaining > 0" class="o_kanban_load_more">
+ <t t-call="KanbanView.LoadMore"/>
+ </div>
+ </div>
+</t>
+
+<t t-name="KanbanView.LoadMore">
+ <a href="#">Load more... (<t t-esc="widget.remaining"/> remaining)</a>
</t>
-<t t-name="KanbanView.group_records_container">
- <td class="oe_kanban_column">
- <div class="oe_kanban_group_list_header"/>
- <div class="oe_kanban_column_cards"/>
- <div class="oe_kanban_folded_column_cards"/>
- <div class="oe_kanban_show_more">
- <button class="oe_button">Show more... (<span class="oe_kanban_remaining"></span> remaining)</button>
+
+<t t-name="KanbanView.ColumnQuickCreate">
+ <div class="o_column_quick_create">
+ <div class="o_column_header">
+ <span class="o_kanban_add_column"><i class="fa fa-chevron-right"/></span>
+ <span class="o_kanban_title">Add new Column</span>
</div>
- </td>
+ <div class="o_kanban_quick_create">
+ <input type="text" placeholder="Add"/>
+ <button class="btn btn-primary o_kanban_add">Add</button>
+ <button class="btn btn-default o_kanban_cancel">Cancel</button>
+ </div>
+ </div>
</t>
+
<t t-name="KanbanView.record">
- <div class="oe_fold_column oe_kanban_record">
- <t t-raw="content"/>
+ <t t-raw="widget.content"/>
+</t>
+
+<t t-name="KanbanView.QuickCreate">
+ <div class="o_kanban_quick_create">
+ <input type="text" placeholder="Add"/>
+ <button class="btn btn-primary o_kanban_add">Add</button>
+ <button class="btn btn-default o_kanban_cancel">Cancel</button>
</div>
</t>
+
<t t-name="KanbanColorPicker">
- <li t-foreach="widget.view.number_of_color_schemes" t-as="color">
+ <li t-foreach="10" t-as="color">
<a href="#" t-att-data-color="color_index" t-attf-class="oe_kanban_color_#{color}"/>
</li>
</t>
@@ -98,11 +80,13 @@
</t>
</div>
<t t-name="KanbanView.nocontent">
- <div class="oe_view_nocontent" style="position:absolute;">
- <div class="oe_view_nocontent_bg"></div>
- <div class="oe_view_nocontent_content">
- <t t-raw="content"/>
- </div>
+ <div class="oe_view_nocontent">
+ <t t-raw="content"/>
+ </div>
+</t>
+<t t-name="KanbanAttachmentImage">
+ <div class="o_kanban_attachment_image">
+ <img t-att-src="'/web/binary/image?model=ir.attachment&amp;field=datas&amp;id=' + widget.field.raw_value[0]"/>
</div>
</t>
</template>