Widget
======

.. js:class:: openerp.web.Widget

This is the base class for all visual components. It corresponds to an MVC
view. It provides a number of services to handle a section of a page:

* Rendering with QWeb

* Parenting-child relations

* Life-cycle management (including facilitating children destruction when a
  parent object is removed)

* DOM insertion, via jQuery-powered insertion methods. Insertion targets can
  be anything the corresponding jQuery method accepts (generally selectors,
  DOM nodes and jQuery objects):

  :js:func:`~openerp.web.Widget.appendTo`
    Renders the widget and inserts it as the last child of the target, uses
    `.appendTo()`_

  :js:func:`~openerp.web.Widget.prependTo`
    Renders the widget and inserts it as the first child of the target, uses
    `.prependTo()`_

  :js:func:`~openerp.web.Widget.insertAfter`
    Renders the widget and inserts it as the preceding sibling of the target,
    uses `.insertAfter()`_

  :js:func:`~openerp.web.Widget.insertBefore`
    Renders the widget and inserts it as the following sibling of the target,
    uses `.insertBefore()`_

* Backbone-compatible shortcuts

.. _widget-dom_root:

DOM Root
--------

A :js:class:`~openerp.web.Widget` is responsible for a section of the
page materialized by the DOM root of the widget. The DOM root is
available via the :js:attr:`~openerp.web.Widget.el` and
:js:attr:`~openerp.web.Widget.$el` attributes, which are
respectively the raw DOM Element and the jQuery wrapper around the DOM
element.

There are two main ways to define and generate this DOM root:

.. js:attribute:: openerp.web.Widget.template

    Should be set to the name of a QWeb template (a
    :js:class:`String`). If set, the template will be rendered after
    the widget has been initialized but before it has been
    started. The root element generated by the template will be set as
    the DOM root of the widget.

.. js:attribute:: openerp.web.Widget.tagName

    Used if the widget has no template defined. Defaults to ``div``,
    will be used as the tag name to create the DOM element to set as
    the widget's DOM root. It is possible to further customize this
    generated DOM root with the following attributes:

    .. js:attribute:: openerp.web.Widget.id

        Used to generate an ``id`` attribute on the generated DOM
        root.

    .. js:attribute:: openerp.web.Widget.className

        Used to generate a ``class`` attribute on the generated DOM root.

    .. js:attribute:: openerp.web.Widget.attributes

        Mapping (object literal) of attribute names to attribute
        values. Each of these k:v pairs will be set as a DOM attribute
        on the generated DOM root.

    None of these is used in case a template is specified on the widget.

The DOM root can also be defined programmatically by overridding

.. js:function:: openerp.web.Widget.renderElement

    Renders the widget's DOM root and sets it. The default
    implementation will render a set template or generate an element
    as described above, and will call
    :js:func:`~openerp.web.Widget.setElement` on the result.

    Any override to :js:func:`~openerp.web.Widget.renderElement` which
    does not call its ``_super`` **must** call
    :js:func:`~openerp.web.Widget.setElement` with whatever it
    generated or the widget's behavior is undefined.

    .. note::

        The default :js:func:`~openerp.web.Widget.renderElement` can
        be called repeatedly, it will *replace* the previous DOM root
        (using ``replaceWith``). However, this requires that the
        widget correctly sets and unsets its events (and children
        widgets). Generally,
        :js:func:`~openerp.web.Widget.renderElement` should not be
        called repeatedly unless the widget advertizes this feature.

Accessing DOM content
~~~~~~~~~~~~~~~~~~~~~

Because a widget is only responsible for the content below its DOM
root, there is a shortcut for selecting sub-sections of a widget's
DOM:

.. js:function:: openerp.web.Widget.$(selector)

    Applies the CSS selector specified as parameter to the widget's
    DOM root.

    .. code-block:: javascript

        this.$(selector);

    is functionally identical to:

    .. code-block:: javascript

        this.$el.find(selector);

    :param String selector: CSS selector
    :returns: jQuery object

    .. note:: this helper method is compatible with
              ``Backbone.View.$``

Resetting the DOM root
~~~~~~~~~~~~~~~~~~~~~~

.. js:function:: openerp.web.Widget.setElement(element)

    Re-sets the widget's DOM root to the provided element, also
    handles re-setting the various aliases of the DOM root as well as
    unsetting and re-setting delegated events.

    :param Element element: a DOM element or jQuery object to set as
                            the widget's DOM root

    .. note:: should be mostly compatible with `Backbone's
              setElement`_

DOM events handling
-------------------

A widget will generally need to respond to user action within its
section of the page. This entails binding events to DOM elements.

To this end, :js:class:`~openerp.web.Widget` provides an shortcut:

.. js:attribute:: openerp.web.Widget.events

    Events are a mapping of ``event selector`` (an event name and a
    CSS selector separated by a space) to a callback. The callback can
    be either a method name in the widget or a function. In either
    case, the ``this`` will be set to the widget:

    .. code-block:: javascript

        events: {
            'click p.oe_some_class a': 'some_method',
            'change input': function (e) {
                e.stopPropagation();
            }
        },

    The selector is used for jQuery's `event delegation`_, the
    callback will only be triggered for descendants of the DOM root
    matching the selector [0]_. If the selector is left out (only an
    event name is specified), the event will be set directly on the
    widget's DOM root.

.. js:function:: openerp.web.Widget.delegateEvents

    This method is in charge of binding
    :js:attr:`~openerp.web.Widget.events` to the DOM. It is
    automatically called after setting the widget's DOM root.

    It can be overridden to set up more complex events than the
    :js:attr:`~openerp.web.Widget.events` map allows, but the parent
    should always be called (or :js:attr:`~openerp.web.Widget.events`
    won't be handled correctly).

.. js:function:: openerp.web.Widget.undelegateEvents

    This method is in charge of unbinding
    :js:attr:`~openerp.web.Widget.events` from the DOM root when the
    widget is destroyed or the DOM root is reset, in order to avoid
    leaving "phantom" events.

    It should be overridden to un-set any event set in an override of
    :js:func:`~openerp.web.Widget.delegateEvents`.

.. note:: this behavior should be compatible with `Backbone's
          delegateEvents`_, apart from not accepting any argument.

Subclassing Widget
------------------

:js:class:`~openerp.base.Widget` is subclassed in the standard manner (via the
:js:func:`~openerp.base.Class.extend` method), and provides a number of
abstract properties and concrete methods (which you may or may not want to
override). Creating a subclass looks like this:

.. code-block:: javascript

    var MyWidget = openerp.base.Widget.extend({
        // QWeb template to use when rendering the object
        template: "MyQWebTemplate",

        init: function(parent) {
            this._super(parent);
            // insert code to execute before rendering, for object
            // initialization
        },
        start: function() {
            this._super();
            // post-rendering initialization code, at this point
            // ``this.$element`` has been initialized
            this.$element.find(".my_button").click(/* an example of event binding * /);

            // if ``start`` is asynchronous, return a promise object so callers
            // know when the object is done initializing
            return this.rpc(/* … */)
        }
    });

The new class can then be used in the following manner:

.. code-block:: javascript

    // Create the instance
    var my_widget = new MyWidget(this);
    // Render and insert into DOM
    my_widget.appendTo(".some-div");

After these two lines have executed (and any promise returned by ``appendTo``
has been resolved if needed), the widget is ready to be used.

.. note:: the insertion methods will start the widget themselves, and will
          return the result of :js:func:`~openerp.base.Widget.start()`.

          If for some reason you do not want to call these methods, you will
          have to first call :js:func:`~openerp.base.Widget.render()` on the
          widget, then insert it into your DOM and start it.

If the widget is not needed anymore (because it's transient), simply terminate
it:

.. code-block:: javascript

    my_widget.destroy();

will unbind all DOM events, remove the widget's content from the DOM and
destroy all widget data.

.. [0] not all DOM events are compatible with events delegation

.. _.appendTo():
    http://api.jquery.com/appendTo/

.. _.prependTo():
    http://api.jquery.com/prependTo/

.. _.insertAfter():
    http://api.jquery.com/insertAfter/

.. _.insertBefore():
    http://api.jquery.com/insertBefore/

.. _event delegation:
    http://api.jquery.com/delegate/

.. _Backbone's setElement:
    http://backbonejs.org/#View-setElement

.. _Backbone's delegateEvents:
    http://backbonejs.org/#View-delegateEvents

