2 * handles editability case for lists, because it depends on form and forms already depends on lists it had to be split out
5 openerp.web.list_editable = function (openerp) {
9 // editability status of list rows
10 openerp.web.ListView.prototype.defaults.editable = null;
12 // TODO: not sure second @lends on existing item is correct, to check
13 openerp.web.ListView.include(/** @lends openerp.web.ListView# */{
16 this._super.apply(this, arguments);
18 'edit': function (e, id, dataset) {
19 self.do_edit(dataset.index, id, dataset);
21 'saved': function () {
22 if (self.groups.get_selection().length) {
25 self.configure_pager(self.dataset);
26 self.compute_aggregates();
31 * Handles the activation of a record in editable mode (making a record
32 * editable), called *after* the record has become editable.
34 * The default behavior is to setup the listview's dataset to match
35 * whatever dataset was provided by the editing List
37 * @param {Number} index index of the record in the dataset
38 * @param {Object} id identifier of the record being edited
39 * @param {openerp.web.DataSet} dataset dataset in which the record is available
41 do_edit: function (index, id, dataset) {
42 _.extend(this.dataset, dataset);
45 * Sets editability status for the list, based on defaults, view
46 * architecture and the provided flag, if any.
48 * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom".
50 set_editable: function (force) {
51 // If ``force``, set editability to bottom
52 // otherwise rely on view default
53 // view' @editable is handled separately as we have not yet
54 // fetched and processed the view at this point.
55 this.options.editable = (
57 || this.defaults.editable);
60 * Replace do_search to handle editability process
62 do_search: function(domain, context, group_by) {
63 this.set_editable(context['set_editable']);
64 this._super.apply(this, arguments);
67 * Replace do_add_record to handle editability (and adding new record
68 * as an editable row at the top or bottom of the list)
70 do_add_record: function () {
71 if (this.options.editable) {
72 this.groups.new_record();
77 on_loaded: function (data, grouped) {
78 // tree/@editable takes priority on everything else if present.
79 this.options.editable = data.arch.attrs.editable || this.options.editable;
80 return this._super(data, grouped);
83 * Ensures the editable list is saved (saves any pending edition if
84 * needed, or tries to)
86 * Returns a deferred to the end of the saving.
88 * @returns {$.Deferred}
90 ensure_saved: function () {
91 return this.groups.ensure_saved();
95 openerp.web.ListView.Groups.include(/** @lends openerp.web.ListView.Groups# */{
96 passtrough_events: openerp.web.ListView.Groups.prototype.passtrough_events + " edit saved",
97 new_record: function () {
98 // TODO: handle multiple children
99 this.children[null].new_record();
102 * Ensures descendant editable List instances are all saved if they have
105 * @returns {$.Deferred}
107 ensure_saved: function () {
108 return $.when.apply(null,
110 _.values(this.children),
115 openerp.web.ListView.List.include(/** @lends openerp.web.ListView.List# */{
116 row_clicked: function (event) {
117 if (!this.options.editable) {
118 return this._super(event);
120 this.edit_record($(event.currentTarget).data('id'));
123 * Checks if a record is being edited, and if so cancels it
125 cancel_pending_edition: function () {
126 var self = this, cancelled = $.Deferred();
129 return cancelled.promise();
132 if (this.edition_id != null) {
133 this.reload_record(self.records.get(this.edition_id)).then(function () {
139 cancelled.then(function () {
140 self.view.unpad_columns();
141 self.edition_form.stop();
142 self.edition_form.$element.remove();
143 delete self.edition_form;
144 delete self.edition_id;
147 this.pad_table_to(5);
148 return cancelled.promise();
151 * Adapts this list's view description to be suitable to the inner form
152 * view of a row being edited.
154 * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows.
156 get_form_fields_view: function () {
158 var view = $.extend(true, {}, this.group.view.fields_view);
159 _(view.arch.children).each(function (widget) {
160 widget.attrs.nolabel = true;
161 if (widget.tag === 'button') {
162 delete widget.attrs.string;
165 view.arch.attrs.col = 2 * view.arch.children.length;
168 render_row_as_form: function (row) {
170 this.cancel_pending_edition().then(function () {
171 var record_id = $(row).data('id');
172 var $new_row = $('<tr>', {
173 id: _.uniqueId('oe-editable-row-'),
174 'data-id': record_id,
175 'class': row ? $(row).attr('class') : '' + ' oe_forms',
176 click: function (e) {e.stopPropagation();}
178 .delegate('button.oe-edit-row-save', 'click', function () {
181 .delegate('button.oe-edit-row-cancel', 'click', function () {
182 self.cancel_edition();
184 .delegate('button', 'keyup', function (e) {
185 e.stopImmediatePropagation();
187 .keyup(function (e) {
193 self.cancel_edition();
200 $new_row.replaceAll(row);
201 } else if (self.options.editable) {
202 var $last_child = self.$current.children('tr:last');
203 if (self.records.length) {
204 if (self.options.editable === 'top') {
205 $new_row.insertBefore(
206 self.$current.children('[data-id]:first'));
208 $new_row.insertAfter(
209 self.$current.children('[data-id]:last'));
212 $new_row.prependTo(self.$current);
214 if ($last_child.is(':not([data-id])')) {
215 $last_child.remove();
219 self.edition_id = record_id;
220 self.edition_form = _.extend(new openerp.web.ListEditableFormView(self.view, self.dataset, false), {
221 form_template: 'ListView.row.form',
222 registry: openerp.web.list.form.widgets,
226 self.edition_form.appendTo();
227 $.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () {
228 // put in $.when just in case FormView.on_loaded becomes asynchronous
230 .addClass('oe-field-cell')
233 .find('td:first').removeClass('oe-field-cell').end()
234 .find('td:last').removeClass('oe-field-cell').end();
235 // pad in case of groupby
236 _(self.columns).each(function (column) {
238 $new_row.prepend('<td>');
241 // Add columns for the cancel and save buttons, if
242 // there are none in the list
243 if (!self.options.selectable) {
244 self.view.pad_columns(
245 1, {except: $new_row, position: 'before'});
247 if (!self.options.deletable) {
248 self.view.pad_columns(
249 1, {except: $new_row});
252 self.edition_form.do_show();
256 handle_onwrite: function (source_record_id) {
258 var on_write_callback = self.view.fields_view.arch.attrs.on_write;
259 if (!on_write_callback) { return; }
260 this.dataset.call(on_write_callback, [source_record_id], function (ids) {
261 _(ids).each(function (id) {
262 var record = self.records.get(id);
264 // insert after the source record
265 var index = self.records.indexOf(
266 self.records.get(source_record_id)) + 1;
267 record = new openerp.web.list.Record({id: id});
268 self.records.add(record, {at: index});
269 self.dataset.ids.splice(index, 0, id);
271 self.reload_record(record);
276 * Saves the current row, and triggers the edition of its following
279 * @param {Boolean} [edit_next=false] should the next row become editable
280 * @returns {$.Deferred}
282 save_row: function (edit_next) {
283 //noinspection JSPotentiallyInvalidConstructorUsage
284 var self = this, done = $.Deferred();
285 this.edition_form.do_save(function (result) {
286 if (result.created && !self.edition_id) {
287 self.records.add({id: result.result},
288 {at: self.options.editable === 'top' ? 0 : null});
289 self.edition_id = result.result;
291 var edited_record = self.records.get(self.edition_id),
292 next_record = self.records.at(
293 self.records.indexOf(edited_record) + 1);
296 self.handle_onwrite(self.edition_id),
297 self.cancel_pending_edition().then(function () {
298 $(self).trigger('saved', [self.dataset]);
302 if (result.created) {
308 next_record_id = next_record.get('id');
309 self.dataset.index = _(self.dataset.ids)
310 .indexOf(next_record_id);
312 self.dataset.index = 0;
313 next_record_id = self.records.at(0).get('id');
315 self.edit_record(next_record_id);
316 })).then(function () {
319 }, this.options.editable === 'top').fail(function () {
322 return done.promise();
325 * If the current list is being edited, ensures it's saved
327 ensure_saved: function () {
329 return this.save_row();
331 //noinspection JSPotentiallyInvalidConstructorUsage
332 return $.Deferred().resolve().promise();
335 * Cancels the edition of the row for the current dataset index
337 cancel_edition: function () {
338 this.cancel_pending_edition();
341 * Edits record currently selected via dataset
343 edit_record: function (record_id) {
344 this.render_row_as_form(
345 this.$current.find('[data-id=' + record_id + ']'));
348 [record_id, this.dataset]);
350 new_record: function () {
351 this.dataset.index = null;
352 this.render_row_as_form();
355 if (!openerp.web.list) {
356 openerp.web.list = {};
358 if (!openerp.web.list.form) {
359 openerp.web.list.form = {};
361 openerp.web.list.form.WidgetFrame = openerp.web.form.WidgetFrame.extend({
362 template: 'ListView.row.frame'
364 var form_widgets = openerp.web.form.widgets;
365 openerp.web.list.form.widgets = form_widgets.clone({
366 'frame': 'openerp.web.list.form.WidgetFrame'
368 // All form widgets inherit a problematic behavior from
369 // openerp.web.form.WidgetFrame: the cell itself is removed when invisible
370 // whether it's @invisible or @attrs[invisible]. In list view, only the
371 // former should completely remove the cell. We need to override update_dom
372 // on all widgets since we can't just hit on widget itself (I think)
373 var list_form_widgets = openerp.web.list.form.widgets;
374 _(list_form_widgets.map).each(function (widget_path, key) {
375 if (key === 'frame') { return; }
376 var new_path = 'openerp.web.list.form.' + key;
378 openerp.web.list.form[key] = (form_widgets.get_object(key)).extend({
379 update_dom: function () {
380 this.$element.children().css('visibility', '');
381 if (this.modifiers.tree_invisible) {
382 var old_invisible = this.invisible;
383 this.invisible = true;
385 this.invisible = old_invisible;
386 } else if (this.invisible) {
387 this.$element.children().css('visibility', 'hidden');
393 list_form_widgets.add(key, new_path);
396 openerp.web.ListEditableFormView = openerp.web.FormView.extend({
397 init_view: function() {},
398 _render_and_insert: function () {