[IMP] only display last 3 log items, add link to display everything
[odoo/odoo.git] / addons / web / static / src / js / view_list_editable.js
1 /**
2  * handles editability case for lists, because it depends on form and forms already depends on lists it had to be split out
3  * @namespace
4  */
5 openerp.web.list_editable = function (openerp) {
6     var KEY_RETURN = 13,
7         KEY_ESCAPE = 27;
8
9     // editability status of list rows
10     openerp.web.ListView.prototype.defaults.editable = null;
11
12     // TODO: not sure second @lends on existing item is correct, to check
13     openerp.web.ListView.include(/** @lends openerp.web.ListView# */{
14         init: function () {
15             var self = this;
16             this._super.apply(this, arguments);
17             $(this.groups).bind({
18                 'edit': function (e, id, dataset) {
19                     self.do_edit(dataset.index, id, dataset);
20                 },
21                 'saved': function () {
22                     if (self.groups.get_selection().length) {
23                         return;
24                     }
25                     self.configure_pager(self.dataset);
26                     self.compute_aggregates();
27                 }
28             })
29         },
30         /**
31          * Handles the activation of a record in editable mode (making a record
32          * editable), called *after* the record has become editable.
33          *
34          * The default behavior is to setup the listview's dataset to match
35          * whatever dataset was provided by the editing List
36          *
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
40          */
41         do_edit: function (index, id, dataset) {
42             _.extend(this.dataset, dataset);
43         },
44         /**
45          * Sets editability status for the list, based on defaults, view
46          * architecture and the provided flag, if any.
47          *
48          * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom".
49          */
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 = (
56                     (force && "bottom")
57                     || this.defaults.editable);
58         },
59         /**
60          * Replace do_search to handle editability process
61          */
62         do_search: function(domain, context, group_by) {
63             this.set_editable(context['set_editable']);
64             this._super.apply(this, arguments);
65         },
66         /**
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)
69          */
70         do_add_record: function () {
71             if (this.options.editable) {
72                 this.groups.new_record();
73             } else {
74                 this._super();
75             }
76         },
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);
81         },
82         /**
83          * Ensures the editable list is saved (saves any pending edition if
84          * needed, or tries to)
85          *
86          * Returns a deferred to the end of the saving.
87          *
88          * @returns {$.Deferred}
89          */
90         ensure_saved: function () {
91             return this.groups.ensure_saved();
92         }
93     });
94
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();
100         },
101         /**
102          * Ensures descendant editable List instances are all saved if they have
103          * pending editions.
104          *
105          * @returns {$.Deferred}
106          */
107         ensure_saved: function () {
108             return $.when.apply(null,
109                 _.invoke(
110                     _.values(this.children),
111                     'ensure_saved'));
112         }
113     });
114
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);
119             }
120             this.edit_record($(event.currentTarget).data('id'));
121         },
122         /**
123          * Checks if a record is being edited, and if so cancels it
124          */
125         cancel_pending_edition: function () {
126             var self = this, cancelled = $.Deferred();
127             if (!this.edition) {
128                 cancelled.resolve();
129                 return cancelled.promise();
130             }
131
132             if (this.edition_id != null) {
133                 this.reload_record(self.records.get(this.edition_id)).then(function () {
134                     cancelled.resolve();
135                 });
136             } else {
137                 cancelled.resolve();
138             }
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;
145                 delete self.edition;
146             });
147             this.pad_table_to(5);
148             return cancelled.promise();
149         },
150         /**
151          * Adapts this list's view description to be suitable to the inner form
152          * view of a row being edited.
153          *
154          * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows.
155          */
156         get_form_fields_view: function () {
157             // deep copy of view
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;
163                 }
164             });
165             view.arch.attrs.col = 2 * view.arch.children.length;
166             return view;
167         },
168         render_row_as_form: function (row) {
169             var self = this;
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();}
177                     })
178                     .delegate('button.oe-edit-row-save', 'click', function () {
179                         self.save_row();
180                     })
181                     .delegate('button.oe-edit-row-cancel', 'click', function () {
182                         self.cancel_edition();
183                     })
184                     .delegate('button', 'keyup', function (e) {
185                         e.stopImmediatePropagation();
186                     })
187                     .keyup(function (e) {
188                         switch (e.which) {
189                             case KEY_RETURN:
190                                 self.save_row(true);
191                                 break;
192                             case KEY_ESCAPE:
193                                 self.cancel_edition();
194                                 break;
195                             default:
196                                 return;
197                         }
198                     });
199                 if (row) {
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'));
207                         } else {
208                             $new_row.insertAfter(
209                                 self.$current.children('[data-id]:last'));
210                         }
211                     } else {
212                         $new_row.prependTo(self.$current);
213                     }
214                     if ($last_child.is(':not([data-id])')) {
215                         $last_child.remove();
216                     }
217                 }
218                 self.edition = true;
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,
223                     $element: $new_row
224                 });
225                 // HA HA
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
229                     $new_row.find('td')
230                           .addClass('oe-field-cell')
231                           .removeAttr('width')
232                       .end()
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) {
237                         if (column.meta) {
238                             $new_row.prepend('<td>');
239                         }
240                     });
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'});
246                     }
247                     if (!self.options.deletable) {
248                         self.view.pad_columns(
249                             1, {except: $new_row});
250                     }
251
252                     self.edition_form.do_show();
253                 });
254             });
255         },
256         handle_onwrite: function (source_record_id) {
257             var self = this;
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);
263                     if (!record) {
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);
270                     }
271                     self.reload_record(record);
272                 });
273             });
274         },
275         /**
276          * Saves the current row, and triggers the edition of its following
277          * sibling if asked.
278          *
279          * @param {Boolean} [edit_next=false] should the next row become editable
280          * @returns {$.Deferred}
281          */
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;
290                 }
291                 var edited_record = self.records.get(self.edition_id),
292                     next_record = self.records.at(
293                             self.records.indexOf(edited_record) + 1);
294
295                 $.when(
296                     self.handle_onwrite(self.edition_id),
297                     self.cancel_pending_edition().then(function () {
298                         $(self).trigger('saved', [self.dataset]);
299                         if (!edit_next) {
300                             return;
301                         }
302                         if (result.created) {
303                             self.new_record();
304                             return;
305                         }
306                         var next_record_id;
307                         if (next_record) {
308                             next_record_id = next_record.get('id');
309                             self.dataset.index = _(self.dataset.ids)
310                                     .indexOf(next_record_id);
311                         } else {
312                             self.dataset.index = 0;
313                             next_record_id = self.records.at(0).get('id');
314                         }
315                         self.edit_record(next_record_id);
316                     })).then(function () {
317                         done.resolve();
318                     });
319             }, this.options.editable === 'top').fail(function () {
320                 done.reject();
321             });
322             return done.promise();
323         },
324         /**
325          * If the current list is being edited, ensures it's saved
326          */
327         ensure_saved: function () {
328             if (this.edition) {
329                 return this.save_row();
330             }
331             //noinspection JSPotentiallyInvalidConstructorUsage
332             return $.Deferred().resolve().promise();
333         },
334         /**
335          * Cancels the edition of the row for the current dataset index
336          */
337         cancel_edition: function () {
338             this.cancel_pending_edition();
339         },
340         /**
341          * Edits record currently selected via dataset
342          */
343         edit_record: function (record_id) {
344             this.render_row_as_form(
345                 this.$current.find('[data-id=' + record_id + ']'));
346             $(this).trigger(
347                 'edit',
348                 [record_id, this.dataset]);
349         },
350         new_record: function () {
351             this.dataset.index = null;
352             this.render_row_as_form();
353         }
354     });
355     if (!openerp.web.list) {
356         openerp.web.list = {};
357     }
358     if (!openerp.web.list.form) {
359         openerp.web.list.form = {};
360     }
361     openerp.web.list.form.WidgetFrame = openerp.web.form.WidgetFrame.extend({
362         template: 'ListView.row.frame'
363     });
364     var form_widgets = openerp.web.form.widgets;
365     openerp.web.list.form.widgets = form_widgets.clone({
366         'frame': 'openerp.web.list.form.WidgetFrame'
367     });
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;
377
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;
384                     this._super();
385                     this.invisible = old_invisible;
386                 } else if (this.invisible) {
387                     this.$element.children().css('visibility', 'hidden');
388                 } else {
389                     this._super();
390                 }
391             }
392         });
393         list_form_widgets.add(key, new_path);
394     });
395     
396     openerp.web.ListEditableFormView = openerp.web.FormView.extend({
397         init_view: function() {},
398         _render_and_insert: function () {
399             return this.start();
400         }
401     });
402 };