diff --git a/addons/base/static/lib/qweb/qweb2.js b/addons/base/static/lib/qweb/qweb2.js
index 89a77bf1b85..8987f1dc40e 100644
--- a/addons/base/static/lib/qweb/qweb2.js
+++ b/addons/base/static/lib/qweb/qweb2.js
@@ -169,7 +169,7 @@ var QWeb2 = {
for (var i = 0; i < enu; i++) {
_enu.push(i);
}
- this.foreach(context, enu, as, old_dict, callback);
+ this.foreach(context, _enu, as, old_dict, callback);
} else {
var index = 0;
for (var k in enu) {
diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js
index 4cebdcc2893..954e2024965 100644
--- a/addons/base/static/src/js/chrome.js
+++ b/addons/base/static/src/js/chrome.js
@@ -566,6 +566,11 @@ openerp.base.Login = openerp.base.Widget.extend({
openerp.base.Header = openerp.base.Widget.extend({
init: function(parent, element_id) {
this._super(parent, element_id);
+ if (jQuery.deparam(jQuery.param.querystring()).debug !== undefined) {
+ this.qs = '?debug'
+ } else {
+ this.qs = ''
+ }
},
start: function() {
return this.do_update();
diff --git a/addons/base/static/src/js/core.js b/addons/base/static/src/js/core.js
index 441230b3bac..fca993ffef9 100644
--- a/addons/base/static/src/js/core.js
+++ b/addons/base/static/src/js/core.js
@@ -10,6 +10,9 @@ openerp.base.core = function(openerp) {
var initializing = false,
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
+ /**
+ * @class
+ */
openerp.base.Class = function(){};
// Create a new Class that inherits from this class
diff --git a/addons/base/static/src/js/formats.js b/addons/base/static/src/js/formats.js
index 63ac2b1d82a..c141aad908c 100644
--- a/addons/base/static/src/js/formats.js
+++ b/addons/base/static/src/js/formats.js
@@ -149,6 +149,9 @@ openerp.base.format_cell = function (row_data, column, value_if_empty) {
].join('')
}
+ if (!row_data[column.id]) {
+ return value_if_empty === undefined ? '' : value_if_empty;
+ }
return openerp.base.format_value(
row_data[column.id].value, column, value_if_empty);
}
diff --git a/addons/base/static/src/js/list-editable.js b/addons/base/static/src/js/list-editable.js
index db10597aa8b..8810d902346 100644
--- a/addons/base/static/src/js/list-editable.js
+++ b/addons/base/static/src/js/list-editable.js
@@ -105,7 +105,7 @@ openerp.base.list_editable = function (openerp) {
}
if (this.edition_index !== null) {
- this.reload_record(this.edition_index, true).then(function () {
+ this.reload_record(this.edition_index).then(function () {
cancelled.resolve();
});
} else {
@@ -142,6 +142,8 @@ openerp.base.list_editable = function (openerp) {
this.cancel_pending_edition().then(function () {
var $new_row = $('
', {
id: _.uniqueId('oe-editable-row-'),
+ 'data-id': $(row).data('id'),
+ 'data-index': $(row).data('index'),
'class': $(row).attr('class') + ' oe_forms',
click: function (e) {e.stopPropagation();}
})
@@ -209,6 +211,8 @@ openerp.base.list_editable = function (openerp) {
var self = this;
this.edition_form.do_save(function (result) {
if (result.created && !self.edition_index) {
+ self.records.add({id: result.result},
+ {at: self.options.editable === 'top' ? 0 : null});
self.edition_index = self.dataset.index;
}
self.cancel_pending_edition().then(function () {
@@ -236,12 +240,10 @@ openerp.base.list_editable = function (openerp) {
*/
edit_record: function () {
this.render_row_as_form(
- this.$current.children(
- _.sprintf('[data-index=%d]',
- this.dataset.index)));
+ this.$current.children(':eq(' + this.dataset.index + ')'));
$(this).trigger(
'edit',
- [this.rows[this.dataset.index].data.id.value, this.dataset]);
+ [this.records.at(this.dataset.index).get('id'), this.dataset]);
},
new_record: function () {
this.dataset.index = null;
diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js
index d715c3600be..95db36f82de 100644
--- a/addons/base/static/src/js/list.js
+++ b/addons/base/static/src/js/list.js
@@ -42,6 +42,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* @borrows openerp.base.ActionExecutor#execute_action as #execute_action
*/
init: function(parent, element_id, dataset, view_id, options) {
+ var self = this;
this._super(parent, element_id);
this.set_default_options(_.extend({}, this.defaults, options || {}));
this.dataset = dataset;
@@ -50,6 +51,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.columns = [];
+ this.records = new Collection();
+
this.set_groups(new openerp.base.ListView.Groups(this));
if (this.dataset instanceof openerp.base.DataSetStatic) {
@@ -57,6 +60,13 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
}
this.page = 0;
+ this.records.bind('change', function (event, record, key) {
+ if (!_(self.aggregate_columns).chain()
+ .pluck('name').contains(key).value()) {
+ return;
+ }
+ self.compute_aggregates();
+ });
},
/**
* Retrieves the view's number of records per page (|| section)
@@ -336,9 +346,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
this.sidebar.$element.show();
}
if (this.hidden) {
- this.$element.find('.oe-listview-content').append(
- this.groups.apoptosis().render());
- this.hidden = false;
+ this.reload_content();
}
},
do_hide: function () {
@@ -374,8 +382,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* re-renders the content of the list view
*/
reload_content: function () {
+ this.records.reset();
this.$element.find('.oe-listview-content').append(
- this.groups.apoptosis().render(
+ this.groups.render(
$.proxy(this, 'compute_aggregates')));
},
/**
@@ -403,8 +412,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
do_actual_search: function (results) {
this.groups.datagroup = new openerp.base.DataGroup(
this, this.model,
- this.dataset.get_domain(results.domain),
- this.dataset.get_context(results.context),
+ results.domain,
+ results.context,
results.group_by);
this.groups.datagroup.sort = this.dataset._sort;
@@ -426,7 +435,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
}
var self = this;
return $.when(this.dataset.unlink(ids)).then(function () {
- self.groups.drop_records(ids);
+ _(ids).each(function (id) {
+ self.records.remove(self.records.get(id));
+ });
self.compute_aggregates();
});
},
@@ -456,16 +467,11 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi
* @param {Function} callback should be called after the action is executed, if non-null
*/
do_button_action: function (name, id, callback) {
- var self = this,
- action = _.detect(this.columns, function (field) {
+ var action = _.detect(this.columns, function (field) {
return field.name === name;
});
if (!action) { return; }
- this.execute_action(action, this.dataset, id, function () {
- $.when(callback.apply(this, arguments).then(function () {
- self.compute_aggregates();
- }));
- });
+ this.execute_action(action, this.dataset, id, callback);
},
/**
* Handles the activation of a record (clicking on it)
@@ -622,7 +628,31 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
this.options = opts.options;
this.columns = opts.columns;
this.dataset = opts.dataset;
- this.rows = opts.rows;
+ this.records = opts.records;
+
+ this.record_callbacks = {
+ 'remove': function (event, record) {
+ var $row = self.$current.find(
+ '[data-id=' + record.get('id') + ']');
+ var index = $row.data('index');
+ $row.remove();
+ self.refresh_zebra(index);
+ },
+ 'reset': $.proxy(this, 'on_records_reset'),
+ 'change': function (event, record) {
+ var $row = self.$current.find('[data-id=' + record.get('id') + ']');
+ $row.replaceWith(self.render_record($row.data('index')));
+ },
+ 'add': function (ev, records, record, index) {
+ $('
').attr({
+ 'data-id': record.get('id')
+ }).insertAfter(self.$current.children(':eq(' + index + ')'));
+ self.refresh_zebra(index, 1);
+ }
+ };
+ _(this.record_callbacks).each(function (callback, event) {
+ this.records.bind(event, callback);
+ }, this);
this.$_element = $('
')
.appendTo(document.body)
@@ -646,7 +676,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
index = self.row_position($row);
$(self).trigger('action', [field, record_id, function () {
- return self.reload_record(index, true);
+ return self.reload_record(index);
}]);
})
.delegate('tr', 'click', function (e) {
@@ -658,7 +688,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
row_clicked: function () {
$(this).trigger(
'row_link',
- [this.rows[this.dataset.index].data.id.value,
+ [this.records.at(this.dataset.index).get('id'),
this.dataset]);
},
render: function () {
@@ -678,16 +708,13 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
if (!this.options.selectable) {
return [];
}
- var rows = this.rows;
+ var records = this.records;
var result = {ids: [], records: []};
this.$current.find('th.oe-record-selector input:checked')
.closest('tr').each(function () {
- var record = {};
- _(rows[$(this).data('index')].data).each(function (obj, key) {
- record[key] = obj.value;
- });
- result.ids.push(record.id);
- result.records.push(record);
+ var record = records.get($(this).data('id'));
+ result.ids.push(record.get('id'));
+ result.records.push(record.attributes);
});
return result;
},
@@ -708,54 +735,25 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
* @returns {Number|String} the identifier of the row's object
*/
row_id: function (row) {
- return this.rows[this.row_position(row)].data.id.value;
+ return $(row).data('id');
},
/**
- * Death signal, cleans up list
+ * Death signal, cleans up list display
*/
- apoptosis: function () {
+ on_records_reset: function () {
+ _(this.record_callbacks).each(function (callback, event) {
+ this.records.unbind(event, callback);
+ }, this);
if (!this.$current) { return; }
this.$current.remove();
this.$current = null;
this.$_element.remove();
},
get_records: function () {
- return _(this.rows).map(function (row) {
- var record = {};
- _(row.data).each(function (obj, key) {
- record[key] = obj.value;
- });
- return {count: 1, values: record};
+ return this.records.map(function (record) {
+ return {count: 1, values: record.attributes};
});
},
- /**
- * Transforms a record from what is returned by a dataset read (a simple
- * mapping of ``$fieldname: $value``) to the format expected by list rows
- * and form views:
- *
- * data: {
- * $fieldname: {
- * value: $value
- * }
- * }
- *
- * This format allows for the insertion of a bunch of metadata (names,
- * colors, etc...)
- *
- * @param {Object} record original record, in dataset format
- * @returns {Object} record displayable in a form or list view
- */
- transform_record: function (record) {
- // TODO: colors handling
- var form_data = {},
- form_record = {data: form_data};
-
- _(record).each(function (value, key) {
- form_data[key] = {value: value};
- });
-
- return form_record;
- },
/**
* Reloads the record at index ``row_index`` in the list's rows.
*
@@ -765,26 +763,21 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
* @param {Number} record_index index of the record to reload
* @param {Boolean} fetch fetches the record from remote before reloading it
*/
- reload_record: function (record_index, fetch) {
- var self = this;
- var read_p = null;
- if (fetch) {
- // save index to restore it later, if already set
- var old_index = this.dataset.index;
- this.dataset.index = record_index;
- read_p = this.dataset.read_index(
- _.pluck(_(this.columns).filter(function (r) {return r.tag === 'field';}), 'name'),
- function (record) {
- var form_record = self.transform_record(record);
- self.rows.splice(record_index, 1, form_record);
- self.dataset.index = old_index;
- }
- )
- }
+ reload_record: function (record_index) {
+ var r = this.records.at(record_index);
- return $.when(read_p).then(function () {
- self.$current.children().eq(record_index)
- .replaceWith(self.render_record(record_index)); });
+ return this.dataset.read_ids(
+ [r.get('id')],
+ _.pluck(_(this.columns).filter(function (r) {
+ return r.tag === 'field';
+ }), 'name'),
+ function (record) {
+ _(record[0]).each(function (value, key) {
+ r.set(key, value, {silent: true});
+ });
+ r.trigger('change', r);
+ }
+ );
},
/**
* Renders a list record to HTML
@@ -796,31 +789,32 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base.
return QWeb.render('ListView.row', {
columns: this.columns,
options: this.options,
- row: this.rows[record_index],
+ record: this.records.at(record_index),
row_parity: (record_index % 2 === 0) ? 'even' : 'odd',
row_index: record_index,
render_cell: openerp.base.format_cell
});
},
/**
- * Stops displaying the records matching the provided ids.
+ * Fixes @data-index on all table rows, and fixes the even/odd classes
*
- * @param {Array} ids identifiers of the records to remove
+ * @param {Number} [from_index] index from which to resequence
+ * @param {Number} [offset = 0] selection offset for DOM, in case there are rows to ignore in the table
*/
- drop_records: function (ids) {
- var self = this;
- _(this.rows).chain()
- .map(function (record, index) {
- return {index: index, id: record.data.id.value};
- }).filter(function (record) {
- return _(ids).contains(record.id);
- }).reverse()
- .each(function (record) {
- self.$current.find('tr:eq(' + record.index + ')').remove();
- self.rows.splice(record.index, 1);
- })
+ refresh_zebra: function (from_index, offset) {
+ offset = offset || 0;
+ from_index = from_index || 0;
+ var dom_offset = offset + from_index;
+ var sel = dom_offset ? ':gt(' + (dom_offset - 1) + ')' : null;
+ this.$current.children(sel).each(function (i, e) {
+ var index = from_index + i;
+ // reset record-index accelerators on rows and even/odd
+ var even = index%2 === 0;
+ $(e).attr('data-index', index)
+ .toggleClass('even', even)
+ .toggleClass('odd', !even);
+ });
}
- // drag and drop
});
openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.base.ListView.Groups# */{
passtrough_events: 'action deleted row_link',
@@ -830,17 +824,28 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
*
* Provides events similar to those of
* :js:class:`~openerp.base.ListView.List`
+ *
+ * @constructs
+ * @param {openerp.base.ListView} view
+ * @param {Object} [options]
+ * @param {Collection} [options.records]
+ * @param {Object} [options.options]
+ * @param {Array} [options.columns]
*/
- init: function (view) {
+ init: function (view, options) {
+ options = options || {};
this.view = view;
- this.options = view.options;
- this.columns = view.columns;
+ this.records = options.records || view.records;
+ this.options = options.options || view.options;
+ this.columns = options.columns || view.columns;
this.datagroup = null;
this.$row = null;
this.children = {};
this.page = 0;
+
+ this.records.bind('reset', $.proxy(this, 'on_records_reset'));
},
make_fragment: function () {
return document.createDocumentFragment();
@@ -901,7 +906,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
},
close: function () {
this.$row.children().last().empty();
- this.apoptosis();
+ this.records.reset();
},
/**
* Prefixes ``$node`` with floated spaces in order to indent it relative
@@ -922,10 +927,11 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
var placeholder = this.make_fragment();
_(datagroups).each(function (group) {
if (self.children[group.value]) {
- self.children[group.value].apoptosis();
+ self.records.proxy(group.value).reset();
delete self.children[group.value];
}
var child = self.children[group.value] = new openerp.base.ListView.Groups(self.view, {
+ records: self.records.proxy(group.value),
options: self.options,
columns: self.columns
});
@@ -1020,13 +1026,12 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
});
},
render_dataset: function (dataset) {
- var rows = [],
- self = this,
+ var self = this,
list = new openerp.base.ListView.List(this, {
options: this.options,
columns: this.columns,
dataset: dataset,
- rows: rows
+ records: this.records
});
this.bind_child_events(list);
@@ -1053,11 +1058,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
.attr('disabled', page === pages - 1);
}
- var form_records = _(records).map(
- $.proxy(list, 'transform_record'));
-
- rows.splice(0, rows.length);
- rows.push.apply(rows, form_records);
+ self.records.add(records, {silent: true});
list.render();
d.resolve(list);
});
@@ -1077,29 +1078,23 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
var from = ui.item.data('index'),
to = ui.item.prev().data('index') || 0;
if (from === to) { return; }
- list.rows.splice(to, 0, list.rows.splice(from, 1)[0]);
+ var to_move = list.records.at(from);
+ list.records.remove(to_move);
+ list.records.add(to_move, {at: to});
- ui.item.parent().children().each(function (i, e) {
- // reset record-index accelerators on rows and even/odd
- var even = i%2 === 0;
- $(e).data('index', i)
- .toggleClass('even', even)
- .toggleClass('odd', !even);
- });
+ list.refresh_zebra();
// resequencing time!
- var data, index = to,
+ var record, index = to,
// if drag to 1st row (to = 0), start sequencing from 0
// (exclusive lower bound)
- seq = to ? list.rows[to - 1].data.sequence.value : 0;
- while (++seq, list.rows[index]) {
- data = list.rows[index].data;
- data.sequence.value = seq;
+ seq = to ? list.records.at(to - 1).get('sequence') : 0;
+ 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
- dataset.write(data.id.value, {sequence: seq});
- list.reload_record(index++);
+ dataset.write(record.get('id'), {sequence: seq});
+ record.set('sequence', seq);
}
}
});
@@ -1144,13 +1139,9 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
return {ids: ids, records: records};
},
- apoptosis: function () {
- _(this.children).each(function (child) {
- child.apoptosis();
- });
+ on_records_reset: function () {
this.children = {};
$(this.elements).remove();
- return this;
},
get_records: function () {
if (_(this.children).isEmpty()) {
@@ -1163,22 +1154,301 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas
.map(function (child) {
return child.get_records();
}).flatten().value();
- },
- /**
- * Stops displaying the records with the linked ids, assumes these records
- * were deleted from the DB.
- *
- * This is the up-signal from the `deleted` event on groups and lists.
- *
- * @param {Array} ids list of identifier of the records to remove.
- */
- drop_records: function (ids) {
- _.each(this.children, function (child) {
- child.drop_records(ids);
- });
}
});
+
+/**
+ * @class
+ * @extends openerp.base.Class
+ */
+var Events = {
+ /**
+ * @param {String} event event to listen to on the current object, null for all events
+ * @param {Function} handler event handler to bind to the relevant event
+ * @returns this
+ */
+ bind: function (event, handler) {
+ var calls = this['_callbacks'] || (this._callbacks = {});
+
+ if (event in calls) {
+ calls[event].push(handler);
+ } else {
+ calls[event] = [handler];
+ }
+ return this;
+ },
+ /**
+ * @param {String} event event to unbind on the current object
+ * @param {function} [handler] specific event handler to remove (otherwise unbind all handlers for the event)
+ * @returns this
+ */
+ unbind: function (event, handler) {
+ var calls = this._callbacks || {};
+ if (!(event in calls)) { return this; }
+ if (!handler) {
+ delete calls[event];
+ } else {
+ var handlers = calls[event];
+ handlers.splice(
+ _(handlers).indexOf(handler),
+ 1);
+ }
+ return this;
+ },
+ /**
+ * @param {String} event
+ * @returns this
+ */
+ trigger: function (event) {
+ var calls;
+ if (!(calls = this._callbacks)) { return this; }
+ var callbacks = (calls[event] || []).concat(calls[null] || []);
+ for(var i=0, length=callbacks.length; i
-
+