summaryrefslogtreecommitdiff
path: root/doc/reference/javascript_cheatsheet.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/reference/javascript_cheatsheet.rst')
-rw-r--r--doc/reference/javascript_cheatsheet.rst324
1 files changed, 324 insertions, 0 deletions
diff --git a/doc/reference/javascript_cheatsheet.rst b/doc/reference/javascript_cheatsheet.rst
new file mode 100644
index 00000000000..447405ad711
--- /dev/null
+++ b/doc/reference/javascript_cheatsheet.rst
@@ -0,0 +1,324 @@
+:banner: banners/javascript.jpg
+
+=====================
+Javascript Cheatsheet
+=====================
+
+There are many ways to solve a problem in JavaScript, and in Odoo. However, the
+Odoo framework was designed to be extensible (this is a pretty big constraint),
+and some common problems have a nice standard solution. The standard solution
+has probably the advantage of being easy to understand for an odoo developers,
+and will probably keep working when Odoo is modified.
+
+This document tries to explain the way one could solve some of these issues.
+Note that this is not a reference. This is just a random collection of recipes,
+or explanations on how to proceed in some cases.
+
+
+First of all, remember that the first rule of customizing odoo with JS is:
+*try to do it in python*. This may seem strange, but the python framework is
+quite extensible, and many behaviours can be done simply with a touch of xml or
+python. This has usually a lower cost of maintenance than working with JS:
+
+- the JS framework tends to change more, so JS code needs to be more frequently
+ updated
+- it is often more difficult to implement a customized behaviour if it needs to
+ communicate with the server and properly integrate with the javascript framework.
+ There are many small details taken care by the framework that customized code
+ needs to replicate. For example, responsiveness, or updating the url, or
+ displaying data without flickering.
+
+
+.. note:: This document does not really explain any concepts. This is more a
+ cookbook. For more details, please consult the javascript reference
+ page (see :doc:`javascript_reference`)
+
+Creating a new field widget
+===========================
+
+This is probably a really common usecase: we want to display some information in
+a form view in a really specific (maybe business dependent) way. For example,
+assume that we want to change the text color depending on some business condition.
+
+This can be done in three steps: creating a new widget, registering it in the
+field registry, then adding the widget to the field in the form view
+
+- creating a new widget:
+ This can be done by extending a widget:
+
+ .. code-block:: javascript
+
+ var FieldChar = require('web.basic_fields').FieldChar;
+
+ var CustomFieldChar = Fieldchar.extend({
+ renderReadonly: function () {
+ // implement some custom logic here
+ },
+ });
+
+- registering it in the field registry:
+ The web client needs to know the mapping between a widget name and its
+ actual class. This is done by a registry:
+
+ .. code-block:: javascript
+
+ var fieldRegistry = require('web.field_registry');
+
+ fieldRegistry.add('my-custom-field', CustomFieldChar);
+
+- adding the widget in the form view
+ .. code-block:: xml
+
+ <field name="somefield" widget="my-custom-field"/>
+
+ Note that only the form, list and kanban views use this field widgets registry.
+ These views are tightly integrated, because the list and kanban views can
+ appear inside a form view).
+
+Modifying an existing field widget
+==================================
+
+Another use case is that we want to modify an existing field widget. For
+example, the voip addon in odoo need to modify the FieldPhone widget to add the
+possibility to easily call the given number on voip. This is done by *including*
+the FieldPhone widget, so there is no need to change any existing form view.
+
+Field Widgets (instances of (subclass of) AbstractField) are like every other
+widgets, so they can be monkey patched. This looks like this:
+
+.. code-block:: javascript
+
+ var basic_fields = require('web.basic_fields');
+ var Phone = basic_fields.FieldPhone;
+
+ Phone.include({
+ events: _.extend({}, Phone.prototype.events, {
+ 'click': '_onClick',
+ }),
+
+ _onClick: function (e) {
+ if (this.mode === 'readonly') {
+ e.preventDefault();
+ var phoneNumber = this.value;
+ // call the number on voip...
+ }
+ },
+ });
+
+Note that there is no need to add the widget to the registry, since it is already
+registered.
+
+Modifying a main widget from the interface
+==========================================
+
+Another common usecase is the need to customize some elements from the user
+interface. For example, adding a message in the home menu. The usual process
+in this case is again to *include* the widget. This is the only way to do it,
+since there are no registries for those widgets.
+
+This is usually done with code looking like this:
+
+.. code-block:: javascript
+
+ var AppSwitcher = require('web_enterprise.AppSwitcher');
+
+ AppSwitcher.include({
+ render: function () {
+ this._super();
+ // do something else here...
+ },
+ });
+
+
+Adding a client action
+======================
+
+A client action is a widget which will control the part of the screen below the
+menu bar. It can have a control panel, if necessary. Defining a client action
+can be done in two steps: implementing a new widget, and registering the widget
+in the action registry.
+
+- Implementing a new client action:
+ This is done by creating a widget:
+
+ .. code-block:: javascript
+
+ var ControlPanelMixin = require('web.ControlPanelMixin');
+ var Widget = require('web.Widget');
+
+ var ClientAction = Widget.extend(ControlPanelMixin, {
+ ...
+ });
+
+ Do not add the controlpanel mixin if you do not need it. Note that some
+ code is needed to interact with the control panel (via the
+ ``update_control_panel`` method given by the mixin).
+
+- Registering the client action:
+ As usual, we need to make the web client aware of the mapping between
+ client actions and the actual class:
+
+ .. code-block:: javascript
+
+ var core = require('web.core');
+
+ core.action_registry.add('my-custom-action', ClientAction);
+
+
+ Then, to use the client action in the web client, we need to create a client
+ action record (a record of the model ``ir.actions.client``) with the proper
+ ``tag`` attribute:
+
+ .. code-block:: xml
+
+ <record id="my_client_action" model="ir.actions.client">
+ <field name="name">Some Name</field>
+ <field name="tag">my-custom-action</field>
+ </record>
+
+Creating a new view (from scratch)
+==================================
+
+Creating a new view is a more advanced topic. This cheatsheet will only
+highlight the steps that will probably need to be done (in no particular order):
+
+- adding a new view type to the field ``type`` of ``ir.ui.view``::
+
+ class View(models.Model):
+ _inherit = 'ir.ui.view'
+
+ type = fields.Selection(selection_add=[('map', "Map")])
+
+- adding the new view type to the field ``view_mode`` of ``ir.actions.act_window.view``::
+
+ class ActWindowView(models.Model):
+ _inherit = 'ir.actions.act_window.view'
+
+ view_mode = fields.Selection(selection_add=[('map', "Map")])
+
+
+- creating the four main pieces which makes a view (in JavaScript):
+ we need a view (a subclass of ``AbstractView``, this is the factory), a
+ renderer (from ``AbstractRenderer``), a controller (from ``AbstractController``)
+ and a model (from ``AbstractModel``). I suggest starting by simply
+ extending the superclasses:
+
+ .. code-block:: javascript
+
+ var AbstractController = require('web.AbstractController');
+ var AbstractModel = require('web.AbstractModel');
+ var AbstractRenderer = require('web.AbstractRenderer');
+ var AbstractView = require('web.AbstractView');
+
+ var MapController = AbstractController.extend({});
+ var MapRenderer = AbstractRenderer.extend({});
+ var MapModel = AbstractModel.extend({});
+
+ var MapView = AbstractView.extend({
+ config: {
+ Model: MapModel,
+ Controller: MapController,
+ Renderer: MapRenderer,
+ },
+ });
+
+- adding the view to the registry:
+ As usual, the mapping between a view type and the actual class needs to be
+ updated:
+
+ .. code-block:: javascript
+
+ var viewRegistry = require('web.view_registry');
+
+ viewRegistry.add('map', MapView);
+
+- implementing the four main classes:
+ The ``View`` class needs to parse the ``arch`` field and setup the other
+ three classes. The ``Renderer`` is in charge of representing the data in
+ the user interface, the ``Model`` is supposed to talk to the server, to
+ load data and process it. And the ``Controller`` is there to coordinate,
+ to talk to the web client, ...
+
+- creating some views in the database:
+ .. code-block:: xml
+
+ <record id="customer_map_view" model="ir.ui.view">
+ <field name="name">customer.map.view</field>
+ <field name="model">res.partner</field>
+ <field name="arch" type="xml">
+ <map latitude="partner_latitude" longitude="partner_longitude">
+ <field name="name"/>
+ </map>
+ </field>
+ </record>
+
+
+Customizing an existing view
+============================
+
+Assume we need to create a custom version of a generic view. For example, a
+kanban view with some extra *ribbon-like* widget on top (to display some
+specific custom information). In that case, this can be done with 3 steps:
+extend the kanban view (which also probably mean extending controllers/renderers
+and/or models), then registering the view in the view registry, and finally,
+using the view in the kanban arch (a specific example is the helpdesk dashboard).
+
+- extending a view:
+ Here is what it could look like:
+
+ .. code-block:: javascript
+
+ var HelpdeskDashboardRenderer = KanbanRenderer.extend({
+ ...
+ });
+
+ var HelpdeskDashboardModel = KanbanModel.extend({
+ ...
+ });
+
+ var HelpdeskDashboardController = KanbanController.extend({
+ ...
+ });
+
+ var HelpdeskDashboardView = KanbanView.extend({
+ config: _.extend({}, KanbanView.prototype.config, {
+ Model: HelpdeskDashboardModel,
+ Renderer: HelpdeskDashboardRenderer,
+ Controller: HelpdeskDashboardController,
+ }),
+ });
+
+- adding it to the view registry:
+ as usual, we need to inform the web client of the mapping between the name
+ of the views and the actual class.
+
+ .. code-block:: javascript
+
+ var viewRegistry = require('web.view_registry');
+ viewRegistry.add('helpdesk_dashboard', HelpdeskDashboardView);
+
+- using it in an actual view:
+ we now need to inform the web client that a specific ``ir.ui.view`` needs to
+ use our new class. Note that this is a web client specific concern. From
+ the point of view of the server, we still have a kanban view. The proper
+ way to do this is by using a special attribute ``js_class`` (which will be
+ renamed someday into ``widget``, because this is really not a good name) on
+ the root node of the arch:
+
+ .. code-block:: xml
+
+ <record id="helpdesk_team_view_kanban" model="ir.ui.view" >
+ ...
+ <field name="arch" type="xml">
+ <kanban js_class="helpdesk_dashboard">
+ ...
+ </kanban>
+ </field>
+ </record>
+
+.. note::
+
+ Note: you can change the way the view interprets the arch structure. However,
+ from the server point of view, this is still a view of the same base type,
+ subjected to the same rules (rng validation, for example). So, your views still
+ need to have a valid arch field. \ No newline at end of file