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 (instance) {
8 var QWeb = instance.web.qweb;
10 // editability status of list rows
11 instance.web.ListView.prototype.defaults.editable = null;
13 // TODO: not sure second @lends on existing item is correct, to check
14 instance.web.ListView.include(/** @lends instance.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 {instance.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 = (
57 ! this.options.read_only && ((force && "bottom") || 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.$element.find('table:first').show();
73 this.$element.find('.oe_view_nocontent').remove();
74 this.groups.new_record();
79 on_loaded: function (data, grouped) {
80 // tree/@editable takes priority on everything else if present.
81 this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable);
82 return this._super(data, grouped);
85 * Ensures the editable list is saved (saves any pending edition if
86 * needed, or tries to)
88 * Returns a deferred to the end of the saving.
90 * @returns {$.Deferred}
92 ensure_saved: function () {
93 return this.groups.ensure_saved();
97 instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{
98 passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved",
99 new_record: function () {
100 // TODO: handle multiple children
101 this.children[null].new_record();
104 * Ensures descendant editable List instances are all saved if they have
107 * @returns {$.Deferred}
109 ensure_saved: function () {
110 return $.when.apply(null,
112 _.values(this.children),
117 instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{
118 row_clicked: function (event) {
119 if (!this.options.editable) {
120 return this._super.apply(this, arguments);
122 this.edit_record($(event.currentTarget).data('id'));
125 * Checks if a record is being edited, and if so cancels it
127 cancel_pending_edition: function () {
128 var self = this, cancelled;
133 if (this.edition_id) {
134 cancelled = this.reload_record(this.records.get(this.edition_id));
136 cancelled = $.when();
138 cancelled.then(function () {
139 self.view.unpad_columns();
140 self.edition_form.destroy();
141 self.edition_form.$element.remove();
142 delete self.edition_form;
143 self.dataset.index = null;
144 delete self.edition_id;
147 this.pad_table_to(5);
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 on_row_keyup: function (e) {
174 //e.stopImmediatePropagation();
175 setTimeout(function () {
176 self.save_row().then(function (result) {
177 if (result.created) {
183 next_record = self.records.at(
184 self.records.indexOf(result.edited_record) + 1);
186 next_record_id = next_record.get('id');
187 self.dataset.index = _(self.dataset.ids)
188 .indexOf(next_record_id);
190 self.dataset.index = 0;
191 next_record_id = self.records.at(0).get('id');
193 self.edit_record(next_record_id);
198 this.cancel_edition();
202 render_row_as_form: function (row) {
204 return this.ensure_saved().pipe(function () {
205 var record_id = $(row).data('id');
206 var $new_row = $('<tr>', {
207 id: _.uniqueId('oe-editable-row-'),
208 'data-id': record_id,
209 'class': (row ? $(row).attr('class') : ''),
210 click: function (e) {e.stopPropagation();}
212 .addClass('oe_form oe_form_container')
213 .delegate('button.oe_list_edit_row_save', 'click', function () {
216 .delegate('button', 'keyup', function (e) {
217 e.stopImmediatePropagation();
220 return self.on_row_keyup.apply(self, arguments); })
221 .keydown(function (e) { e.stopPropagation(); })
222 .keypress(function (e) {
223 if (e.which === KEY_RETURN) {
229 $new_row.replaceAll(row);
230 } else if (self.options.editable) {
231 var $last_child = self.$current.children('tr:last');
232 if (self.records.length) {
233 if (self.options.editable === 'top') {
234 $new_row.insertBefore(
235 self.$current.children('[data-id]:first'));
237 $new_row.insertAfter(
238 self.$current.children('[data-id]:last'));
241 $new_row.prependTo(self.$current);
243 if ($last_child.is(':not([data-id])')) {
244 $last_child.remove();
248 self.edition_id = record_id;
249 self.dataset.index = _(self.dataset.ids).indexOf(record_id);
250 if (self.dataset.index === -1) {
251 self.dataset.index = null;
253 self.edition_form = _.extend(new instance.web.ListEditableFormView(self.view, self.dataset, false), {
257 // put in $.when just in case FormView.on_loaded becomes asynchronous
258 return $.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () {
259 $new_row.find('> td')
261 .find('td:last').removeClass('oe_list_field_cell').end();
262 // pad in case of groupby
263 _(self.columns).each(function (column) {
265 $new_row.prepend('<td>');
268 // Add column for the save, if
269 // there is none in the list
270 if (!self.options.deletable) {
271 self.view.pad_columns(
272 1, {except: $new_row});
275 self.edition_form.do_show();
279 handle_onwrite: function (source_record_id) {
281 var on_write_callback = self.view.fields_view.arch.attrs.on_write;
282 if (!on_write_callback) { return; }
283 this.dataset.call(on_write_callback, [source_record_id], function (ids) {
284 _(ids).each(function (id) {
285 var record = self.records.get(id);
287 // insert after the source record
288 var index = self.records.indexOf(
289 self.records.get(source_record_id)) + 1;
290 record = new instance.web.list.Record({id: id});
291 self.records.add(record, {at: index});
292 self.dataset.ids.splice(index, 0, id);
294 self.reload_record(record);
299 * Saves the current row, and returns a Deferred resolving to an object
300 * with the following properties:
303 * Boolean flag indicating whether the record saved was being created
304 * (``true`` or edited (``false``)
306 * The result of saving the record (either the newly created record,
307 * or the post-edition record), after insertion in the Collection if
310 * @returns {$.Deferred<{created: Boolean, edited_record: Record}>}
312 save_row: function () {
313 //noinspection JSPotentiallyInvalidConstructorUsage
315 return this.edition_form
316 .do_save(null, this.options.editable === 'top')
317 .pipe(function (result) {
318 if (result.created && !self.edition_id) {
319 self.records.add({id: result.result},
320 {at: self.options.editable === 'top' ? 0 : null});
321 self.edition_id = result.result;
323 var edited_record = self.records.get(self.edition_id);
326 self.handle_onwrite(self.edition_id),
327 self.cancel_pending_edition().then(function () {
328 $(self).trigger('saved', [self.dataset]);
329 })).pipe(function () {
331 created: result.created || false,
332 edited_record: edited_record
338 * If the current list is being edited, ensures it's saved
340 ensure_saved: function () {
342 // kinda-hack-ish: if the user has entered data in a field,
343 // oe_form_dirty will be set on the form so save, otherwise
344 // discard the current (entirely empty) line
345 if (this.edition_form.$element.is('.oe_form_dirty')) {
346 return this.save_row();
348 return this.cancel_pending_edition();
350 //noinspection JSPotentiallyInvalidConstructorUsage
354 * Cancels the edition of the row for the current dataset index
356 cancel_edition: function () {
357 this.cancel_pending_edition();
360 * Edits record currently selected via dataset
362 edit_record: function (record_id) {
363 this.render_row_as_form(
364 this.$current.find('[data-id=' + record_id + ']'));
367 [record_id, this.dataset]);
369 new_record: function () {
370 this.render_row_as_form();
372 render_record: function (record) {
373 var index = this.records.indexOf(record),
375 // FIXME: context dict should probably be extracted cleanly
376 return QWeb.render('ListView.row', {
377 columns: this.columns,
378 options: this.options,
380 row_parity: (index % 2 === 0) ? 'even' : 'odd',
382 render_cell: function () {
383 return self.render_cell.apply(self, arguments); },
384 edited: !!this.edition_form
389 instance.web.ListEditableFormView = instance.web.FormView.extend({
391 this._super.apply(this, arguments);
392 this.rendering_engine = new instance.web.ListEditableRenderingEngine(this);
393 this.options.initial_mode = "edit";
395 renderElement: function() {}
398 instance.web.ListEditableRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({
399 init: function(view) {
402 set_fields_view: function(fields_view) {
403 this.fvg = fields_view;
405 set_tags_registry: function(tags_registry) {
406 this.tags_registry = tags_registry;
408 set_fields_registry: function(fields_registry) {
409 this.fields_registry = fields_registry;
411 render_to: function($element) {
414 var xml = instance.web.json_node_to_xml(this.fvg.arch);
417 if (this.view.editable_list.options.selectable)
418 $("<td>").appendTo($element);
420 $xml.children().each(function(i, el) {
421 var modifiers = JSON.parse($(el).attr("modifiers") || "{}");
423 if (modifiers.tree_invisible === true)
425 var tag_name = el.tagName.toLowerCase();
427 if (tag_name === "field") {
428 var name = $(el).attr("name");
429 var key = $(el).attr('widget') || self.fvg.fields[name].type;
430 var obj = self.view.fields_registry.get_object(key);
431 w = new (obj)(self.view, instance.web.xml_to_json(el));
432 self.view.register_field(w, $(el).attr("name"));
434 var obj = self.tags_registry.get_object(tag_name);
435 w = new (obj)(self.view, instance.web.xml_to_json(el));
438 $td.appendTo($element);
440 $(QWeb.render('ListView.row.save')).appendTo($element);