diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 0926e233109..826ee03a0c1 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -139,7 +139,7 @@ openerp.web.list_editable = function (instance) { if (!record) { record = new instance.web.list.Record(); this.records.add(record, { - at: this.options.editable === 'top' ? 0 : null}); + at: this.isPrependOnCreate() ? 0 : null}); } var $recordRow = this.groups.getRowFor(record); var cells = this.getCellsFor($recordRow); @@ -293,6 +293,9 @@ openerp.web.list_editable = function (instance) { })); }); }, + isPrependOnCreate: function () { + return this.options.editable === 'top'; + } }); instance.web.list.Editor = instance.web.Widget.extend({ @@ -305,18 +308,21 @@ openerp.web.list_editable = function (instance) { * @param {instance.web.Widget} parent * @param {Object} options * @param {instance.web.FormView} [options.formView=instance.web.FormView] + * @param {Object} [options.delegate] */ init: function (parent, options) { this._super(parent); this.options = options || {}; _.defaults(this.options, { - formView: instance.web.FormView + formView: instance.web.FormView, + delegate: this.getParent() }); + this.delegate = this.options.delegate; this.record = null; this.form = new (this.options.formView)( - this, this.getParent().dataset, false, { + this, this.delegate.dataset, false, { initial_mode: 'edit', $buttons: $(), $pager: $() @@ -325,8 +331,7 @@ openerp.web.list_editable = function (instance) { start: function () { var self = this; var _super = this._super(); - // TODO: getParent() should be delegate defaulting to getParent() - this.form.embedded_view = this.getParent().editionView(this); + this.form.embedded_view = this.delegate.editionView(this); var form_ready = this.form.appendTo(this.$element).then( self.form.proxy('do_hide')); return $.when(_super, form_ready); @@ -363,7 +368,7 @@ openerp.web.list_editable = function (instance) { save: function () { var self = this; return this.form - .do_save(null, this.getParent().options.editable === 'top') + .do_save(null, this.delegate.isPrependOnCreate()) .pipe(function (result) { var created = result.created && !self.record.id; if (created) { diff --git a/doc/index.rst b/doc/index.rst index c5b85326dac..8608af59d3d 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -18,6 +18,7 @@ Contents: search-view + list-view form-notes Older stuff diff --git a/doc/list-view.rst b/doc/list-view.rst new file mode 100644 index 00000000000..a7436e419dc --- /dev/null +++ b/doc/list-view.rst @@ -0,0 +1,295 @@ +List View +========= + +Editable list view +------------------ + +List view edition is an extension to the base listview providing the +capability of inline record edition by delegating to an embedded form +view. + +.. todo:: + + cleanup options and settings for editability configuration. Right + now there are: + + ``defaults.editable`` + + ``null``, ``"top"`` or ``"bottom"``, generally broken and + useless + + ``context.set_editable`` + + forces ``options.editable`` to ``"bottom"`` + + ``view.arch.attrs.editable`` + + same as ``defaults.editable``, but applied separately (after + reloading the view), if absent delegates to + ``options.editable`` which may have been set previously. + + ``options.read_only`` + + worthless shit added by niv in the listview directly (wtf?) + + .. note:: can probably be replaced by cancelling ``edit:before`` + + and :js:func:`~openerp.web.ListView.set_editable` which + ultimately behaves weird-as-fuck-ly. + +The editable list view module adds a number of methods to the list +view, on top of implementing the :js:class:`EditorDelegate` protocol: + +Interaction Methods ++++++++++++++++++++ + +.. js:function:: openerp.web.ListView.ensureSaved + + Attempts to resolve the pending edition, if any, by saving the + edited row's current state. + + :returns: delegate resolving to all editions having been saved, or + rejected if a pending edition could not be saved + (e.g. validation failure) + +.. js:function:: openerp.web.ListView.startEdition([record]) + + Starts editing the provided record inline, through an overlay form + view of editable fields in the record. + + If no record is provided, creates a new one according to the + editability configuration of the list view. + + This method resolves any pending edition when invoked, before + starting a new edition. + + :type record: :js:class:`~openerp.web.list.Record` + :returns: delegate to the form used for the edition + +.. js:function:: openerp.web.ListView.saveEdition + + Resolves the pending edition. + + :returns: delegate to the save being completed, resolves to an + object with two attributes ``created`` (flag indicating + whether the saved record was just created or was + updated) and ``record`` the reloaded record having been + edited. + +.. js:function:: openerp.web.ListView.cancelEdition + + Cancels pending edition, cleans up the list view in case of + creation (removes the empty record being created). + +Utility Methods ++++++++++++++++ + +.. js:function:: openerp.web.ListView.getCellsFor(row) + + Extracts the cells from a listview row, and puts them in a + {fieldname: cell} mapping for analysis and manipulation. + + :param jQuery row: + :rtype: Object + +.. js:function:: openerp.web.ListView.withEvent(event_name, event, action[, args][, trigger_params]) + + Executes ``action`` in the context of the view's editor, + bracketing it with cancellable event signals. + + :param String event_name: base name for the bracketing event, will + be postfixed by ``:before`` and + ``:after`` before being called + (respectively before and after + ``action`` is executed) + :param Object event: object passed to the ``:before`` event + handlers. + :param Function action: function called with the view's editor as + its ``this``. May return a deferred. + :param Array args: arguments passed to ``action`` + :param Array trigger_params: arguments passed to the ``:after`` + event handler alongside the results + of ``action`` + +Behavioral Customizations ++++++++++++++++++++++++++ + +.. js:function:: openerp.web.ListView.handleOnWrite(record) + + Implements the handling of the ``onwrite`` listview attribute: + calls the RPC methods specified by ``@onwrite``, and if that + method returns an array of ids loads or reloads the records + corresponding to those ids. + + :param record: record being written having triggered the + ``onwrite`` callback + :type record: openerp.web.list.Record + :returns: deferred to all reloadings being done + +Events +++++++ + +For simpler interactions by/with external users of the listview, the +view provides a number of dedicated events to its lifecycle. + +.. note:: if an event is defined as *cancellable*, it means its first + parameter is an object on which the ``cancel`` attribute can + be set. If the ``cancel`` attribute is set, the view will + abort its current behavior as soon as possible, and rollback + any state modification. + +``edit:before`` *cancellable* + + Invoked before the list view starts editing a record. + + Provided with an event object with a single property ``record``, + holding the attributes of the record being edited (``record`` is + empty *but not null* for a new record) + +``edit:after`` + + Invoked after the list view has gone into an edition state, + provided with the attributes of the record being edited (see + ``edit:before``) as first parameter and the form used for the + edition as second parameter. + +``save:before`` *cancellable* + + Invoked right before saving a pending edition, provided with an + event object holding the listview's editor (``editor``) and the + edition form (``form``) + +``save:after`` + + Invoked after a save has been completed + + .. todo:: currently invoked before the record has reloaded, which + is kinda shitty + +``cancel:before`` *cancellable* + + Invoked before cancelling a pending edition, provided with the + same information as ``save:before``. + +``cancel:after`` + + Invoked after a pending edition has been cancelled. + +Editor +------ + +The list-edition modules does not generally interact with the embedded +formview, delegating instead to its +:js:class:`~openerp.web.list.Editor`. + +.. js:class:: openerp.web.list.Editor(parent[, options]) + + The editor object provides a more convenient interface to form + views, and simplifies the usage of form views for semi-arbitrary + edition of stuff. + + However, the editor does *not* task itself with being internally + consistent at this point: calling + e.g. :js:func:`~openerp.web.list.Editor.edit` multiple times in a + row without saving or cancelling each edit is undefined. + + .. todo:: define this behavior + + :param parent: + :type parent: :js:class:`~openerp.web.Widget` + :param EditorOptions options: + + .. js:function:: openerp.web.list.Editor.isEditing + + Indicates whether the editor is currently in the process of + providing edition for a field. + + :rtype: Boolean + + .. js:function:: openerp.web.list.Editor.edit(record, configureField) + + Loads the provided record into the internal form view and + displays the form view. + + Will also attempt to focus the first visible field of the form + view. + + :param Object record: record to load into the form view + (key:value mapping similar to the result + of a ``read``) + :param configureField: function called with each field of the + form view right after the form is + displayed, lets whoever called this + method do some last-minute + configuration of form fields. + :type configureField: Function + :returns: jQuery delegate to the form object + + .. js:function:: openerp.web.list.Editor.save + + Attempts to save the internal form, then hide it + + :returns: delegate to the record under edition (with ``id`` + added for a creation). The record is not updated + from when it was passed in, aside from the ``id`` + attribute. + + .. js:function:: openerp.web.list.Editor.cancel + + Attemps to cancel the edition of the internal form, then hide + the form + + :returns: delegate to the record under edition + +.. js:class:: EditorOptions + + .. js:attribute:: EditorOptions.formView + + Form view (sub)-class to instantiate and delegate edition to. + + By default, :js:class:`~openerp.web.FormView` + + .. js:attribute:: EditorOptions.delegate + + Object used to get various bits of information about how to + display stuff. + + By default, uses the editor's parent widget. See + :js:class:`EditorDelegate` for the methods and attributes to + provide. + +.. js:class:: EditorDelegate + + Informal protocol defining the methods and attributes expected of + the :js:class:`~openerp.web.list.Editor`'s delegate. + + .. js:attribute:: EditorDelegate.dataset + + The dataset passed to the form view to synchronize the form + view and the outer widget. + + .. js:function:: EditorDelegate.editionView(editor) + + Called by the :js:class:`~openerp.web.list.Editor` object to + get a form view (JSON) to pass along to the form view it + created. + + The result should be a valid form view, see :doc:`Form Notes + ` for various peculiarities of the form view + format. + + :param editor: editor object asking for the view + :type editor: :js:class:`~openerp.web.list.Editor` + :returns: form view + :rtype: Object + + .. js:function:: EditorDelegate.isPrependOnCreate + + By default, the :js:class:`~openerp.web.list.Editor` will + append the ids of newly created records to the + :js:attr:`EditorDelegate.dataset`. If this method returns + ``true``, it will prepend these ids instead. + + :returns: whether new records should be prepended to the + dataset (instead of appended) + :rtype: Boolean