this.currently_dragging = {};
this.limit = options.limit || 40;
this.add_group_mutex = new $.Mutex();
+ this.last_position = 'static';
},
view_loading: function(r) {
return this.load_kanban(r);
this._super.apply(this, arguments);
this.$el.on('click', '.oe_kanban_dummy_cell', function() {
if (self.$buttons) {
- self.$buttons.find('.oe_kanban_add_column').effect('bounce', {distance: 18, times: 5}, 250);
+ self.$buttons.find('.oe_kanban_add_column').openerpBounce();
}
});
},
}
switch (node.tag) {
case 'field':
- if (this.fields_view.fields[node.attrs.name].type === 'many2many') {
- this.many2manys.push(node.attrs.name);
+ var ftype = this.fields_view.fields[node.attrs.name].type;
+ ftype = node.attrs.widget ? node.attrs.widget : ftype;
+ if (ftype === 'many2many') {
+ if (_.indexOf(this.many2manys, node.attrs.name) < 0) {
+ this.many2manys.push(node.attrs.name);
+ }
node.tag = 'div';
node.attrs['class'] = (node.attrs['class'] || '') + ' oe_form_field oe_tags';
+ } else if (instance.web_kanban.fields_registry.contains(ftype)) {
+ // do nothing, the kanban record will handle it
} else {
node.tag = QWeb.prefix;
node.attrs[QWeb.prefix + '-esc'] = 'record.' + node.attrs['name'] + '.value';
},
do_search: function(domain, context, group_by) {
var self = this;
- this.$el.find('.oe_view_nocontent').remove();
this.search_domain = domain;
this.search_context = context;
this.search_group_by = group_by;
- $.when(this.has_been_loaded).done(function() {
+ return $.when(this.has_been_loaded).then(function() {
self.group_by = group_by.length ? group_by[0] : self.fields_view.arch.attrs.default_group_by;
self.group_by_field = self.fields_view.fields[self.group_by] || {};
self.grouped_by_m2o = (self.group_by_field.type === 'many2one');
self.$buttons.find('.oe_alternative').toggle(self.grouped_by_m2o);
self.$el.toggleClass('oe_kanban_grouped_by_m2o', self.grouped_by_m2o);
- var grouping = new instance.web.Model(self.dataset.model, context, domain).query().group_by(self.group_by);
- $.when(grouping).done(function(groups) {
+ var grouping_fields = self.group_by ? [self.group_by].concat(_.keys(self.aggregates)) : undefined;
+ if (!_.isEmpty(grouping_fields)) {
+ // ensure group_by fields are read.
+ 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) {
+ self.remove_no_result();
if (groups) {
self.do_process_groups(groups);
} else {
},
do_process_groups: function(groups) {
var self = this;
+ this.$el.find('table:first').show();
this.$el.removeClass('oe_kanban_ungrouped').addClass('oe_kanban_grouped');
this.add_group_mutex.exec(function() {
+ self.do_clear_groups();
self.dataset.ids = [];
- var remaining = groups.length - 1,
- groups_array = [];
+ if (!groups.length) {
+ self.no_result();
+ return false;
+ }
+ self.nb_records = 0;
+ var groups_array = [];
return $.when.apply(null, _.map(groups, function (group, index) {
- self.do_clear_groups();
+ var def = $.when([]);
var dataset = new instance.web.DataSetSearch(self, self.dataset.model,
new instance.web.CompoundContext(self.dataset.get_context(), group.model.context()), group.model.domain());
- return dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit })
- .then(function(records) {
+ if (group.attributes.length >= 1) {
+ def = dataset.read_slice(self.fields_keys.concat(['__last_update']), { 'limit': self.limit });
+ }
+ return def.then(function(records) {
+ self.nb_records += records.length;
self.dataset.ids.push.apply(self.dataset.ids, dataset.ids);
groups_array[index] = new 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);
+ });
});
},
do_process_dataset: function() {
var self = this;
+ this.$el.find('table:first').show();
this.$el.removeClass('oe_kanban_grouped').addClass('oe_kanban_ungrouped');
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) {
- self.do_clear_groups();
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();
var $last_td = self.$el.find('.oe_kanban_groups_headers td:last');
var groups_started = _.map(this.groups, function(group) {
if (!group.is_started) {
+ group.on("add_record", self, function () {
+ self.remove_no_result();
+ });
return group.insertBefore($last_td);
}
});
},
on_groups_started: function() {
var self = this;
- this.compute_groups_width();
if (this.group_by) {
// Kanban cards drag'n'drop
- var $columns = this.$el.find('.oe_kanban_column .oe_kanban_column_cards');
+ 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');
$columns.sortable({
handle : '.oe_kanban_draghandle',
start: function(event, ui) {
self.currently_dragging.index = ui.item.parent().children('.oe_kanban_record').index(ui.item);
- self.currently_dragging.group = ui.item.parents('.oe_kanban_column:first').data('widget');
+ self.currently_dragging.group = prev_widget = ui.item.parents('.oe_kanban_column:first').data('widget');
ui.item.find('*').on('click.prevent', function(ev) {
return false;
});
+ record = ui.item.data('widget');
+ record.$el.bind('mouseup',function(ev,ui){
+ if (is_folded) {
+ record.$el.hide();
+ }
+ record.$el.unbind('mouseup');
+ })
ui.placeholder.height(ui.item.height());
},
+ over: function(event, ui) {
+ var parent = $(event.target).parent();
+ prev_widget.highlight(false);
+ is_folded = parent.hasClass('oe_kanban_group_folded');
+ if (is_folded) {
+ var widget = parent.data('widget');
+ widget.highlight(true);
+ prev_widget = widget;
+ }
+ },
revert: 150,
stop: function(event, ui) {
- var record = ui.item.data('widget');
+ prev_widget.highlight(false);
var old_index = self.currently_dragging.index;
var new_index = ui.item.parent().children('.oe_kanban_record').index(ui.item);
var old_group = self.currently_dragging.group;
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];
this.dataset.write(record.id, data, {}).done(function() {
record.do_reload();
new_group.do_save_sequences();
+ if (new_group.state.folded) {
+ new_group.do_action_toggle_fold();
+ record.prependTo(new_group.$records.find('.oe_kanban_column_cards'));
+ }
}).fail(function(error, evt) {
evt.preventDefault();
- alert(_t("An error has occured while moving the record to this group."));
+ alert(_t("An error has occured while moving the record to this group: ") + error.data.message);
self.do_reload(); // TODO: use draggable + sortable in order to cancel the dragging when the rcp fails
});
}
},
- compute_groups_width: function() {
- var unfolded = 0;
- var self = this;
- _.each(this.groups, function(group) {
- unfolded += group.state.folded ? 0 : 1;
- group.$el.children(':first').css('width', '');
- });
- _.each(this.groups, function(group) {
- if (!group.state.folded) {
- if (182*unfolded>=self.$el.width()) {
- group.$el.children(':first').css('width', "170px");
- } else if (262*unfolded<self.$el.width()) {
- group.$el.children(':first').css('width', "250px");
- } else {
- // -12 because of padding 6 between cards
- // -1 because of the border of the latest dummy column
- group.$el.children(':first').css('width', Math.floor((self.$el.width()-1)/unfolded)-12 + 'px');
- }
- }
- });
- },
do_show: function() {
if (this.$buttons) {
}
},
no_result: function() {
+ var self = this;
if (this.groups.group_by
|| !this.options.action
- || !this.options.action.help) {
+ || (!this.options.action.help && !this.options.action.get_empty_list_help)) {
return;
}
- this.$el.find('.oe_view_nocontent').remove();
- this.$el.prepend(
- $('<div class="oe_view_nocontent">').html(this.options.action.help)
- );
- var create_nocontent = this.$buttons;
+ this.last_position = this.$el.find('table:first').css("position");
+ this.$el.find('table:first').css("position", "absolute");
+ $(QWeb.render('KanbanView.nocontent', { content : this.options.action.get_empty_list_help || this.options.action.help})).insertAfter(this.$('table:first'));
this.$el.find('.oe_view_nocontent').click(function() {
- create_nocontent.effect('bounce', {distance: 18, times: 5}, 250);
+ self.$buttons.openerpBounce();
});
},
+ remove_no_result: function() {
+ this.$el.find('table:first').css("position", this.last_position);
+ this.$el.find('.oe_view_nocontent').remove();
+ },
/*
* postprocessing of fields type many2many
} catch(e) {}
}
_.each(this.view.aggregates, function(value, key) {
- self.aggregates[value] = group.get('aggregates')[key];
+ self.aggregates[value] = instance.web.format_value(group.get('aggregates')[key], {type: 'float'});
});
}
});
},
start: function() {
- var self = this,
- def = this._super();
+ var self = this;
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.$el.find('.oe_kanban_add').click(function () {
+ if (self.view.quick) {
+ self.view.quick.trigger('close');
+ }
if (self.quick) {
- return self.quick.trigger('close');
+ return false;
}
+ self.view.$el.find('.oe_view_nocontent').hide();
var ctx = {};
ctx['default_' + self.view.group_by] = self.value;
self.quick = new (get_class(self.view.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);
this.$records.find(".oe_kanban_column_cards").click(function (ev) {
if (ev.target == ev.currentTarget) {
if (!self.state.folded) {
- add_btn.effect('bounce', {distance: 18, times: 5}, 250);
+ add_btn.openerpBounce();
}
}
});
this.is_started = true;
- return def;
+ var def_tooltip = this.fetch_tooltip();
+ return $.when(def_tooltip);
+ },
+ fetch_tooltip: function() {
+ if (! this.group)
+ return;
+ var field_name = this.view.group_by;
+ var field = this.view.group_by_field;
+ var field_desc = null;
+ var recurse = function(node) {
+ if (node.tag === "field" && node.attrs.name === field_name) {
+ field_desc = node;
+ return;
+ }
+ _.each(node.children, function(child) {
+ if (field_desc === null)
+ 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 || "").tipsy({html: true});
+ });
+ }
},
compute_cards_auto_height: function() {
// oe_kanban_no_auto_height is an empty class used to disable this feature
'limit': self.view.limit,
'offset': self.dataset_offset += self.view.limit
}).then(function(records) {
- self.view.dataset.ids = ids.concat(self.view.dataset.ids);
+ self.view.dataset.ids = ids.concat(self.dataset.ids);
self.do_add_records(records);
self.compute_cards_auto_height();
+ self.view.postprocess_m2m_tags();
return records;
});
},
},
do_action_toggle_fold: function() {
this.do_toggle_fold();
- this.view.compute_groups_width();
},
do_action_edit: function() {
var self = this;
*/
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');
+ }
}
});
};
}
this.state = this.view.state.records[this.id];
+ this.fields = {};
},
set_record: function(record) {
var self = this;
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();
},
},
renderElement: function() {
this.qweb_context = {
+ instance: instance,
record: this.record,
widget: this,
read_only_mode: this.view.options.read_only_mode,
'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 = instance.web_kanban.fields_registry.get_object(type);
+ var widget = new obj(this, field, $orig);
+ this.sub_widgets.push(widget);
+ widget.replace($node);
},
bind_events: function() {
var self = this;
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.$el.data('widget', self);
- self.bind_events();
+ self.init_content();
self.group.compute_cards_auto_height();
self.view.postprocess_m2m_tags();
} else {
var color = this.kanban_getcolor(variable);
return color === '' ? '' : 'oe_kanban_color_' + color;
},
- kanban_gravatar: function(email, size) {
- size = size || 22;
- email = _.str.trim(email || '').toLowerCase();
- var default_ = _.str.isBlank(email) ? 'mm' : 'identicon';
- var email_md5 = $.md5(email);
- return 'http://www.gravatar.com/avatar/' + email_md5 + '.png?s=' + size + '&d=' + default_;
- },
kanban_image: function(model, field, id, cache, options) {
options = options || {};
var url;
*/
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.
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();
quick_add: function () {
var self = this;
var val = this.$input.val();
- if (/^\s*$/.test(val)) { return; }
+ if (/^\s*$/.test(val)) { this.$el.remove(); return; }
this._dataset.call(
'name_create', [val, new instance.web.CompoundContext(
this._dataset.get_context(), this._context)])
});
}
});
+
+/**
+ * Interface to be implemented by kanban fields.
+ *
+ */
+instance.web_kanban.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.
+ *
+ */
+instance.web_kanban.AbstractField = instance.web.Widget.extend(instance.web_kanban.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 = instance.web.py_eval(this.$node.attr("options") || '{}');
+ this.set("value", field.raw_value);
+ },
+});
+
+instance.web_kanban.fields_registry = new instance.web.Registry({});
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: