-openerp.web.list = function (instance) {
+(function() {
+
+var instance = openerp;
+openerp.web.list = {};
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
this.groups = groups;
$(this.groups).bind({
- 'selected': function (e, ids, records) {
- self.do_select(ids, records);
+ 'selected': function (e, ids, records, deselected) {
+ self.do_select(ids, records, deselected);
},
'deleted': function (e, ids) {
self.do_delete(ids);
* @returns {String} CSS style declaration
*/
style_for: function (record) {
- var style= '';
+ var len, style= '';
var context = _.extend({}, record.attributes, {
uid: this.session.uid,
- current_date: new Date().toString('yyyy-MM-dd')
+ current_date: moment().format('YYYY-MM-DD')
// TODO: time, datetime, relativedelta
});
-
+ var i;
+ var pair;
+ var expression;
if (this.fonts) {
- for(var i=0, len=this.fonts.length; i<len; ++i) {
- var pair = this.fonts[i],
- font = pair[0],
+ for(i=0, len=this.fonts.length; i<len; ++i) {
+ pair = this.fonts[i];
+ var font = pair[0];
expression = pair[1];
if (py.evaluate(expression, context).toJSON()) {
switch(font) {
}
if (!this.colors) { return style; }
- for(var i=0, len=this.colors.length; i<len; ++i) {
- var pair = this.colors[i],
- color = pair[0],
- expression = pair[1];
+ for(i=0, len=this.colors.length; i<len; ++i) {
+ pair = this.colors[i];
+ var color = pair[0];
+ expression = pair[1];
if (py.evaluate(expression, context).toJSON()) {
return style += 'color: ' + color + ';';
}
// Pager
if (!this.$pager) {
- this.$pager = $(QWeb.render("ListView.pager", {'widget':self}));
+ this.$pager = $(QWeb.render("ListView.pager", {'widget':self})).hide();
if (this.options.$buttons) {
this.$pager.appendTo(this.options.$pager);
} else {
this.sidebar.$el.hide();
}
//Sort
+ 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(','));
+ }
+
if(this.dataset._sort.length){
if(this.dataset._sort[0].indexOf('-') == -1){
this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown");
sort_by_column: function (e) {
e.stopPropagation();
var $column = $(e.currentTarget);
- var col_name = $column.data('id')
+ var col_name = $column.data('id');
var field = this.fields_view.fields[col_name];
- // test if the field is a function field with store=false, since it's impossible
- // for the server to sort those fields we desactivate the feature
- if (field && field.store === false) {
+ // test whether the field is sortable
+ if (field && !field.sortable) {
return false;
}
this.dataset.sort(col_name);
var total = dataset.size();
var limit = this.limit() || total;
- if (total == 0)
- this.$pager.hide();
- else
- this.$pager.css("display", "");
- this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit));
+ this.$pager.find('.oe-pager-button').toggle(total > limit);
+ this.$pager.find('.oe_pager_value').toggle(total !== 0);
var spager = '-';
if (total) {
var range_start = this.page * limit + 1;
var range_stop = range_start - 1 + limit;
+ if (this.records.length) {
+ range_stop = range_start - 1 + this.records.length;
+ }
if (range_stop > total) {
range_stop = total;
}
* @param {String} [view="page"] the view type to switch to
*/
select_record:function (index, view) {
- view = view || index == null ? 'form' : 'form';
+ view = view || index === null || index === undefined ? 'form' : 'form';
this.dataset.index = index;
_.delay(_.bind(function () {
this.do_switch_view(view);
self.dataset.index = 0;
}
} else if (self.dataset.index >= self.records.length) {
- self.dataset.index = 0;
+ self.dataset.index = self.records.length ? 0 : null;
}
self.compute_aggregates();
},
reload_record: function (record) {
var self = this;
+ var fields = this.fields_view.fields;
return this.dataset.read_ids(
[record.get('id')],
_.pluck(_(this.columns).filter(function (r) {
return r.tag === 'field';
- }), 'name')
+ }), 'name'),
+ {check_access_rule: true}
).done(function (records) {
var values = records[0];
if (!values) {
self.records.remove(record);
return;
}
- _(_.keys(values)).each(function(key){
- record.set(key, values[key], {silent: true});
+ _.each(values, function (value, key) {
+ if (fields[key] && fields[key].type === 'many2many')
+ record.set(key + '__display', false, {silent: true});
+ record.set(key, value, {silent: true});
});
record.trigger('change', record);
});
_(ids).each(function (id) {
self.records.remove(self.records.get(id));
});
- self.configure_pager(self.dataset);
+ if (self.records.length === 0 && self.dataset.size() > 0) {
+ //Trigger previous manually to navigate to previous page,
+ //If all records are deleted on current page.
+ self.$pager.find('ul li:first a').trigger('click');
+ } else if (self.dataset.size() == self.limit()) {
+ //Reload listview to update current page with next page records
+ //because pager going to be hidden if dataset.size == limit
+ self.reload();
+ } else {
+ self.configure_pager(self.dataset);
+ }
self.compute_aggregates();
});
},
* @param {Array} ids selected record ids
* @param {Array} records selected record values
*/
- do_select: function (ids, records) {
+ do_select: function (ids, records, deselected) {
+ // uncheck header hook if at least one row has been deselected
+ if (deselected) {
+ this.$('.oe_list_record_selector').prop('checked', false);
+ }
+
if (!ids.length) {
this.dataset.index = 0;
if (this.sidebar) {
return ids;
},
/**
+ * Calculate the active domain of the list view. This should be done only
+ * if the header checkbox has been checked. This is done by evaluating the
+ * search results, and then adding the dataset domain (i.e. action domain).
+ */
+ get_active_domain: function () {
+ var self = this;
+ if (this.$('.oe_list_record_selector').prop('checked')) {
+ var search_view = this.getParent().searchview;
+ var search_data = search_view.build_search_data();
+ return instance.web.pyeval.eval_domains_and_contexts({
+ domains: search_data.domains,
+ contexts: search_data.contexts,
+ group_by_seq: search_data.groupbys || []
+ }).then(function (results) {
+ var domain = self.dataset.domain.concat(results.domain || []);
+ return domain
+ });
+ }
+ else {
+ return $.Deferred().resolve();
+ }
+ },
+ /**
* Adds padding columns at the start or end of all table rows (including
* field names row)
*
this.$el.prepend(
$('<div class="oe_view_nocontent">').html(this.options.action.help)
);
- var create_nocontent = this.$buttons;
+ var $buttons = this.$buttons;
this.$el.find('.oe_view_nocontent').click(function() {
- create_nocontent.openerpBounce();
+ $buttons.width($buttons.width() + 1).openerpBounce();
});
}
});
.delegate('th.oe_list_record_selector', 'click', function (e) {
e.stopPropagation();
var selection = self.get_selection();
+ var checked = $(e.currentTarget).find('input').prop('checked');
$(self).trigger(
- 'selected', [selection.ids, selection.records]);
+ 'selected', [selection.ids, selection.records, ! checked]);
})
.delegate('td.oe_list_record_delete button', 'click', function (e) {
e.stopPropagation();
id = parseInt(ref_match[2], 10);
new instance.web.DataSet(this.view, model).name_get([id]).done(function(names) {
if (!names.length) { return; }
- record.set(column.id, names[0][1]);
+ record.set(column.id + '__display', names[0][1]);
});
}
} else if (column.type === 'many2one') {
ids = value;
}
new instance.web.Model(column.relation)
- .call('name_get', [ids]).done(function (names) {
+ .call('name_get', [ids, this.dataset.context]).done(function (names) {
// FIXME: nth horrible hack in this poor listview
record.set(column.id + '__display',
_(names).pluck(1).join(', '));
if (column.invisible === '1') {
return;
}
- if (column.tag === 'button') {
- cells.push('<td class="oe_button" title="' + column.string + '"> </td>');
- } else {
- cells.push('<td title="' + column.string + '"> </td>');
- }
+ cells.push('<td title="' + column.string + '"> </td>');
});
if (this.options.deletable) {
cells.push('<td class="oe_list_record_delete"><button type="button" style="visibility: hidden"> </button></td>');
}
},
close: function () {
- this.$row.children().last().empty();
+ this.$row.children().last().find('button').remove();
+ this.$row.children().last().find('span').remove();
this.records.reset();
},
/**
if (group.grouped_on) {
var row_data = {};
row_data[group.grouped_on] = group;
+ var group_label = _t("Undefined");
var group_column = _(self.columns).detect(function (column) {
return column.id === group.grouped_on; });
- if (! group_column) {
- throw new Error(_.str.sprintf(
- _t("Grouping on field '%s' is not possible because that field does not appear in the list view."),
- group.grouped_on));
- }
- var group_label;
- try {
- group_label = group_column.format(row_data, {
- value_if_empty: _t("Undefined"),
- process_modifiers: false
- });
- } catch (e) {
- group_label = _.str.escapeHTML(row_data[group_column.id].value);
+ if (group_column) {
+ try {
+ group_label = group_column.format(row_data, {
+ value_if_empty: _t("Undefined"),
+ process_modifiers: false
+ });
+ } catch (e) {
+ group_label = _.str.escapeHTML(row_data[group_column.id].value);
+ }
+ } else {
+ group_label = group.value;
+ if (group_label instanceof Array) {
+ group_label = group_label[1];
+ }
+ if (group_label === false) {
+ group_label = _t('Undefined');
+ }
+ group_label = _.str.escapeHTML(group_label);
}
+
// group_label is html-clean (through format or explicit
// escaping if format failed), can inject straight into HTML
$group_column.html(_.str.sprintf(_t("%s (%d)"),
bind_child_events: function (child) {
var $this = $(this),
self = this;
- $(child).bind('selected', function (e) {
+ $(child).bind('selected', function (e, _0, _1, deselected) {
// can have selections spanning multiple links
var selection = self.get_selection();
- $this.trigger(e, [selection.ids, selection.records]);
+ $this.trigger(e, [selection.ids, selection.records, deselected]);
}).bind(this.passthrough_events, function (e) {
// additional positional parameters are provided to trigger as an
// Array, following the event type or event object, but are
var view = this.view,
limit = view.limit(),
- d = new $.Deferred(),
page = this.datagroup.openable ? this.page : view.page;
- var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field"}), 'name');
+ var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field";}), 'name');
var options = { offset: page * limit, limit: limit, context: {bin_size: true} };
//TODO xmo: investigate why we need to put the setTimeout
- $.async_when().done(function() {
- dataset.read_slice(fields, options).done(function (records) {
+ return $.async_when().then(function() {
+ return dataset.read_slice(fields, options).then(function (records) {
// FIXME: ignominious hacks, parents (aka form view) should not send two ListView#reload_content concurrently
if (self.records.length) {
self.records.reset(null, {silent: true});
} else {
if (dataset.size() == records.length) {
// only one page
- self.$row.find('td.oe_list_group_pagination').empty();
+ self.$row.find('td.oe_list_group_pagination').find('button').remove();
+ self.$row.find('td.oe_list_group_pagination').find('span').remove();
} else {
var pages = Math.ceil(dataset.size() / limit);
self.$row
}))
.end()
.find('button[data-pager-action=previous]')
- .css('visibility',
- page === 0 ? 'hidden' : '')
+ .toggleClass('disabled', page === 0)
.end()
.find('button[data-pager-action=next]')
- .css('visibility',
- page === pages - 1 ? 'hidden' : '');
+ .toggleClass('disabled', page === pages - 1);
}
}
self.records.add(records, {silent: true});
list.render();
- d.resolve(list);
if (_.isEmpty(records)) {
view.no_result();
}
+ return list;
});
});
- return d.promise();
},
setup_resequence_rows: function (list, dataset) {
// drag and drop enabled if list is not sorted and there is a
// if drag to 1st row (to = 0), start sequencing from 0
// (exclusive lower bound)
seq = to ? list.records.at(to - 1).get(seqname) : 0;
- while (++seq, record = list.records.at(index++)) {
+ var fct = function (dataset, id, seq) {
+ $.async_when().done(function () {
+ var attrs = {};
+ attrs[seqname] = seq;
+ dataset.write(id, attrs);
+ });
+ };
+ while (++seq, (record = list.records.at(index++))) {
// write are independent from one another, so we can just
// launch them all at the same time and we don't really
// give a fig about when they're done
// FIXME: breaks on o2ms (e.g. Accounting > Financial
// Accounting > Taxes > Taxes, child tax accounts)
// when synchronous (without setTimeout)
- (function (dataset, id, seq) {
- $.async_when().done(function () {
- var attrs = {};
- attrs[seqname] = seq;
- dataset.write(id, attrs);
- });
- }(dataset, record.get('id'), seq));
+ fct(dataset, record.get('id'), seq);
record.set(seqname, seq);
}
}
this.datagroup.list(
_(this.view.visible_columns).chain()
- .filter(function (column) { return column.tag === 'field' })
+ .filter(function (column) { return column.tag === 'field';})
.pluck('name').value(),
function (groups) {
+ self.view.$pager.hide();
$el[0].appendChild(
self.render_groups(groups));
if (post_render) { post_render(); }
}, function (dataset) {
- self.render_dataset(dataset).done(function (list) {
+ self.render_dataset(dataset).then(function (list) {
self.children[null] = list;
self.elements =
[list.$current.replaceAll($el)[0]];
self.setup_resequence_rows(list, dataset);
+ }).always(function() {
if (post_render) { post_render(); }
+ self.view.trigger('view_list_rendered');
});
});
return $el;
return {
count: this.datagroup.length,
values: this.datagroup.aggregates
- }
+ };
}
return _(this.children).chain()
.map(function (child) {
function synchronized(fn) {
var fn_mutex = new $.Mutex();
return function () {
+ var obj = this;
var args = _.toArray(arguments);
- args.unshift(this);
- return fn_mutex.exec(fn.bind.apply(fn, args));
+ return fn_mutex.exec(function () {
+ if (obj.isDestroyed()) { return $.when(); }
+ return fn.apply(obj, args)
+ });
};
}
var DataGroup = instance.web.Class.extend({
},
list: function (fields, ifGroups, ifRecords) {
var self = this;
+ if (!_.isEmpty(this.group_by)) {
+ // ensure group_by fields are read.
+ fields = _.unique((fields || []).concat(this.group_by));
+ }
var query = this.model.query(fields).order_by(this.sort).group_by(this.group_by);
$.when(query).done(function (querygroups) {
// leaf node
var instance_ = (records[i] instanceof Record) ? records[i] : new Record(records[i]);
instance_.bind(null, this._onRecordEvent);
this._byId[instance_.get('id')] = instance_;
- if (options.at == undefined) {
+ if (options.at === undefined || options.at === null) {
this.records.push(instance_);
if (!options.silent) {
this.trigger('add', this, instance_, this.records.length-1);
if (!_(this._proxies).isEmpty()) {
var record = null;
_(this._proxies).detect(function (proxy) {
- return record = proxy.get(id);
+ record = proxy.get(id);
+ return record;
});
return record;
}
* @returns {Collection}
*/
proxy: function (section) {
- return this._proxies[section] = new Collection(null, {
+ this._proxies[section] = new Collection(null, {
parent: this,
key: section
}).bind(null, this._onRecordEvent);
+ return this._proxies[section];
},
/**
* @param {Array} [records]
var record;
for(var section in this._proxies) {
if (!this._proxies.hasOwnProperty(section)) {
- continue
+ continue;
}
if ((record = this._proxies[section].find(callback))) {
return record;
'field': 'instance.web.list.Column',
'field.boolean': 'instance.web.list.Boolean',
'field.binary': 'instance.web.list.Binary',
+ 'field.char': 'instance.web.list.Char',
'field.progressbar': 'instance.web.list.ProgressBar',
'field.handle': 'instance.web.list.Handle',
'button': 'instance.web.list.Button',
'field.many2onebutton': 'instance.web.list.Many2OneButton',
- 'field.many2many': 'instance.web.list.Many2Many'
+ 'field.reference': 'instance.web.list.Reference',
+ 'field.many2many': 'instance.web.list.Many2Many',
+ 'button.toggle_button': 'instance.web.list.toggle_button',
});
instance.web.list.columns.for_ = function (id, field, node) {
var description = _.extend({tag: node.tag}, field, node.attrs);
tag + '.'+ description.type,
tag
]);
- return new Type(id, node.tag, description)
+ return new Type(id, node.tag, description);
};
instance.web.list.Column = instance.web.Class.extend({
attrs = this.modifiers_for(row_data);
}
if (attrs.invisible) { return ''; }
-
- return QWeb.render('ListView.row.button', {
+ var template = this.icon && 'ListView.row.button' || 'ListView.row.text_button';
+ return QWeb.render(template, {
widget: this,
prefix: instance.session.prefix,
disabled: attrs.readonly
});
}
});
+instance.web.list.Char = instance.web.list.Column.extend({
+ replacement: '*',
+ /**
+ * If password field, only display replacement characters (if value is
+ * non-empty)
+ */
+ _format: function (row_data, options) {
+ var value = row_data[this.id].value;
+ if (value && this.password === 'True') {
+ return value.replace(/[\s\S]/g, _.escape(this.replacement));
+ }
+ return this._super(row_data, options);
+ }
+});
instance.web.list.ProgressBar = instance.web.list.Column.extend({
/**
* Return a formatted progress bar display
return this._super(row_data, options);
}
});
-};
-// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
+instance.web.list.Reference = instance.web.list.Column.extend({
+ _format: function (row_data, options) {
+ if (!_.isEmpty(row_data[this.id].value)) {
+ // If value, use __display version for printing
+ if (!!row_data[this.id + '__display']) {
+ row_data[this.id] = row_data[this.id + '__display'];
+ } else {
+ row_data[this.id] = {'value': ''};
+ }
+ }
+ return this._super(row_data, options);
+ }
+});
+instance.web.list.toggle_button = instance.web.list.Column.extend({
+ format: function (row_data, options) {
+ this._super(row_data, options);
+ var button_tips = JSON.parse(this.options);
+ var fieldname = this.field_name;
+ var has_value = row_data[fieldname] && !!row_data[fieldname].value;
+ this.icon = has_value ? 'gtk-yes' : 'gtk-normal';
+ this.string = has_value ? _t(button_tips ? button_tips['active']: ''): _t(button_tips ? button_tips['inactive']: '');
+ return QWeb.render('toggle_button', {
+ widget: this,
+ prefix: instance.session.prefix,
+ });
+ },
+});
+})();