[REF] web_kanban: new redesigned kanban view
This commit introduces an almost completely rewritten kanban view. It was necessary in order to be able to render a different DOM to match the new design. It basically has the same features as before, but will render a different DOM. Three main differences, API wise, should be noted: * the way the kanban view components communicate has been changed. It now uses events, and event bubbling. * kanban records do not have access to the view, so every call to this.view should be updated * kanban templates are no longer wrapped in a div, so they should have a single root element for better result.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -514,7 +514,7 @@
|
||||
padding: 0px
|
||||
background: #ffffff
|
||||
.openerp
|
||||
.oe_popup_form
|
||||
.o_popup_form
|
||||
.oe_kanban_buttons
|
||||
.oe_highlight
|
||||
color: #404040
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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') > 99"> 99+ </t><t t-if="widget.group.get('length') <= 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 && parent.is_action_enabled('group_edit')"><a data-action="edit" href="#">Edit</a></li>
|
||||
<li t-if="parent && 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') > 99"> 99+ </t><t t-if="widget.group.get('length') <= 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 && widget.view._is_quick_create_enabled()">
|
||||
<span class="oe_kanban_no_group_qc_placeholder"/>
|
||||
</t>
|
||||
</td>
|
||||
</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.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>
|
||||
</td>
|
||||
</t>
|
||||
<t t-name="KanbanView.record">
|
||||
<div class="oe_fold_column oe_kanban_record">
|
||||
<t t-raw="content"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="KanbanView.LoadMore">
|
||||
<a href="#">Load more... (<t t-esc="widget.remaining"/> remaining)</a>
|
||||
</t>
|
||||
|
||||
<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>
|
||||
<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">
|
||||
<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&field=datas&id=' + widget.field.raw_value[0]"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
<data>
|
||||
<template id="assets_backend" name="web_kanban assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/web_kanban/static/src/css/kanban.css"/>
|
||||
<link rel="stylesheet" href="/web_kanban/static/src/less/kanban_dashboard.less"/>
|
||||
<link rel="stylesheet" href="/web_kanban/static/src/less/kanban_view.less"/>
|
||||
|
||||
<script type="text/javascript" src="/web_kanban/static/src/js/kanban_record.js"></script>
|
||||
<script type="text/javascript" src="/web_kanban/static/src/js/kanban_quick_create.js"></script>
|
||||
<script type="text/javascript" src="/web_kanban/static/src/js/kanban_column.js"></script>
|
||||
<script type="text/javascript" src="/web_kanban/static/src/js/kanban_view.js"></script>
|
||||
<script type="text/javascript" src="/web_kanban/static/src/js/kanban_common.js"></script>
|
||||
<script type="text/javascript" src="/web_kanban/static/src/js/kanban_widgets.js"></script>
|
||||
<script type="text/javascript" src="/web_kanban/static/src/js/compatibility.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user