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 + })); + }