1 openerp.web_kanban = function (openerp) {
3 var _t = openerp.web._t;
4 var QWeb = openerp.web.qweb;
5 openerp.web.views.add('kanban', 'openerp.web_kanban.KanbanView');
7 openerp.web_kanban.KanbanView = openerp.web.View.extend({
8 template: "KanbanView",
10 init: function (parent, dataset, view_id, options) {
12 this.set_default_options(options);
13 this.dataset = dataset;
14 this.view_id = view_id;
15 this.fields_view = {};
16 this.fields_keys = [];
23 this.form_dialog = new openerp.web.FormDialog(this, {}, this.options.action_views_ids.form, dataset).start();
24 this.form_dialog.on_form_dialog_saved.add_last(this.do_reload);
26 this.group_operators = ['avg', 'max', 'min', 'sum', 'count'];
27 this.qweb = new QWeb2.Engine();
28 this.qweb.debug = openerp.connection.debug;
29 this.qweb.default_dict = _.clone(QWeb.default_dict);
30 this.has_been_loaded = $.Deferred();
31 this.search_domain = this.search_context = this.search_group_by = null;
32 this.currently_dragging = {};
36 this.$element.find('button.oe_kanban_button_new').click(this.do_add_record);
37 this.$groups = this.$element.find('.oe_kanban_groups tr');
38 var context = new openerp.web.CompoundContext(this.dataset.get_context());
39 return this.rpc('/web/view/load', {
40 'model': this.dataset.model,
41 'view_id': this.view_id,
42 'view_type': 'kanban',
46 on_loaded: function(data) {
47 this.fields_view = data;
48 this.fields_keys = _.keys(this.fields_view.fields);
49 this.add_qweb_template();
50 this.has_been_loaded.resolve();
52 add_qweb_template: function() {
53 for (var i=0, ii=this.fields_view.arch.children.length; i < ii; i++) {
54 var child = this.fields_view.arch.children[i];
55 if (child.tag === "templates") {
56 this.transform_qweb_template(child);
57 this.qweb.add_template(openerp.web.json_node_to_xml(child));
59 } else if (child.tag === 'field') {
60 this.extract_aggregates(child);
64 extract_aggregates: function(node) {
65 for (var j = 0, jj = this.group_operators.length; j < jj; j++) {
66 if (node.attrs[this.group_operators[j]]) {
67 this.aggregates[node.attrs.name] = node.attrs[this.group_operators[j]];
72 transform_qweb_template: function(node) {
73 var qweb_prefix = QWeb.prefix;
76 node.tag = qweb_prefix;
77 node.attrs[qweb_prefix + '-esc'] = 'record.' + node.attrs['name'] + '.value';
78 this.extract_aggregates(node);
82 var type = node.attrs.type || '';
83 if (_.indexOf('action,object,edit,delete,color'.split(','), type) !== -1) {
84 _.each(node.attrs, function(v, k) {
85 if (_.indexOf('icon,type,name,args,string,context,states,kanban_states'.split(','), k) != -1) {
86 node.attrs['data-' + k] = v;
87 delete(node.attrs[k]);
90 if (node.attrs['data-states']) {
91 var states = _.map(node.attrs['data-states'].split(','), function(state) {
92 return "record.state.raw_value == '" + _.str.trim(state) + "'";
94 node.attrs[qweb_prefix + '-if'] = states.join(' or ');
96 if (node.attrs['data-kanban_states']) {
97 var states = _.map(node.attrs['data-kanban_states'].split(','), function(state) {
98 return "record.kanban_state.raw_value == '" + _.str.trim(state) + "'";
100 node.attrs[qweb_prefix + '-if'] = states.join(' or ');
102 if (node.attrs['data-string']) {
103 node.attrs.title = node.attrs['data-string'];
105 if (node.attrs['data-icon']) {
109 src: openerp.connection.server + '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png',
115 if (node.tag == 'a') {
116 node.attrs.href = '#';
118 node.attrs.type = 'button';
120 node.attrs['class'] = (node.attrs['class'] || '') + ' oe_kanban_action oe_kanban_action_' + node.tag;
125 for (var i = 0, ii = node.children.length; i < ii; i++) {
126 this.transform_qweb_template(node.children[i]);
130 do_add_record: function() {
131 this.dataset.index = null;
132 this.do_switch_view('form');
134 do_search: function(domain, context, group_by) {
136 this.search_domain = domain;
137 this.search_context = context;
138 this.search_group_by = group_by;
139 $.when(this.has_been_loaded).then(function() {
140 self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by;
141 self.datagroup = new openerp.web.DataGroup(self, self.dataset.model, domain, context, self.group_by ? [self.group_by] : []);
142 self.datagroup.list(self.fields_keys, self.do_process_groups, self.do_process_dataset);
145 do_process_groups: function(groups) {
146 this.do_clear_groups();
147 this.dataset.ids = [];
149 remaining = groups.length - 1,
151 _.each(groups, function (group, index) {
152 var group_name = group.value,
153 group_value = group.value,
154 group_aggregates = {};
155 if (group.value instanceof Array) {
156 group_name = group.value[1];
157 group_value = group.value[0];
159 _.each(self.aggregates, function(value, key) {
160 group_aggregates[value] = group.aggregates[key];
162 var dataset = new openerp.web.DataSetSearch(self, self.dataset.model, group.context, group.domain);
163 dataset.read_slice(self.fields_keys, {'domain': group.domain, 'context': group.context}, function(records) {
164 self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
165 groups_array[index] = new openerp.web_kanban.KanbanGroup(self, records, group_value, group_name, group_aggregates);
167 self.dataset.index = self.dataset.ids.length ? 0 : null;
168 self.do_add_groups(groups_array);
173 do_process_dataset: function(dataset) {
175 this.do_clear_groups();
176 this.dataset.read_slice(this.fields_keys, {}, function(records) {
178 while (records.length) {
179 for (var i = 0; i < self.default_nr_columns; i++) {
183 groups[i].push(records.shift());
186 for (var i = 0; i < groups.length; i++) {
187 groups[i] = new openerp.web_kanban.KanbanGroup(self, _.compact(groups[i]));
189 self.do_add_groups(groups);
192 do_reload: function() {
193 this.do_search(this.search_domain, this.search_context, this.search_group_by);
195 do_clear_groups: function() {
196 _.each(this.groups, function(group) {
200 this.$element.find('.oe_kanban_groups_headers, .oe_kanban_groups_records').empty();
202 do_add_groups: function(groups) {
204 _.each(groups, function(group) {
205 self.groups[group.undefined_title ? 'unshift' : 'push'](group);
207 _.each(this.groups, function(group) {
208 group.appendTo(self.$element.find('.oe_kanban_groups_headers'));
210 this.on_groups_started();
212 on_groups_started: function() {
214 this.compute_groups_width();
216 this.$element.find('.oe_kanban_column').sortable({
217 connectWith: '.oe_kanban_column',
218 handle : '.oe_kanban_draghandle',
219 start: function(event, ui) {
220 self.currently_dragging.index = ui.item.index();
221 self.currently_dragging.group = ui.item.parents('.oe_kanban_column:first').data('widget');
223 stop: function(event, ui) {
224 var record = ui.item.data('widget'),
225 old_index = self.currently_dragging.index,
226 new_index = ui.item.index(),
227 old_group = self.currently_dragging.group,
228 new_group = ui.item.parents('.oe_kanban_column:first').data('widget');
229 if (!(old_group.title === new_group.title && old_group.value === new_group.value && old_index == new_index)) {
230 self.on_record_moved(record, old_group, old_index, new_group, new_index);
236 this.$element.find('.oe_kanban_draghandle').removeClass('oe_kanban_draghandle');
239 on_record_moved : function(record, old_group, old_index, new_group, new_index) {
241 if (old_group === new_group) {
242 new_group.records.splice(old_index, 1);
243 new_group.records.splice(new_index, 0, record);
244 new_group.do_save_sequences();
246 old_group.records.splice(old_index, 1);
247 new_group.records.splice(new_index, 0, record);
248 record.group = new_group;
250 data[this.group_by] = new_group.value;
251 this.dataset.write(record.id, data, {}, function() {
253 new_group.do_save_sequences();
254 }).fail(function(error, evt) {
255 evt.preventDefault();
256 alert("An error has occured while moving the record to this group.");
257 self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
261 compute_groups_width: function() {
263 _.each(this.groups, function(group) {
264 unfolded += group.state.folded ? 0 : 1;
265 group.$element.css('width', '');
267 _.each(this.groups, function(group) {
268 if (!group.state.folded) {
269 group.$element.css('width', Math.round(100/unfolded) + '%');
275 openerp.web_kanban.KanbanGroup = openerp.web.Widget.extend({
276 template: 'KanbanView.group_header',
277 init: function (parent, records, value, title, aggregates) {
283 if (title === false) {
284 this.title = _t('Undefined');
285 this.undefined_title = true;
287 this.aggregates = aggregates || {};
288 var key = this.view.group_by + '-' + value;
289 if (!this.view.state.groups[key]) {
290 this.view.state.groups[key] = {
294 this.state = this.view.state.groups[key];
295 this.$records = null;
296 this.records = _.map(records, function(record) {
297 return new openerp.web_kanban.KanbanRecord(self, record);
303 this.$records = $(QWeb.render('KanbanView.group_records_container', { widget : this}));
304 this.$records.appendTo(this.view.$element.find('.oe_kanban_groups_records'));
305 _.each(this.records, function(record) {
306 record.appendTo(self.$records);
308 this.$element.find(".oe_kanban_fold_icon").click(function() {
309 self.do_toggle_fold();
310 self.view.compute_groups_width();
313 if (this.state.folded) {
314 this.do_toggle_fold();
316 this.$element.data('widget', this);
317 this.$records.data('widget', this);
323 this.$records.remove();
326 remove_record: function(id, remove_from_dataset) {
327 for (var i = 0, ii = this.records.length; i < ii; i++) {
328 if (this.records[i]['id'] === id) {
329 this.records.splice(i, 1);
333 do_toggle_fold: function(compute_width) {
334 this.$element.toggleClass('oe_kanban_group_folded');
335 this.$records.find('.oe_kanban_record').toggle();
336 this.state.folded = this.$element.is('.oe_kanban_group_folded');
338 do_save_sequences: function() {
340 if (_.indexOf(this.view.fields_keys, 'sequence') > -1) {
341 _.each(this.records, function(record, index) {
342 self.view.dataset.write(record.id, { sequence : index });
348 openerp.web_kanban.KanbanRecord = openerp.web.Widget.extend({
349 template: 'KanbanView.record',
350 init: function (parent, record) {
353 this.view = parent.view;
355 this.set_record(record);
356 if (!this.view.state.records[this.id]) {
357 this.view.state.records[this.id] = {
361 this.state = this.view.state.records[this.id];
363 set_record: function(record) {
365 this.record = this.transform_record(record);
369 this.$element.data('widget', this);
372 transform_record: function(record) {
375 _.each(record, function(value, name) {
376 var r = _.clone(self.view.fields_view.fields[name] || {});
377 if ((r.type === 'date' || r.type === 'datetime') && value) {
378 r.raw_value = openerp.web.auto_str_to_date(value);
382 r.value = openerp.web.format_value(value, r);
383 new_record[name] = r;
388 this.qweb_context = {
392 for (var p in this) {
393 if (_.str.startsWith(p, 'kanban_')) {
394 this.qweb_context[p] = _.bind(this[p], this);
398 'content': this.view.qweb.render('kanban-box', this.qweb_context)
401 bind_events: function() {
403 $show_on_click = self.$element.find('.oe_kanban_box_show_onclick');
404 $show_on_click.toggle(this.state.folded);
405 this.$element.find('.oe_kanban_box_show_onclick_trigger').click(function() {
406 $show_on_click.toggle();
407 self.state.folded = !self.state.folded;
410 this.$element.find('[tooltip]').tipTip({
412 defaultPosition: 'top',
413 content: function() {
414 var template = $(this).attr('tooltip');
415 if (!self.view.qweb.has_template(template)) {
418 return self.view.qweb.render(template, self.qweb_context);
422 this.$element.find('.oe_kanban_action').click(function() {
423 var $action = $(this),
424 type = $action.data('type') || 'button',
425 method = 'do_action_' + (type === 'action' ? 'object' : type);
426 if (_.str.startsWith(type, 'switch_')) {
427 self.view.do_switch_view(type.substr(7));
428 } else if (typeof self[method] === 'function') {
429 self[method]($action);
431 self.do_warn("Kanban: no action for type : " + type);
436 do_action_delete: function($action) {
438 if (confirm(_t("Are you sure you want to delete this record ?"))) {
439 return $.when(this.view.dataset.unlink([this.id])).then(function() {
440 self.group.remove_record(self.id)
445 do_action_edit: function($action) {
447 if ($action.attr('target') === 'dialog') {
448 this.view.form_dialog.select_id(this.id).then(function() {
449 self.view.form_dialog.open();
452 if (self.view.dataset.select_id(this.id)) {
453 this.view.do_switch_view('form');
455 this.do_warn("Kanban: could not find id#" + id);
459 do_action_color: function($action) {
461 colors = '#FFFFFF,#CCCCCC,#FFC7C7,#FFF1C7,#E3FFC7,#C7FFD5,#C7FFFF,#C7D5FF,#E3C7FF,#FFC7F1'.split(','),
462 $cpicker = $(QWeb.render('KanbanColorPicker', { colors : colors, columns: 2 }));
463 $action.after($cpicker);
464 $cpicker.mouseenter(function() {
465 clearTimeout($cpicker.data('timeoutId'));
466 }).mouseleave(function(evt) {
467 var timeoutId = setTimeout(function() { $cpicker.remove() }, 500);
468 $cpicker.data('timeoutId', timeoutId);
470 $cpicker.find('a').click(function() {
472 data[$action.data('name')] = $(this).data('color');
473 self.view.dataset.write(self.id, data, {}, function() {
474 self.record[$action.data('name')] = $(this).data('color');
481 do_action_object: function ($action) {
482 var button_attrs = $action.data();
483 this.view.do_execute_action(button_attrs, this.view.dataset, this.id, this.do_reload);
485 do_reload: function() {
487 this.view.dataset.read_ids([this.id], this.view.fields_keys, function(records) {
488 if (records.length) {
489 self.set_record(records[0]);
496 do_render: function() {
497 this.$element.html(this.render());
500 kanban_color: function(variable) {
501 var number_of_color_schemes = 10,
503 switch (typeof(variable)) {
505 for (var i=0, ii=variable.length; i<ii; i++) {
506 index += variable.charCodeAt(i);
510 index = Math.round(variable);
515 var color = (index % number_of_color_schemes);
516 return 'oe_kanban_color_' + color;
518 kanban_gravatar: function(email, size) {
520 email = _.str.trim(email || '').toLowerCase();
521 var default_ = _.str.isBlank(email) ? 'mm' : 'identicon';
522 var email_md5 = $.md5(email);
523 return 'http://www.gravatar.com/avatar/' + email_md5 + '.png?s=' + size + '&d=' + default_;
525 kanban_image: function(model, field, id) {
527 return openerp.connection.server + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + id;
529 kanban_text_ellipsis: function(s, size) {
533 } else if (s.length <= size) {
536 return s.substr(0, size) + '...';
542 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: