2 * @namespace handles editability case for lists, because it depends on form and forms already depends on lists it had to be split out
4 openerp.base.list.editable = function (openerp) {
8 // editability status of list rows
9 openerp.base.ListView.prototype.defaults.editable = null;
11 var old_init = openerp.base.ListView.prototype.init,
12 old_actual_search = openerp.base.ListView.prototype.do_actual_search,
13 old_add_record = openerp.base.ListView.prototype.do_add_record,
14 old_on_loaded = openerp.base.ListView.prototype.on_loaded;
15 // TODO: not sure second @lends on existing item is correct, to check
16 _.extend(openerp.base.ListView.prototype, /** @lends openerp.base.ListView# */{
19 old_init.apply(this, arguments);
21 'edit': function (e, id, dataset) {
22 self.do_edit(dataset.index, id, dataset);
24 'saved': function () {
25 if (self.groups.get_selection().length) {
28 self.compute_aggregates();
33 * Handles the activation of a record in editable mode (making a record
34 * editable), called *after* the record has become editable.
36 * The default behavior is to setup the listview's dataset to match
37 * whatever dataset was provided by the editing List
39 * @param {Number} index index of the record in the dataset
40 * @param {Object} id identifier of the record being edited
41 * @param {openerp.base.DataSet} dataset dataset in which the record is available
43 do_edit: function (index, id, dataset) {
44 _.extend(this.dataset, dataset);
47 * Sets editability status for the list, based on defaults, view
48 * architecture and the provided flag, if any.
50 * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom".
52 set_editable: function (force) {
53 // If ``force``, set editability to bottom
54 // otherwise rely on view default
55 // view' @editable is handled separately as we have not yet
56 // fetched and processed the view at this point.
57 this.options.editable = (
59 || this.defaults.editable);
62 * Replace do_actual_search to handle editability process
64 do_actual_search: function (results) {
65 this.set_editable(results.context['set_editable']);
66 old_actual_search.call(this, results);
69 * Replace do_add_record to handle editability (and adding new record
70 * as an editable row at the top or bottom of the list)
72 do_add_record: function () {
73 if (this.options.editable) {
74 this.groups.new_record();
76 old_add_record.call(this);
79 on_loaded: function (data, grouped) {
80 // tree/@editable takes priority on everything else if present.
81 this.options.editable = data.fields_view.arch.attrs.editable || this.options.editable;
82 return old_on_loaded.call(this, data, grouped);
86 _.extend(openerp.base.ListView.Groups.prototype, /** @lends openerp.base.ListView.Groups# */{
87 passtrough_events: openerp.base.ListView.Groups.prototype.passtrough_events + " edit saved",
88 new_record: function () {
89 // TODO: handle multiple children
90 this.children[null].new_record();
94 var old_list_row_clicked = openerp.base.ListView.List.prototype.row_clicked;
95 _.extend(openerp.base.ListView.List.prototype, /** @lends openerp.base.ListView.List */{
96 row_clicked: function (event) {
97 if (!this.options.editable) {
98 return old_list_row_clicked.call(this, event);
103 * Checks if a record is being edited, and if so cancels it
105 cancel_pending_edition: function () {
106 var self = this, cancelled = $.Deferred();
109 return cancelled.promise();
112 if (this.edition_index !== null) {
113 this.reload_record(this.edition_index, true).then(function () {
119 cancelled.then(function () {
120 self.edition_form.stop();
121 self.edition_form.$element.remove();
122 delete self.edition_form;
123 delete self.edition_index;
126 return cancelled.promise();
129 * Adapts this list's view description to be suitable to the inner form view of a row being edited.
131 * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows.
133 get_form_fields_view: function () {
135 var view = $.extend(true, {}, this.group.view.fields_view);
136 _(view.arch.children).each(function (widget) {
137 widget.attrs.nolabel = true;
138 if (widget.tag === 'button') {
139 delete widget.attrs.string;
142 view.arch.attrs.col = 2 * view.arch.children.length;
145 render_row_as_form: function (row) {
147 this.cancel_pending_edition().then(function () {
148 var $new_row = $('<tr>', {
149 id: _.uniqueId('oe-editable-row-'),
150 'class': $(row).attr('class') + ' oe_forms',
151 click: function (e) {e.stopPropagation();}
153 .delegate('button.oe-edit-row-save', 'click', function () {
156 .delegate('button.oe-edit-row-cancel', 'click', function () {
157 self.cancel_edition();
159 .delegate('button', 'keyup', function (e) {
160 e.stopImmediatePropagation();
162 .keyup(function (e) {
168 self.cancel_edition();
175 $new_row.replaceAll(row);
176 } else if (self.options.editable === 'top') {
177 self.$current.prepend($new_row);
178 } else if (self.options.editable) {
179 self.$current.append($new_row);
182 self.edition_index = self.dataset.index;
183 self.edition_form = _.extend(new openerp.base.FormView(
184 self, $new_row.attr('id'), self.dataset, false), {
185 template: 'ListView.row.form',
186 registry: openerp.base.list.form.widgets
188 $.when(self.edition_form.on_loaded({fields_view: self.get_form_fields_view()})).then(function () {
189 // put in $.when just in case FormView.on_loaded becomes asynchronous
191 .addClass('oe-field-cell')
194 .find('td:first').removeClass('oe-field-cell').end()
195 .find('td:last').removeClass('oe-field-cell').end();
196 // pad in case of groupby
197 _(self.columns).each(function (column) {
199 $new_row.prepend('<td>');
203 self.edition_form.do_show();
208 * Saves the current row, and triggers the edition of its following
211 * @param {Boolean} [edit_next=false] should the next row become editable
213 save_row: function (edit_next) {
215 this.edition_form.do_save(function (result) {
216 if (result.created && !self.edition_index) {
217 self.edition_index = self.dataset.index;
219 self.cancel_pending_edition().then(function () {
220 $(self).trigger('saved', [self.dataset]);
224 if (result.created) {
231 }, this.options.editable === 'top');
234 * Cancels the edition of the row for the current dataset index
236 cancel_edition: function () {
237 this.cancel_pending_edition();
240 * Edits record currently selected via dataset
242 edit_record: function () {
243 this.render_row_as_form(
244 this.$current.children(
245 _.sprintf('[data-index=%d]',
246 this.dataset.index)));
249 [this.rows[this.dataset.index].data.id.value, this.dataset]);
251 new_record: function () {
252 this.dataset.index = null;
253 this.render_row_as_form();
256 if (!openerp.base.list) {
257 openerp.base.list = {};
259 if (!openerp.base.list.form) {
260 openerp.base.list.form = {};
262 openerp.base.list.form.WidgetFrame = openerp.base.form.WidgetFrame.extend({
263 template: 'ListView.row.frame'
265 var form_widgets = openerp.base.form.widgets;
266 openerp.base.list.form.widgets = form_widgets.clone({
267 'frame': 'openerp.base.list.form.WidgetFrame'
269 // All form widgets inherit a problematic behavior from
270 // openerp.base.form.WidgetFrame: the cell itself is removed when invisible
271 // whether it's @invisible or @attrs[invisible]. In list view, only the
272 // former should completely remove the cell. We need to override update_dom
273 // on all widgets since we can't just hit on widget itself (I think)
274 var list_form_widgets = openerp.base.list.form.widgets;
275 _(list_form_widgets.map).each(function (widget_path, key) {
276 if (key === 'frame') { return; }
277 var new_path = 'openerp.base.list.form.' + key;
279 openerp.base.list.form[key] = (form_widgets.get_object(key)).extend({
280 update_dom: function () {
281 this.$element.children().css('visibility', '');
282 if (this.modifiers.tree_invisible) {
283 var old_invisible = this.invisible;
284 this.invisible = !!this.modifiers.tree_invisible;
286 this.invisible = old_invisible;
287 } else if (this.invisible) {
288 this.$element.children().css('visibility', 'hidden');
292 list_form_widgets.add(key, new_path);