/** * handles editability case for lists, because it depends on form and forms already depends on lists it had to be split out * @namespace */ openerp.web.list_editable = function (instance) { // editability status of list rows instance.web.ListView.prototype.defaults.editable = null; // TODO: not sure second @lends on existing item is correct, to check instance.web.ListView.include(/** @lends instance.web.ListView# */{ init: function () { var self = this; this._super.apply(this, arguments); this.editor = this.make_editor(); // Stores records of {field, cell}, allows for re-rendering fields // depending on cell state during and after resize events this.fields_for_resize = []; instance.web.bus.on('resize', this, this.resize_fields); $(this.groups).bind({ 'edit': function (e, id, dataset) { self.do_edit(dataset.index, id, dataset); }, 'saved': function () { if (self.groups.get_selection().length) { return; } self.configure_pager(self.dataset); self.compute_aggregates(); } }); this.records.bind('remove', function () { if (self.editor.is_editing()) { self.cancel_edition(); } }); this.on('edit:after', this, function () { self.$element.add(self.$buttons).addClass('oe_editing'); }); this.on('save:after cancel:after', this, function () { self.$element.add(self.$buttons).removeClass('oe_editing'); }); }, destroy: function () { instance.web.bus.off('resize', this, this.resize_fields); this._super(); }, /** * Handles the activation of a record in editable mode (making a record * editable), called *after* the record has become editable. * * The default behavior is to setup the listview's dataset to match * whatever dataset was provided by the editing List * * @param {Number} index index of the record in the dataset * @param {Object} id identifier of the record being edited * @param {instance.web.DataSet} dataset dataset in which the record is available */ do_edit: function (index, id, dataset) { _.extend(this.dataset, dataset); }, /** * Sets editability status for the list, based on defaults, view * architecture and the provided flag, if any. * * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom". */ set_editable: function (force) { // TODO: fix handling of editability status to be simpler & clearer & more coherent // If ``force``, set editability to bottom // otherwise rely on view default // view' @editable is handled separately as we have not yet // fetched and processed the view at this point. this.options.editable = ( ! this.options.read_only && ((force && "bottom") || this.defaults.editable)); }, /** * Replace do_search to handle editability process */ do_search: function(domain, context, group_by) { this.set_editable(context['set_editable']); this._super.apply(this, arguments); }, /** * Replace do_add_record to handle editability (and adding new record * as an editable row at the top or bottom of the list) */ do_add_record: function () { if (this.options.editable) { this.$element.find('table:first').show(); this.$element.find('.oe_view_nocontent').remove(); this.start_edition(); } else { this._super(); } }, on_loaded: function (data, grouped) { var self = this; if (this.editor) { this.editor.destroy(); } // tree/@editable takes priority on everything else if present. this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable); var result = this._super(data, grouped); if (this.options.editable) { // FIXME: any hook available to ensure this is only done once? this.$buttons .off('click', '.oe_list_save') .on('click', '.oe_list_save', this.proxy('save_edition')) .off('click', '.oe_list_discard') .on('click', '.oe_list_discard', function (e) { e.preventDefault(); self.cancel_edition(); }); this.$element .off('click', 'tbody td:not(.oe_list_field_cell)') .on('click', 'tbody td:not(.oe_list_field_cell)', function () { if (!self.editor.is_editing()) { self.start_edition(); } }); // Editor is not restartable due to formview not being // restartable this.editor = this.make_editor(); var editor_ready = this.editor.prependTo(this.$element) .then(this.proxy('setup_events')); return $.when(result, editor_ready); } return result; }, /** * Builds a new editor object * * @return {instance.web.list.Editor} */ make_editor: function () { return new instance.web.list.Editor(this); }, do_button_action: function () { var self = this, args = arguments; this.ensure_saved().then(function () { self.handle_button.apply(self, args); }); }, /** * Ensures the editable list is saved (saves any pending edition if * needed, or tries to) * * Returns a deferred to the end of the saving. * * @returns {$.Deferred} */ ensure_saved: function () { if (!this.editor.is_editing()) { return $.when(); } return this.save_edition(); }, /** * Set up the edition of a record of the list view "inline" * * @param {instance.web.list.Record} [record] record to edit, leave empty to create a new record * @param {Object} [options] * @param {String} [options.focus_field] field to focus at start of edition * @return {jQuery.Deferred} */ start_edition: function (record, options) { var self = this; var item = false; if (record) { item = record.attributes; } else { var attrs = {id: false}; _(this.columns).chain() .filter(function (x) { return x.tag === 'field'}) .pluck('name') .each(function (field) { attrs[field] = false; }); record = new instance.web.list.Record(attrs); this.records.add(record, { at: this.prepends_on_create() ? 0 : null}); } var $recordRow = this.groups.get_row_for(record); var cells = this.get_cells_for($recordRow); return this.ensure_saved().pipe(function () { self.fields_for_resize.splice(0, self.fields_for_resize.length); return self.with_event('edit', { record: record.attributes, cancel: false }, function () { return self.editor.edit(item, function (field_name, field) { var cell = cells[field_name]; if (!cell || field.get('effective_readonly')) { // Readonly fields can just remain the list's, // form's usually don't have backgrounds &al field.set({invisible: true}); return; } // FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow field.$element.attr('data-fieldname', field_name); self.fields_for_resize.push({field: field, cell: cell}); }, options).pipe(function () { $recordRow.addClass('oe_edition'); self.resize_fields(); return record.attributes; }); }); }); }, get_cells_for: function ($row) { var cells = {}; $row.children('td').each(function (index, el) { cells[el.getAttribute('data-field')] = el }); return cells; }, /** * If currently editing a row, resizes all registered form fields based * on the corresponding row cell */ resize_fields: function () { if (!this.editor.is_editing()) { return; } for(var i=0, len=this.fields_for_resize.length; i