Готовые рецепты для JavaScript - Odoo 11.0

Есть много способов решить проблему как в JavaScript, так и в Odoo. Тем не менее, фреймворк Odoo был разработан для того чтобы быть расширяемым (это довольно большое ограничение), и некоторые распространенные проблемы имеют хорошее стандартное решение. Стандартное решение имеет преимущество в том, что его легко понять разработчикам odoo, и, скорее всего, оно будет работать, когда Odoo изменится в процессе развития.

Этот документ пытается объяснить, как можно решить некоторые из этих проблем. Обратите внимание, что это не справка. Это просто случайная коллекция рецептов или объяснения того, как действовать в некоторых случаях.

Прежде всего, помните, что первое правило настройки odoo с помощью JS: попробуйте сделать это в python. Это может показаться странным, но среда Python достаточно расширяема, и многие варианты поведения могут быть реализованы просто с помощью xml или python. Как правило, такое решение имеет более низкую стоимость обслуживания, чем работа с JS:

  • сам JS фреймворк имеет тенденцию меняться больше,что приводит к необходимости чаще менять JS код
  • часто бывает сложнее реализовать настраиваемое поведение, если ему необходимо взаимодействовать с сервером и должным образом интегрироваться во фреймворк javascript. Фреймворк требует много мелких деталей, которые нужно реплицировать в коде. Например, отзывчивость, или обновление URL, или отображение данных без мерцания.

Создание нового поля виджета

Скорее всего это очень распространенный сценарий использования: мы хотим отображать информацию в представлении Form действительно особенным(возможно, бизнес-зависимым) способом. Например, мы хотим изменить цвет текста в зависимости от бизнес-условия.

Это можно сделать в три этапа: создать новый виджет, зарегистрировать его в реестре полей, затем добавить виджет в поле в представления Form

  • создание нового виджета:

    Это можно сделать, расширив виджет:

    var FieldChar = require('web.basic_fields').FieldChar;
    
    var CustomFieldChar = Fieldchar.extend({
        renderReadonly: function () {
            // implement some custom logic here
        },
    });
    
  • регистрация в реестре полей:

    Веб-клиент должен знать как сопоставить имя виджета и его класс. Это делается реестром:

    var fieldRegistry = require('web.field_registry');
    
    fieldRegistry.add('my-custom-field', CustomFieldChar);
    
  • добавляем виджет в представление Form
    <field name="somefield" widget="my-custom-field"/>
    

    Обратите внимание, что только поля представлений Form, List и Kanban используют это поле реестра виджетов. Эти представления тесно интегрированы, поскольку представления List и Kanban могут отображаться внутри представления Form).

Изменение существующего виджета поля

Другой вариант использования - мы хотим изменить существующий виджет поля. Например, аддон voip в odoo должен изменить виджет FieldPhone, чтобы добавить возможность легко позвонить по указанному номеру в voip. Это делается путем including виджета FieldPhone, поэтому нет необходимости изменять какое-либо существующее представление Form.

Виджеты полей (экземпляры (подкласса) AbstractField), как и все остальные виджеты, могут быть «пропатчены по обезьяньи». Это выглядит так:

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...
        }
    },
});

Обратите внимание, что нет необходимости добавлять виджет в реестр, так как он уже зарегистрирован.

Модификация основного виджета из интерфейса

Другим распространенным вариантом использования является необходимость настройки некоторых элементов из пользовательского интерфейса. Например, добавить сообщение в домашнее меню. Обычный процесс в этом случае снова включает include виджета. Это единственный способ сделать это, поскольку для этих виджетов нет реестров.

Обычно это делается с помощью подобного кода:

var AppSwitcher = require('web_enterprise.AppSwitcher');

AppSwitcher.include({
    render: function () {
        this._super();
        // do something else here...
    },
});

Добавление действия клиента

Действие клиента - это виджет, который будет управлять частью экрана под строкой меню. При необходимости может иметь панель управления. Определение действия клиента может быть выполнено в два этапа: внедрение нового виджета и регистрация виджета в реестре действий.

  • Реализация нового клиентского действия:

    Это делается путем создания виджета:

    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).

  • Регистрация действия клиента:

    Как обычно, нам нужно, чтобы веб-клиент знал о сопоставлении действий клиента и нужного класса:

    var core = require('web.core');
    
    core.action_registry.add('my-custom-action', ClientAction);
    

    Затем, чтобы использовать действие клиента в веб-клиенте, нам нужно создать запись действия клиента (запись модели ir.actions.client) с соответствующим атрибутом tag:

    <record id="my_client_action" model="ir.actions.client">
        <field name="name">Some Name</field>
        <field name="tag">my-custom-action</field>
    </record>
    

Создание нового представления (с нуля)

Создание нового представления - более сложная тема. Этот рецепт содержит только те шаги, которые нужно будет выполнить (порядок выполнения не обязательно такой же):

  • добавление нового типа представления в поле type в модель``ir.ui.view``:

    class View(models.Model):
        _inherit = 'ir.ui.view'
    
        type = fields.Selection(selection_add=[('map', "Map")])
    
  • добавление нового типа представления в поле view_mode модели ir.actions.act_window.view:

    class ActWindowView(models.Model):
        _inherit = 'ir.actions.act_window.view'
    
        view_mode = fields.Selection(selection_add=[('map', "Map")])
    
  • Создание четырех основных частей, из которых создается представление (в JavaScript):

    нам нужно представление (субкласс AbstractView, это фабрика), рендерер (от AbstractRenderer), контроллер (от AbstractController) и модель (from AbstractModel). Я предлагаю начать с простого расширения суперклассов:

    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,
        },
    });
    
  • добавление представления в реестр:

    Как обычно, сопоставление между типом представления и его классом должно быть обновлено:

    var viewRegistry = require('web.view_registry');
    
    viewRegistry.add('map', MapView);
    
  • реализация четырех основных классов:
    Классу View необходимо проанализировать поле arch и настроить остальные три класса. Renderer отвечает за представление данных в пользовательском интерфейсе, Model должен общаться с сервером, загружать данные и обрабатывать их. И Controller предназначен для координации, общения с веб-клиентом, …
  • создание представлений в базе данных:
    <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>
    

Кастомизация существующего представления

Предположим, нам нужно создать пользовательскую версию универсального представления. Например, представление Kanban с дополнительным * ribbon-like* виджетом сверху (для отображения пользовательской информации). В этом случае это можно сделать за 3 шага: расширить представление Kanban (что также, вероятно, означает расширение контроллеров/рендеров и/или моделей), затем зарегистрировать представление в реестре представлений и, наконец, использовать представление в атрибуте arch. (конкретный пример - панель инструментов службы поддержки).

  • расширение представления:

    Вот как это может выглядеть:

    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,
        }),
    });
    
  • добавление в реестр представлений:

    как обычно, мы должны информировать веб-клиента о сопоставлении имени представления и его класса.

    var viewRegistry = require('web.view_registry');
    viewRegistry.add('helpdesk_dashboard', HelpdeskDashboardView);
    
  • используем в действующем представлении:

    Теперь нам нужно сообщить веб-клиенту, что конкретному ir.ui.view необходимо использовать наш новый класс. Обратите внимание, что это особая потребность веб-клиента. С точки зрения сервера у нас все еще есть представление Kanban. Правильный способ сделать это - использовать специальный атрибут js_class (который когда-нибудь будет переименован в widget, потому что это действительно не очень хорошее имя) в корневом узле arch:

    <record id="helpdesk_team_view_kanban" model="ir.ui.view" >
        ...
        <field name="arch" type="xml">
            <kanban js_class="helpdesk_dashboard">
                ...
            </kanban>
        </field>
    </record>