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) {
8 var QWeb = openerp.web.qweb;
10 // editability status of list rows
11 openerp.web.ListView.prototype.defaults.editable = null;
13 // TODO: not sure second @lends on existing item is correct, to check
14 openerp.web.ListView.include(/** @lends openerp.web.ListView# */{
17 this._super.apply(this, arguments);
19 'edit': function (e, id, dataset) {
20 self.do_edit(dataset.index, id, dataset);
22 'saved': function () {
23 if (self.groups.get_selection().length) {
26 self.configure_pager(self.dataset);
27 self.compute_aggregates();
32 * Handles the activation of a record in editable mode (making a record
33 * editable), called *after* the record has become editable.
35 * The default behavior is to setup the listview's dataset to match
36 * whatever dataset was provided by the editing List
38 * @param {Number} index index of the record in the dataset
39 * @param {Object} id identifier of the record being edited
40 * @param {openerp.web.DataSet} dataset dataset in which the record is available
42 do_edit: function (index, id, dataset) {
43 _.extend(this.dataset, dataset);
46 * Sets editability status for the list, based on defaults, view
47 * architecture and the provided flag, if any.
49 * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom".
51 set_editable: function (force) {
52 // If ``force``, set editability to bottom
53 // otherwise rely on view default
54 // view' @editable is handled separately as we have not yet
55 // fetched and processed the view at this point.
56 this.options.editable = (
58 || this.defaults.editable);
61 * Replace do_search to handle editability process
63 do_search: function(domain, context, group_by) {
64 this.set_editable(context['set_editable']);
65 this._super.apply(this, arguments);
68 * Replace do_add_record to handle editability (and adding new record
69 * as an editable row at the top or bottom of the list)
71 do_add_record: function () {
72 if (this.options.editable) {
73 this.groups.new_record();
78 on_loaded: function (data, grouped) {
79 // tree/@editable takes priority on everything else if present.
80 this.options.editable = data.arch.attrs.editable || this.options.editable;
81 return this._super(data, grouped);
84 * Ensures the editable list is saved (saves any pending edition if
85 * needed, or tries to)
87 * Returns a deferred to the end of the saving.
89 * @returns {$.Deferred}
91 ensure_saved: function () {
92 return this.groups.ensure_saved();
96 openerp.web.ListView.Groups.include(/** @lends openerp.web.ListView.Groups# */{
97 passtrough_events: openerp.web.ListView.Groups.prototype.passtrough_events + " edit saved",
98 new_record: function () {
99 // TODO: handle multiple children
100 this.children[null].new_record();
103 * Ensures descendant editable List instances are all saved if they have
106 * @returns {$.Deferred}
108 ensure_saved: function () {
109 return $.when.apply(null,
111 _.values(this.children),
116 openerp.web.ListView.List.include(/** @lends openerp.web.ListView.List# */{
117 row_clicked: function (event) {
118 if (!this.options.editable) {
119 return this._super.apply(this, arguments);
121 this.edit_record($(event.currentTarget).data('id'));
124 * Checks if a record is being edited, and if so cancels it
126 cancel_pending_edition: function () {
127 var self = this, cancelled = $.Deferred();
130 return cancelled.promise();
133 if (this.edition_id != null) {
134 this.reload_record(self.records.get(this.edition_id)).then(function () {
140 cancelled.then(function () {
141 self.view.unpad_columns();
142 self.edition_form.stop();
143 self.edition_form.$element.remove();
144 delete self.edition_form;
145 delete self.edition_id;
148 this.pad_table_to(5);
149 return cancelled.promise();
152 * Adapts this list's view description to be suitable to the inner form
153 * view of a row being edited.
155 * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows.
157 get_form_fields_view: function () {
159 var view = $.extend(true, {}, this.group.view.fields_view);
160 _(view.arch.children).each(function (widget) {
161 widget.attrs.nolabel = true;
162 if (widget.tag === 'button') {
163 delete widget.attrs.string;
166 view.arch.attrs.col = 2 * view.arch.children.length;
169 on_row_keyup: function (e) {
173 this.save_row().then(function (result) {
174 if (result.created) {
180 next_record = self.records.at(
181 self.records.indexOf(result.edited_record) + 1);
183 next_record_id = next_record.get('id');
184 self.dataset.index = _(self.dataset.ids)
185 .indexOf(next_record_id);
187 self.dataset.index = 0;
188 next_record_id = self.records.at(0).get('id');
190 self.edit_record(next_record_id);
194 this.cancel_edition();
198 render_row_as_form: function (row) {
200 this.cancel_pending_edition().then(function () {
201 var record_id = $(row).data('id');
202 var $new_row = $('<tr>', {
203 id: _.uniqueId('oe-editable-row-'),
204 'data-id': record_id,
205 'class': row ? $(row).attr('class') : '' + ' oe_forms',
206 click: function (e) {e.stopPropagation();}
208 .delegate('button.oe-edit-row-save', 'click', function () {
211 .delegate('button', 'keyup', function (e) {
212 e.stopImmediatePropagation();
214 .keyup(function () { return self.on_row_keyup(); });
216 $new_row.replaceAll(row);
217 } else if (self.options.editable) {
218 var $last_child = self.$current.children('tr:last');
219 if (self.records.length) {
220 if (self.options.editable === 'top') {
221 $new_row.insertBefore(
222 self.$current.children('[data-id]:first'));
224 $new_row.insertAfter(
225 self.$current.children('[data-id]:last'));
228 $new_row.prependTo(self.$current);
230 if ($last_child.is(':not([data-id])')) {
231 $last_child.remove();
235 self.edition_id = record_id;
236 self.edition_form = _.extend(new openerp.web.ListEditableFormView(self.view, self.dataset, false), {
237 form_template: 'ListView.row.form',
238 registry: openerp.web.list.form.widgets,
242 self.edition_form.appendTo();
243 $.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () {
244 // put in $.when just in case FormView.on_loaded becomes asynchronous
245 $new_row.find('> td')
246 .addClass('oe-field-cell')
249 .find('td:last').removeClass('oe-field-cell').end();
250 if (self.options.selectable) {
251 $new_row.prepend('<th>');
253 if (self.options.isClarkGable) {
254 $new_row.prepend('<th>');
256 // pad in case of groupby
257 _(self.columns).each(function (column) {
259 $new_row.prepend('<td>');
262 // Add column for the save, if
263 // there is none in the list
264 if (!self.options.deletable) {
265 self.view.pad_columns(
266 1, {except: $new_row});
269 self.edition_form.do_show();
273 handle_onwrite: function (source_record_id) {
275 var on_write_callback = self.view.fields_view.arch.attrs.on_write;
276 if (!on_write_callback) { return; }
277 this.dataset.call(on_write_callback, [source_record_id], function (ids) {
278 _(ids).each(function (id) {
279 var record = self.records.get(id);
281 // insert after the source record
282 var index = self.records.indexOf(
283 self.records.get(source_record_id)) + 1;
284 record = new openerp.web.list.Record({id: id});
285 self.records.add(record, {at: index});
286 self.dataset.ids.splice(index, 0, id);
288 self.reload_record(record);
293 * Saves the current row, and returns a Deferred resolving to an object
294 * with the following properties:
297 * Boolean flag indicating whether the record saved was being created
298 * (``true`` or edited (``false``)
300 * The result of saving the record (either the newly created record,
301 * or the post-edition record), after insertion in the Collection if
304 * @returns {$.Deferred<{created: Boolean, edited_record: Record}>}
306 save_row: function () {
307 //noinspection JSPotentiallyInvalidConstructorUsage
308 var self = this, done = $.Deferred();
309 return this.edition_form
310 .do_save(null, this.options.editable === 'top')
311 .pipe(function (result) {
312 if (result.created && !self.edition_id) {
313 self.records.add({id: result.result},
314 {at: self.options.editable === 'top' ? 0 : null});
315 self.edition_id = result.result;
317 var edited_record = self.records.get(self.edition_id);
320 self.handle_onwrite(self.edition_id),
321 self.cancel_pending_edition().then(function () {
322 $(self).trigger('saved', [self.dataset]);
323 })).pipe(function () {
325 created: result.created || false,
326 edited_record: edited_record
332 * If the current list is being edited, ensures it's saved
334 ensure_saved: function () {
336 return this.save_row();
338 //noinspection JSPotentiallyInvalidConstructorUsage
339 return $.Deferred().resolve().promise();
342 * Cancels the edition of the row for the current dataset index
344 cancel_edition: function () {
345 this.cancel_pending_edition();
348 * Edits record currently selected via dataset
350 edit_record: function (record_id) {
351 this.render_row_as_form(
352 this.$current.find('[data-id=' + record_id + ']'));
355 [record_id, this.dataset]);
357 new_record: function () {
358 this.dataset.index = null;
359 this.render_row_as_form();
361 render_record: function (record) {
362 var index = this.records.indexOf(record),
364 // FIXME: context dict should probably be extracted cleanly
365 return QWeb.render('ListView.row', {
366 columns: this.columns,
367 options: this.options,
369 row_parity: (index % 2 === 0) ? 'even' : 'odd',
371 render_cell: function () { return self.render_cell(); },
372 edited: !!this.edition_form
376 if (!openerp.web.list) {
377 openerp.web.list = {};
379 if (!openerp.web.list.form) {
380 openerp.web.list.form = {};
382 openerp.web.list.form.WidgetFrame = openerp.web.form.WidgetFrame.extend({
383 template: 'ListView.row.frame'
385 var form_widgets = openerp.web.form.widgets;
386 openerp.web.list.form.widgets = form_widgets.clone({
387 'frame': 'openerp.web.list.form.WidgetFrame'
389 // All form widgets inherit a problematic behavior from
390 // openerp.web.form.WidgetFrame: the cell itself is removed when invisible
391 // whether it's @invisible or @attrs[invisible]. In list view, only the
392 // former should completely remove the cell. We need to override update_dom
393 // on all widgets since we can't just hit on widget itself (I think)
394 var list_form_widgets = openerp.web.list.form.widgets;
395 _(list_form_widgets.map).each(function (widget_path, key) {
396 if (key === 'frame') { return; }
397 var new_path = 'openerp.web.list.form.' + key;
399 openerp.web.list.form[key] = (form_widgets.get_object(key)).extend({
400 update_dom: function () {
401 this.$element.children().css('visibility', '');
402 if (this.modifiers.tree_invisible) {
403 var old_invisible = this.invisible;
404 this.invisible = true;
406 this.invisible = old_invisible;
407 } else if (this.invisible) {
408 this.$element.children().css('visibility', 'hidden');
414 list_form_widgets.add(key, new_path);
417 openerp.web.ListEditableFormView = openerp.web.FormView.extend({
418 init_view: function() {},
419 _render_and_insert: function () {