openerp.web_kanban = function (instance) { var _t = instance.web._t, _lt = instance.web._lt; var QWeb = instance.web.qweb; instance.web.views.add('kanban', 'instance.web_kanban.KanbanView'); instance.web_kanban.KanbanView = instance.web.View.extend({ template: "KanbanView", display_name: _lt('Kanban'), default_nr_columns: 3, view_type: "kanban", quick_create_class: "instance.web_kanban.QuickCreate", number_of_color_schemes: 10, init: function (parent, dataset, view_id, options) { this._super(parent, dataset, view_id, options); _.defaults(this.options, { "quick_creatable": true, "creatable": true, "create_text": undefined, "read_only_mode": false, "confirm_on_delete": true, }); this.fields_view = {}; this.fields_keys = []; this.group_by = null; this.state = { groups : {}, records : {} }; this.groups = []; this.form_dialog = new instance.web.form.FormDialog(this, {}, this.options.action_views_ids.form, dataset).start(); this.form_dialog.on_form_dialog_saved.add_last(this.do_reload); this.aggregates = {}; this.group_operators = ['avg', 'max', 'min', 'sum', 'count']; this.qweb = new QWeb2.Engine(); this.qweb.debug = instance.connection.debug; this.qweb.default_dict = _.clone(QWeb.default_dict); this.has_been_loaded = $.Deferred(); this.search_domain = this.search_context = this.search_group_by = null; this.currently_dragging = {}; this.limit = options.limit || 80; this.add_group_mutex = new $.Mutex(); }, start: function() { var self = this; var def = this._super.apply(this, arguments); // Bind kanban cards dropdown menus $('html').on('click.kanban', function() { self.trigger('hide_menus'); }); this.on('hide_menus', this, function() { self.$element.find('.oe_kanban_menu').hide(); }); return def; }, destroy: function() { this._super.apply(this, arguments); $('html').off('click.kanban'); }, on_loaded: function(data) { this.fields_view = data; this.$buttons = $(QWeb.render("KanbanView.buttons", {'widget': this})); if (this.options.$buttons) { this.$buttons.appendTo(this.options.$buttons); } else { this.$element.find('.oe_kanban_buttons').replaceWith(this.$buttons); } this.$buttons .on('click','button.oe_kanban_button_new', this.do_add_record); this.$groups = this.$element.find('.oe_kanban_groups tr'); this.fields_keys = _.keys(this.fields_view.fields); this.add_qweb_template(); this.has_been_loaded.resolve(); return $.when(); }, _is_quick_create_enabled: function() { if (! this.options.quick_creatable) return false; if (this.fields_view.arch.attrs.quick_create !== undefined) return JSON.parse(this.fields_view.arch.attrs.quick_create); return !! this.group_by; }, _is_create_enabled: function() { if (! this.options.creatable) return false; if (this.fields_view.arch.attrs.create !== undefined) return JSON.parse(this.fields_view.arch.attrs.create); return true; }, add_qweb_template: function() { for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) { var child = this.fields_view.arch.children[i]; if (child.tag === "templates") { this.transform_qweb_template(child); this.qweb.add_template(instance.web.json_node_to_xml(child)); break; } else if (child.tag === 'field') { this.extract_aggregates(child); } } }, extract_aggregates: function(node) { for (var j = 0, jj = this.group_operators.length; j < jj; j++) { if (node.attrs[this.group_operators[j]]) { this.aggregates[node.attrs.name] = node.attrs[this.group_operators[j]]; break; } } }, transform_qweb_template: function(node) { var qweb_add_if = function(node, condition) { if (node.attrs[QWeb.prefix + '-if']) { condition = _.str.sprintf("(%s) and (%s)", node.attrs[QWeb.prefix + '-if'], condition); } node.attrs[QWeb.prefix + '-if'] = condition; }; // Process modifiers if (node.tag && node.attrs.modifiers) { var modifiers = JSON.parse(node.attrs.modifiers || '{}'); if (modifiers.invisible) { qweb_add_if(node, _.str.sprintf("!kanban_compute_domain(%s)", JSON.stringify(modifiers.invisible))); } } switch (node.tag) { case 'field': node.tag = QWeb.prefix; node.attrs[QWeb.prefix + '-esc'] = 'record.' + node.attrs['name'] + '.value'; this.extract_aggregates(node); break; case 'button': case 'a': var type = node.attrs.type || ''; if (_.indexOf('action,object,edit,delete'.split(','), type) !== -1) { _.each(node.attrs, function(v, k) { if (_.indexOf('icon,type,name,args,string,context,states,kanban_states'.split(','), k) != -1) { node.attrs['data-' + k] = v; delete(node.attrs[k]); } }); if (node.attrs['data-string']) { node.attrs.title = node.attrs['data-string']; } if (node.attrs['data-icon']) { node.children = [{ tag: 'img', attrs: { src: instance.connection.prefix + '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png', width: '16', height: '16' } }]; } if (node.tag == 'a') { node.attrs.href = '#'; } else { node.attrs.type = 'button'; } node.attrs['class'] = (node.attrs['class'] || '') + ' oe_kanban_action oe_kanban_action_' + node.tag; } break; } if (node.children) { for (var i = 0, ii = node.children.length; i < ii; i++) { this.transform_qweb_template(node.children[i]); } } }, do_add_record: function() { this.dataset.index = null; this.do_switch_view('form'); }, do_search: function(domain, context, group_by) { var self = this; this.$element.find('.oe_view_nocontent').remove(); this.search_domain = domain; this.search_context = context; this.search_group_by = group_by; $.when(this.has_been_loaded).then(function() { self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by; self.datagroup = new instance.web.DataGroup(self, self.dataset.model, domain, context, self.group_by ? [self.group_by] : []); self.datagroup.list(self.fields_keys, self.do_process_groups, self.do_process_dataset); }); }, do_process_groups: function(groups) { var self = this; this.add_group_mutex.exec(function() { self.do_clear_groups(); self.dataset.ids = []; var remaining = groups.length - 1, groups_array = []; return $.when.apply(null, _.map(groups, function (group, index) { var dataset = new instance.web.DataSetSearch(self, self.dataset.model, group.context, group.domain); return dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit }) .pipe(function(records) { 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); } }); })); }); }, do_process_dataset: function(dataset) { var self = this; 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 }).then(function(records) { if (_.isEmpty(records)) { self.no_result(); def.reject(); } else { var kgroup = new instance.web_kanban.KanbanGroup(self, records, null, self.dataset); self.do_add_groups([kgroup]).then(function() { def.resolve(); }); } }).then(null, function() { def.reject(); }); return def; }); }, do_reload: function() { this.do_search(this.search_domain, this.search_context, this.search_group_by); }, do_clear_groups: function() { _.each(this.groups, function(group) { group.destroy(); }); this.groups = []; this.$element.find('.oe_kanban_groups_headers, .oe_kanban_groups_records').empty(); }, do_add_groups: function(groups) { var self = this; _.each(groups, function(group) { self.groups[group.undefined_title ? 'unshift' : 'push'](group); }); var groups_started = _.map(this.groups, function(group) { return group.appendTo(self.$element.find('.oe_kanban_groups_headers')); }); return $.when.apply(null, groups_started).then(function () { self.on_groups_started(); }); }, on_groups_started: function() { var self = this; this.compute_groups_width(); if (this.group_by) { this.$element.find('.oe_kanban_column').sortable({ connectWith: '.oe_kanban_column', handle : '.oe_kanban_draghandle', start: function(event, ui) { self.currently_dragging.index = ui.item.index(); self.currently_dragging.group = ui.item.parents('.oe_kanban_column:first').data('widget'); }, stop: function(event, ui) { var record = ui.item.data('widget'), old_index = self.currently_dragging.index, new_index = ui.item.index(), old_group = self.currently_dragging.group, new_group = ui.item.parents('.oe_kanban_column:first').data('widget'); if (!(old_group.title === new_group.title && old_group.value === new_group.value && old_index == new_index)) { self.on_record_moved(record, old_group, old_index, new_group, new_index); } }, scroll: false }); } else { this.$element.find('.oe_kanban_draghandle').removeClass('oe_kanban_draghandle'); } }, on_record_moved : function(record, old_group, old_index, new_group, new_index) { var self = this; $.fn.tipsy.clear(); $(old_group.$element).add(new_group.$element).find('.oe_kanban_aggregates, .oe_kanban_group_length').hide(); if (old_group === new_group) { new_group.records.splice(old_index, 1); new_group.records.splice(new_index, 0, record); new_group.do_save_sequences(); } else { old_group.records.splice(old_index, 1); new_group.records.splice(new_index, 0, record); record.group = new_group; var data = {}; data[this.group_by] = new_group.value; this.dataset.write(record.id, data, {}, function() { record.do_reload(); new_group.do_save_sequences(); }).fail(function(error, evt) { evt.preventDefault(); alert("An error has occured while moving the record to this group."); self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails }); } }, compute_groups_width: function() { var unfolded = 0; _.each(this.groups, function(group) { unfolded += group.state.folded ? 0 : 1; group.$element.css('width', ''); }); _.each(this.groups, function(group) { if (!group.state.folded) { group.$element.css('width', Math.round(100/unfolded) + '%'); } }); }, do_show: function() { if (this.$buttons) { this.$buttons.show(); } this.do_push_state({}); return this._super(); }, do_hide: function () { if (this.$buttons) { this.$buttons.hide(); } return this._super(); }, open_record: function(id) { if (this.dataset.select_id(id)) { this.do_switch_view('form', null, { editable: true }); } else { this.do_warn("Kanban: could not find id#" + id); } }, no_result: function() { if (this.groups.group_by || !this.options.action || !this.options.action.help) { return; } this.$element.find('.oe_view_nocontent').remove(); this.$element.prepend( $('