[ADD] JS widgets reference documentation
Also fixed Widget#alive's behavior and tested it
This commit is contained in:
parent
2c25fa2dc4
commit
62fcce9054
|
@ -34,7 +34,6 @@ Javascript
|
|||
:maxdepth: 1
|
||||
|
||||
guidelines
|
||||
widget
|
||||
rpc
|
||||
async
|
||||
client_action
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
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
|
||||
|
|
@ -258,24 +258,24 @@ openerp.ParentedMixin = {
|
|||
current object is destroyed.
|
||||
*/
|
||||
alive: function(promise, reject) {
|
||||
var def = $.Deferred();
|
||||
var self = this;
|
||||
promise.done(function() {
|
||||
if (! self.isDestroyed()) {
|
||||
if (! reject)
|
||||
return $.Deferred(function (def) {
|
||||
promise.then(function () {
|
||||
if (!self.isDestroyed()) {
|
||||
def.resolve.apply(def, arguments);
|
||||
else
|
||||
def.reject();
|
||||
}
|
||||
}).fail(function() {
|
||||
if (! self.isDestroyed()) {
|
||||
if (! reject)
|
||||
}
|
||||
}, function () {
|
||||
if (!self.isDestroyed()) {
|
||||
def.reject.apply(def, arguments);
|
||||
else
|
||||
}
|
||||
}).always(function () {
|
||||
if (reject) {
|
||||
// noop if def already resolved or rejected
|
||||
def.reject();
|
||||
}
|
||||
});
|
||||
return def.promise();
|
||||
}
|
||||
// otherwise leave promise in limbo
|
||||
});
|
||||
}).promise();
|
||||
},
|
||||
/**
|
||||
* Inform the object it should destroy itself, releasing any
|
||||
|
|
|
@ -400,8 +400,68 @@ ropenerp.testing.section('Widget.events', {
|
|||
ok(newclicked, "undelegate should only unbind events it created");
|
||||
});
|
||||
});
|
||||
ropenerp.testing.section('Widget.async', {
|
||||
|
||||
ropenerp.testing.section('server-formats', {
|
||||
}, function (test) {
|
||||
test("alive(alive)", {asserts: 1}, function () {
|
||||
var w = new (openerp.Widget.extend({}));
|
||||
return $.async_when(w.start())
|
||||
.then(function () { return w.alive($.async_when()) })
|
||||
.then(function () { ok(true); });
|
||||
});
|
||||
test("alive(dead)", {asserts: 1}, function () {
|
||||
var w = new (openerp.Widget.extend({}));
|
||||
|
||||
return $.Deferred(function (d) {
|
||||
$.async_when(w.start())
|
||||
.then(function () {
|
||||
// destroy widget
|
||||
w.destroy();
|
||||
var promise = $.async_when();
|
||||
// leave time for alive() to do its stuff
|
||||
promise.then(function () {
|
||||
return $.async_when();
|
||||
}).then(function () {
|
||||
ok(true);
|
||||
d.resolve();
|
||||
});
|
||||
// ensure that w.alive() refuses to resolve or reject
|
||||
return w.alive(promise);
|
||||
}).always(function () {
|
||||
d.reject();
|
||||
ok(false, "alive() should not terminate by default");
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test("alive(alive, true)", {asserts: 1}, function () {
|
||||
var w = new (openerp.Widget.extend({}));
|
||||
return $.async_when(w.start())
|
||||
.then(function () { return w.alive($.async_when(), true) })
|
||||
.then(function () { ok(true); });
|
||||
});
|
||||
test("alive(dead, true)", {asserts: 1, fail_on_rejection: false}, function () {
|
||||
var w = new (openerp.Widget.extend({}));
|
||||
|
||||
return $.async_when(w.start())
|
||||
.then(function () {
|
||||
// destroy widget
|
||||
w.destroy();
|
||||
console.log('destroyed');
|
||||
return w.alive($.async_when().done(function () { console.log('when'); }), true);
|
||||
}).then(function () {
|
||||
console.log('unfailed')
|
||||
ok(false, "alive(p, true) should fail its promise");
|
||||
}, function () {
|
||||
console.log('failed')
|
||||
ok(true, "alive(p, true) should fail its promise");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
ropenerp.testing.section('server-formats', {
|
||||
dependencies: ['web.core', 'web.dates']
|
||||
}, function (test) {
|
||||
test('Parse server datetime', function () {
|
||||
|
|
|
@ -6714,3 +6714,6 @@ pre {
|
|||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
}
|
||||
.descclassname {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
@ -551,3 +551,8 @@ pre {
|
|||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
// lighten js namespace/class name
|
||||
.descclassname {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.. highlight:: javascript
|
||||
|
||||
.. default-domain:: js
|
||||
|
||||
==========
|
||||
Javascript
|
||||
==========
|
||||
|
@ -5,8 +9,400 @@ Javascript
|
|||
Widgets
|
||||
=======
|
||||
|
||||
.. qweb integration: ``template`` is an (optional) automatically rendered
|
||||
template
|
||||
.. class:: openerp.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):
|
||||
|
||||
:func:`~openerp.Widget.appendTo`
|
||||
Renders the widget and inserts it as the last child of the target, uses
|
||||
`.appendTo()`_
|
||||
|
||||
:func:`~openerp.Widget.prependTo`
|
||||
Renders the widget and inserts it as the first child of the target, uses
|
||||
`.prependTo()`_
|
||||
|
||||
:func:`~openerp.Widget.insertAfter`
|
||||
Renders the widget and inserts it as the preceding sibling of the target,
|
||||
uses `.insertAfter()`_
|
||||
|
||||
:func:`~openerp.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 :class:`~openerp.Widget` is responsible for a section of the
|
||||
page materialized by the DOM root of the widget.
|
||||
|
||||
A widget's DOM root is available via two attributes:
|
||||
|
||||
.. attribute:: openerp.Widget.el
|
||||
|
||||
raw DOM element set as root to the widget
|
||||
|
||||
.. attribute:: openerp.Widget.$el
|
||||
|
||||
jQuery wrapper around :attr:`~openerp.Widget.el`
|
||||
|
||||
There are two main ways to define and generate this DOM root:
|
||||
|
||||
.. attribute:: openerp.Widget.template
|
||||
|
||||
Should be set to the name of a :ref:`QWeb template <reference/qweb>`.
|
||||
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.
|
||||
|
||||
.. attribute:: openerp.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:
|
||||
|
||||
.. attribute:: openerp.Widget.id
|
||||
|
||||
Used to generate an ``id`` attribute on the generated DOM
|
||||
root.
|
||||
|
||||
.. attribute:: openerp.Widget.className
|
||||
|
||||
Used to generate a ``class`` attribute on the generated DOM root.
|
||||
|
||||
.. attribute:: openerp.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
|
||||
|
||||
.. function:: openerp.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
|
||||
:func:`~openerp.Widget.setElement` on the result.
|
||||
|
||||
Any override to :func:`~openerp.Widget.renderElement` which
|
||||
does not call its ``_super`` **must** call
|
||||
:func:`~openerp.Widget.setElement` with whatever it
|
||||
generated or the widget's behavior is undefined.
|
||||
|
||||
.. note::
|
||||
|
||||
The default :func:`~openerp.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, :func:`~openerp.Widget.renderElement` should
|
||||
not be called repeatedly unless the widget advertizes this feature.
|
||||
|
||||
Using a widget
|
||||
''''''''''''''
|
||||
|
||||
A widget's lifecycle has 3 main phases:
|
||||
|
||||
* creation and initialization of the widget instance
|
||||
|
||||
.. function:: openerp.Widget.init(parent)
|
||||
|
||||
initialization method of widgets, synchronous, can be overridden to
|
||||
take more parameters from the widget's creator/parent
|
||||
|
||||
:param parent: the current widget's parent, used to handle automatic
|
||||
destruction and even propagation. Can be ``null`` for
|
||||
the widget to have no parent.
|
||||
:type parent: :class:`~openerp.Widget`
|
||||
|
||||
* DOM injection and startup, this is done by calling one of:
|
||||
|
||||
.. function:: openerp.Widget.appendTo(element)
|
||||
|
||||
Renders the widget and inserts it as the last child of the target, uses
|
||||
`.appendTo()`_
|
||||
|
||||
.. function:: openerp.Widget.prependTo(element)
|
||||
|
||||
Renders the widget and inserts it as the first child of the target, uses
|
||||
`.prependTo()`_
|
||||
|
||||
.. function:: openerp.Widget.insertAfter(element)
|
||||
|
||||
Renders the widget and inserts it as the preceding sibling of the target,
|
||||
uses `.insertAfter()`_
|
||||
|
||||
.. function:: openerp.Widget.insertBefore(element)
|
||||
|
||||
Renders the widget and inserts it as the following sibling of the target,
|
||||
uses `.insertBefore()`_
|
||||
|
||||
All of these methods accept whatever the corresponding jQuery method accepts
|
||||
(CSS selectors, DOM nodes or jQuery objects). They all return a promise and
|
||||
are charged with three tasks:
|
||||
|
||||
* render the widget's root element via
|
||||
:func:`~openerp.Widget.renderElement`
|
||||
* insert the widget's root element in the DOM using whichever jQuery method
|
||||
they match
|
||||
* start the widget, and return the result of starting it
|
||||
|
||||
.. function:: openerp.Widget.start()
|
||||
|
||||
asynchronous startup of the widget once it's been injected in the DOM,
|
||||
generally used to perform asynchronous RPC calls to fetch whatever
|
||||
remote data is necessary for the widget to do its work.
|
||||
|
||||
Must return a deferred_ to indicate when its work is done.
|
||||
|
||||
A widget is *not guaranteed* to work correctly until its
|
||||
:func:`~openerp.Widget.start` method has finished executing. The
|
||||
widget's parent/creator must wait for a widget to be fully started
|
||||
before interacting with it
|
||||
|
||||
:returns: deferred_ object
|
||||
|
||||
* widget destruction and cleanup
|
||||
|
||||
.. function:: openerp.Widget.destroy()
|
||||
|
||||
destroys the widget's children, unbinds its events and removes its root
|
||||
from the DOM. Automatically called when the widget's parent is destroyed,
|
||||
must be called explicitly if the widget has no parents or if it is
|
||||
removed but its parent remains.
|
||||
|
||||
A widget being destroyed is automatically unlinked from its parent.
|
||||
|
||||
Because a widget can be destroyed at any time, widgets also have utility
|
||||
methods to handle this case:
|
||||
|
||||
.. function:: openerp.Widget.alive(deferred[, reject=false])
|
||||
|
||||
A significant issue with RPC and destruction is that an RPC call may take
|
||||
a long time to execute and return while a widget is being destroyed or
|
||||
after it has been destroyed, trying to execute its operations on a widget
|
||||
in a broken/invalid state.
|
||||
|
||||
This is a frequent source of errors or strange behaviors.
|
||||
|
||||
:func:`~openerp.Widget.alive` can be used to wrap an RPC call,
|
||||
ensuring that whatever operations should be executed when the call ends
|
||||
are only executed if the widget is still alive::
|
||||
|
||||
this.alive(this.model.query().all()).then(function (records) {
|
||||
// would break if executed after the widget is destroyed, wrapping
|
||||
// rpc in alive() prevents execution
|
||||
_.each(records, function (record) {
|
||||
self.$el.append(self.format(record));
|
||||
});
|
||||
});
|
||||
|
||||
:param deferred: a deferred_ object to wrap
|
||||
:param reject: by default, if the RPC call returns after the widget has
|
||||
been destroyed the returned deferred_ is left in limbo
|
||||
(neither resolved nor rejected). If ``reject`` is set to
|
||||
``true``, the deferred_ will be rejected instead.
|
||||
:returns: deferred_ object
|
||||
|
||||
.. function:: openerp.Widget.isDestroyed()
|
||||
|
||||
:returns: ``true`` if the widget is being or has been destroyed, ``false``
|
||||
otherwise
|
||||
|
||||
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:
|
||||
|
||||
.. function:: openerp.Widget.$(selector)
|
||||
|
||||
Applies the CSS selector specified as parameter to the widget's
|
||||
DOM root.
|
||||
|
||||
::
|
||||
|
||||
this.$(selector);
|
||||
|
||||
is functionally identical to::
|
||||
|
||||
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
|
||||
''''''''''''''''''''''
|
||||
|
||||
.. function:: openerp.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, :class:`~openerp.Widget` provides an shortcut:
|
||||
|
||||
.. attribute:: openerp.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 the name of a widget's method or a function object. In either case, the
|
||||
``this`` will be set to the widget::
|
||||
|
||||
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\ [#eventsdelegation]_. If the selector is left out
|
||||
(only an event name is specified), the event will be set directly on the
|
||||
widget's DOM root.
|
||||
|
||||
.. function:: openerp.Widget.delegateEvents
|
||||
|
||||
This method is in charge of binding
|
||||
:attr:`~openerp.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
|
||||
:attr:`~openerp.Widget.events` map allows, but the parent
|
||||
should always be called (or :attr:`~openerp.Widget.events`
|
||||
won't be handled correctly).
|
||||
|
||||
.. function:: openerp.Widget.undelegateEvents
|
||||
|
||||
This method is in charge of unbinding
|
||||
:attr:`~openerp.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
|
||||
:func:`~openerp.Widget.delegateEvents`.
|
||||
|
||||
.. note:: this behavior should be compatible with `Backbone's
|
||||
delegateEvents`_, apart from not accepting any argument.
|
||||
|
||||
Subclassing Widget
|
||||
------------------
|
||||
|
||||
:class:`~openerp.Widget` is subclassed in the standard manner (via the
|
||||
:func:`~openerp.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::
|
||||
|
||||
var MyWidget = openerp.Widget.extend({
|
||||
// QWeb template to use when rendering the object
|
||||
template: "MyQWebTemplate",
|
||||
events: {
|
||||
// events binding example
|
||||
'click .my-button': 'handle_click',
|
||||
},
|
||||
|
||||
init: function(parent) {
|
||||
this._super(parent);
|
||||
// insert code to execute before rendering, for object
|
||||
// initialization
|
||||
},
|
||||
start: function() {
|
||||
var sup = this._super();
|
||||
// post-rendering initialization code, at this point
|
||||
|
||||
// allows multiplexing deferred objects
|
||||
return $.when(
|
||||
// propagate asynchronous signal from parent class
|
||||
sup,
|
||||
// return own's asynchronous signal
|
||||
this.rpc(/* … */))
|
||||
}
|
||||
});
|
||||
|
||||
The new class can then be used in the following manner::
|
||||
|
||||
// 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
|
||||
:func:`~openerp.Widget.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 :func:`~openerp.Widget.start()`.
|
||||
|
||||
If for some reason you do not want to call these methods, you will
|
||||
have to first call :func:`~openerp.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::
|
||||
|
||||
my_widget.destroy();
|
||||
|
||||
will unbind all DOM events, remove the widget's content from the DOM and
|
||||
destroy all widget data.
|
||||
|
||||
.. _.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
|
||||
|
||||
.. _deferred: http://api.jquery.com/category/deferred-object/
|
||||
|
||||
RPC
|
||||
===
|
||||
|
@ -15,3 +411,6 @@ RPC
|
|||
|
||||
Web Client
|
||||
==========
|
||||
|
||||
.. [#eventsdelegation] not all DOM events are compatible with events delegation
|
||||
|
||||
|
|
Loading…
Reference in New Issue