diff --git a/.bzrignore b/.bzrignore
index b83a43bf1d4..7ae985ce4df 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -9,3 +9,4 @@ RE:^include/
RE:^share/
RE:^man/
RE:^lib/
+logging.cfg
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index d3293b4f2ad..31779648f3b 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -26,7 +26,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.set_default_options(options);
this.dataset = dataset;
this.model = dataset.model;
- this.view_id = view_id;
+ this.view_id = view_id || false;
this.fields_view = {};
this.widgets = {};
this.widgets_counter = 0;
@@ -304,9 +304,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
var def = $.Deferred();
$.when(this.has_been_loaded).then(function() {
if (self.can_be_discarded()) {
- self.dataset.default_get(_.keys(self.fields_view.fields)).then(self.on_record_loaded).then(function() {
+ var keys = _.keys(self.fields_view.fields);
+ if (keys.length) {
+ self.dataset.default_get(keys).then(self.on_record_loaded).then(function() {
+ def.resolve();
+ });
+ } else {
+ self.on_record_loaded({});
def.resolve();
- });
+ }
}
});
return def.promise();
@@ -362,7 +368,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
first_invalid_field.focus();
this.on_invalid();
return false;
- } else if (form_dirty) {
+ } else {
console.log("About to save", values);
if (!this.datarecord.id) {
return this.dataset.create(values, function(r) {
@@ -373,11 +379,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
self.on_saved(r, success);
});
}
- } else {
- setTimeout(function() {
- self.on_saved({ result: true }, success);
- });
- return true;
}
},
do_save_edit: function() {
@@ -960,7 +961,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
return !this.invalid;
},
is_dirty: function() {
- return this.dirty;
+ return this.dirty && !this.readonly;
},
get_on_change_value: function() {
return this.get_value();
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 486dea94671..da5f2e0638e 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -342,22 +342,12 @@
-
-
+
-
+
-
LOGOUT
diff --git a/doc/source/addons.rst b/doc/source/addons.rst
index 856c8b2a6c3..3b749ff529c 100644
--- a/doc/source/addons.rst
+++ b/doc/source/addons.rst
@@ -282,6 +282,18 @@ view managers can correctly communicate with them:
defining e.g. the ``start`` method) or at the instance level (in the
class's ``init``), though you should generally set it on the class.
+Frequent development tasks
+--------------------------
+
+There are a number of tasks which OpenERP Web developers do or will need to
+perform quite regularly. To make these easier, we have written a few guides
+to help you get started:
+
+.. toctree::
+ :maxdepth: 1
+
+ guides/client-action.rst
+
Utility behaviors
-----------------
diff --git a/doc/source/guides/client-action.rst b/doc/source/guides/client-action.rst
new file mode 100644
index 00000000000..2e1177371a2
--- /dev/null
+++ b/doc/source/guides/client-action.rst
@@ -0,0 +1,92 @@
+Creating a new client action
+============================
+
+Client actions are the client-side of OpenERP's "Server Actions": instead of
+allowing for semi-arbitrary code to be executed in the server, they allow
+for execution of client-customized code.
+
+On the server side, a client action is an action of type ``ir.actions.client``,
+which has (at most) two properties: a mandatory ``tag``, which is an arbitrary
+string by which the client will identify the action, and an optional ``params``
+which is simply a map of keys and values sent to the client as-is (this way,
+client actions can be made generic and reused in multiple contexts).
+
+General Structure
+-----------------
+
+In the OpenERP Web code, a client action only requires two pieces of
+information:
+
+* Mapping the action's ``tag`` to an OpenERP Web object
+
+* The OpenERP Web object itself, which must inherit from
+ :js:class:`openerp.web.Widget`
+
+Our example will be the actual code for the widgets client action (a client
+action displaying a ``res.widget`` object, used in the homepage dashboard of
+the web client):
+
+.. code-block:: javascript
+
+ // Registers the object 'openerp.web_dashboard.Widget' to the client
+ // action tag 'board.home.widgets'
+ openerp.web.client_actions.add(
+ 'board.home.widgets', 'openerp.web_dashboard.Widget');
+ // This object inherits from View, but only Widget is required
+ openerp.web_dashboard.Widget = openerp.web.View.extend({
+ template: 'HomeWidget'
+ });
+
+At this point, the generic ``Widget`` lifecycle takes over, the template is
+rendered, inserted in the client DOM, bound on the object's ``$element``
+property and the object is started.
+
+If the client action takes parameters, these parameters are passed in as a
+second positional parameter to the constructor:
+
+.. code-block:: javascript
+
+ init: function (parent, params) {
+ // execute the Widget's init
+ this._super(parent);
+ // board.home.widgets only takes a single param, the identifier of the
+ // res.widget object it should display. Store it for later
+ this.widget_id = params.widget_id;
+ }
+
+More complex initialization (DOM manipulations, RPC requests, ...) should be
+performed in the ``start()`` method.
+
+.. note::
+ As required by ``Widget``'s contract, if ``start`` executes any
+ asynchronous code it should return a ``$.Deferred`` so callers know when
+ it's ready for interaction.
+
+ Although generally speaking client actions are not really interacted with.
+
+.. code-block:: javascript
+
+ start: function () {
+ return $.when(
+ this._super(),
+ // Simply read the res.widget object this action should display
+ new openerp.web.DataSet(this, 'res.widget').read_ids(
+ [this.widget_id], ['title'], this.on_widget_loaded));
+ }
+
+The client action can then behave exactly as it wishes to within its root
+(``this.$element``). In this case, it performs further renderings once its
+widget's content is retrieved:
+
+.. code-block:: javascript
+
+ on_widget_loaded: function (widgets) {
+ var widget = widgets[0];
+ var url = _.sprintf(
+ '/web_dashboard/widgets/content?session_id=%s&widget_id=%d',
+ this.session.session_id, widget.id);
+ this.$element.html(QWeb.render('HomeWidget.content', {
+ widget: widget,
+ url: url
+ }));
+ }