this.currently_dragging = {};
this.limit = options.limit || 40;
this.add_group_mutex = new $.Mutex();
+ if (!this.options.$buttons || !this.options.$buttons.length) {
+ this.options.$buttons = false;
+ }
},
view_loading: function(r) {
return this.load_kanban(r);
},
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.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this}));
if (this.options.$buttons) {
this.$buttons.appendTo(this.options.$buttons);
} else {
- this.$el.find('.oe_kanban_buttons').replaceWith(this.$buttons);
+ this.$('.oe_kanban_buttons').replaceWith(this.$buttons);
}
this.$buttons
.on('click', 'button.oe_kanban_button_new', this.do_add_record)
case 'button':
case 'a':
var type = node.attrs.type || '';
- if (_.indexOf('action,object,edit,open,delete'.split(','), type) !== -1) {
+ 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;
}
}];
}
- if (node.tag == 'a') {
+ if (node.tag == 'a' && node.attrs['data-type'] != "url") {
node.attrs.href = '#';
} else {
node.attrs.type = 'button';
self.fields_keys = _.unique(self.fields_keys.concat(grouping_fields));
}
var grouping = new instance.web.Model(self.dataset.model, context, domain).query(self.fields_keys).group_by(grouping_fields);
- return self.alive($.when(grouping)).done(function(groups) {
+ return self.alive($.when(grouping)).then(function(groups) {
self.remove_no_result();
if (groups) {
- self.do_process_groups(groups);
+ return self.do_process_groups(groups);
} else {
- self.do_process_dataset();
+ return self.do_process_dataset();
}
});
});
},
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 = instance.web.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(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 instance.web.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');
- this.add_group_mutex.exec(function() {
+ 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;
+ }
+ });
+ }).done( function () {return self.add_group_mutex.exec(function() {
self.do_clear_groups();
self.dataset.ids = [];
if (!groups.length) {
self.no_result();
- return false;
+ return $.when();
}
self.nb_records = 0;
- var remaining = groups.length - 1,
- groups_array = [];
+ var groups_array = [];
return $.when.apply(null, _.map(groups, function (group, index) {
var def = $.when([]);
var dataset = new instance.web.DataSetSearch(self, self.dataset.model,
self.nb_records += records.length;
self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
groups_array[index] = new instance.web_kanban.KanbanGroup(self, records, group, dataset);
- if (!remaining--) {
- self.dataset.index = self.dataset.size() ? 0 : null;
- return self.do_add_groups(groups_array);
- }
});
})).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() {
- var def = $.Deferred();
self.do_clear_groups();
self.dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit }).done(function(records) {
var kgroup = new instance.web_kanban.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 def;
});
+ return def;
},
do_reload: function() {
this.do_search(this.search_domain, this.search_context, this.search_group_by);
},
on_groups_started: function() {
var self = this;
- if (this.group_by) {
+ if (this.group_by || this.fields_keys.indexOf("sequence") !== -1) {
// Kanban cards drag'n'drop
- var prev_widget, is_folded, record;
- var $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards, .oe_kanban_column .oe_kanban_folded_column_cards');
+ var prev_widget, is_folded, record, $columns;
+ if (this.group_by) {
+ $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards, .oe_kanban_column .oe_kanban_folded_column_cards');
+ } else {
+ $columns = this.$el.find('.oe_kanban_column_cards');
+ }
$columns.sortable({
handle : '.oe_kanban_draghandle',
start: function(event, ui) {
stop: function(event, ui) {
var stop_index = ui.item.index();
if (start_index !== stop_index) {
- var $start_column = $('.oe_kanban_groups_records .oe_kanban_column').eq(start_index);
- var $stop_column = $('.oe_kanban_groups_records .oe_kanban_column').eq(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];
},
on_record_moved : function(record, old_group, old_index, new_group, new_index) {
var self = this;
- record.$el.children().find('[title]').tooltip('destroy');
+ record.$el.find('[title]').tooltip('destroy');
$(old_group.$el).add(new_group.$el).find('.oe_kanban_aggregates, .oe_kanban_group_length').hide();
if (old_group === new_group) {
new_group.records.splice(old_index, 1);
},
do_show: function() {
- if (this.$buttons) {
+ if (this.options.$buttons) {
this.$buttons.show();
}
this.do_push_state({});
this.dataset = dataset;
this.dataset_offset = 0;
this.aggregates = {};
- this.value = this.title = null;
+ 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) {
});
},
start: function() {
- var self = this;
+ var self = this,
+ def = this._super();
if (! self.view.group_by) {
self.$el.addClass("oe_kanban_no_group");
self.quick = new (get_class(self.view.quick_create_class))(this, self.dataset, {}, false)
this.$records.data('widget', this);
this.$has_been_started.resolve();
var add_btn = this.$el.find('.oe_kanban_add');
- add_btn.tooltip({html: true, delay: { show: 500, hide:1000 }});
+ 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) {
}
});
this.is_started = true;
- var def_tooltip = this.fetch_tooltip();
- return $.when(def_tooltip);
- },
+ 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 field_name = this.view.group_by;
- var field = this.view.group_by_field;
- var field_desc = null;
+ return;
+ var options = null;
var recurse = function(node) {
- if (node.tag === "field" && node.attrs.name === field_name) {
- field_desc = node;
+ if (node.tag === "field" && node.attrs.name == self.view.group_by) {
+ options = instance.web.py_eval(node.attrs.options || '{}');
return;
}
_.each(node.children, function(child) {
- if (field_desc === null)
- recurse(child);
+ recurse(child);
});
};
recurse(this.view.fields_view.arch);
- if (! field_desc)
- return;
- var options = instance.web.py_eval(field_desc.attrs.options || '{}')
- if (! options.tooltip_on_group_by)
- return;
-
- var self = this;
- if (this.value) {
- return (new instance.web.Model(field.relation)).query([options.tooltip_on_group_by])
- .filter([["id", "=", this.value]]).first().then(function(res) {
- self.tooltip = res[options.tooltip_on_group_by];
- self.$(".oe_kanban_group_title_text").attr("title", self.tooltip || self.title || "").tooltip({html: true, placement: 'bottom'});
- });
+ if (options && options.group_by_tooltip) {
+ this.tooltip = _.union(
+ [this.title],
+ _.map(
+ options.group_by_tooltip,
+ function (key, value, list) { return self.values && self.values[value] || ''; })
+ ).join('\n\n');
+ this.$(".oe_kanban_group_title_text").attr("title", this.tooltip || this.title || "");
}
},
compute_cards_auto_height: function() {
});
var am = instance.webclient.action_manager;
var form = am.dialog_widget.views.form.controller;
- form.on("on_button_cancel", am.dialog, am.dialog.close);
+ form.on("on_button_cancel", am.dialog, function() { return am.dialog.$dialog_box.modal('hide'); });
form.on('record_saved', self, function() {
- am.dialog.close();
+ am.dialog.$dialog_box.modal('hide');
self.view.do_reload();
});
},
bind_events: function() {
var self = this;
this.setup_color_picker();
- this.$el.find('[title]').tooltip({
- 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);
- },
- placement: 'bottom',
- html: true,
+ 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)
* open on form/edit view : oe_kanban_global_click_edit
*/
on_card_clicked: function(ev) {
- if(this.$el.find('.oe_kanban_global_click_edit').size()>0)
+ if (this.$el.find('.oe_kanban_global_click').size() > 0 && this.$el.find('.oe_kanban_global_click').data('routing')) {
+ instance.web.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();
var button_attrs = $action.data();
this.view.do_execute_action(button_attrs, this.view.dataset, this.id, this.do_reload);
},
+ do_action_url: function($action) {
+ return instance.web.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) {
*/
instance.web_kanban.QuickCreate = instance.web.Widget.extend({
template: 'KanbanView.quick_create',
-
+
/**
* close_btn: If true, the widget will display a "Close" button able to trigger
* a "close" event.
},
});
+instance.web_kanban.Priority = instance.web_kanban.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() {
+ var self = this;
+ this.record_id = self.parent.id;
+ this.priorities = self.prepare_priority();
+ this.$el = $(QWeb.render("Priority", {'widget': this}));
+ 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();
+ },
+});
+
+instance.web_kanban.KanbanSelection = instance.web_kanban.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]]];
+ }
+ 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();
+ },
+});
+
instance.web_kanban.fields_registry = new instance.web.Registry({});
+instance.web_kanban.fields_registry.add('priority','instance.web_kanban.Priority');
+instance.web_kanban.fields_registry.add('kanban_state_selection','instance.web_kanban.KanbanSelection');
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: