[IMP] create JavaScript howtos
The JavaScript cheatsheet is outdated, we therefore remove it and
replace it by multiple howtos:
- Create a view from scratch
- Extending an existing view
- Create a field from scratch
- Extend an existing field
- Create a client action
There is other subjects to introduce as the web framework is big. Other
future contributions will cover them.
closes odoo/documentation#4016
X-original-commit: 7e4435deb8
Signed-off-by: Dardenne Florent (dafl) <dafl@odoo.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
|
||||
======================
|
||||
Create a client action
|
||||
======================
|
||||
|
||||
A client action triggers an action that is entirely implemented in the client side.
|
||||
One of the benefits of using a client action is the ability to create highly customized interfaces
|
||||
with ease. A client action is typically defined by an OWL component; we can also use the web
|
||||
framework and use services, core components, hooks,...
|
||||
|
||||
#. Create the :ref:`client action <reference/actions/client>`, don't forget to
|
||||
make it accessible.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record model="ir.actions.client" id="my_client_action">
|
||||
<field name="name">My Client Action</field>
|
||||
<field name="tag">my_module.MyClientAction</field>
|
||||
</record>
|
||||
|
||||
#. Create a component that represents the client action.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`my_client_action.js`
|
||||
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
class MyClientAction extends Component {}
|
||||
MyClientAction.template = "my_module.clientaction";
|
||||
|
||||
// remember the tag name we put in the first step
|
||||
registry.category("actions").add("my_module.MyClientAction", MyClientAction);
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`my_client_action.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="awesome_tshirt.clientaction" owl="1">
|
||||
Hello world
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
=================
|
||||
Customize a field
|
||||
=================
|
||||
|
||||
Subclass an existing field component
|
||||
====================================
|
||||
|
||||
Let's take an example where we want to extends the `BooleanField` to create a boolean field
|
||||
displaying "Late!" in red whenever the checkbox is checked.
|
||||
|
||||
#. Create a new widget component extending the desired field component.
|
||||
|
||||
.. code-block:: javascript
|
||||
:caption: :file:`late_order_boolean_field.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { BooleanField } from "@web/views/fields/boolean/boolean_field";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
|
||||
class LateOrderBooleanField extends BooleanField {}
|
||||
LateOrderBooleanField.template = "my_module.LateOrderBooleanField";
|
||||
|
||||
#. Create the field template.
|
||||
|
||||
The component uses a new template with the name `my_module.LateOrderBooleanField`. Create it by
|
||||
inheriting the current template of the `BooleanField`.
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`late_order_boolean_field.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="my_module.LateOrderBooleanField" t-inherit="web.BooleanField" owl="1">
|
||||
<xpath expr="//CheckBox" position="after">
|
||||
<span t-if="props.value" class="text-danger"> Late! </span>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
#. Register the component to the fields registry.
|
||||
|
||||
.. code-block::
|
||||
:caption: :file:`late_order_boolean_field.js`
|
||||
|
||||
registry.category("fields").add("late_boolean", LateOrderBooleanField);
|
||||
|
||||
#. Add the widget in the view arch as an attribute of the field.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="somefield" widget="late_boolean"/>
|
||||
|
||||
Create a new field component
|
||||
============================
|
||||
|
||||
Assume that we want to create a field that displays a simple text in red.
|
||||
|
||||
#. Create a new Owl component representing our new field
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`my_text_field.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export class MyTextField extends Component {
|
||||
|
||||
/**
|
||||
* @param {boolean} newValue
|
||||
*/
|
||||
onChange(newValue) {
|
||||
this.props.update(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
MyTextField.template = xml`
|
||||
<input t-att-id="props.id" class="text-danger" t-att-value="props.value" onChange.bind="onChange" />
|
||||
`;
|
||||
MyTextField.props = {
|
||||
...standardFieldProps,
|
||||
};
|
||||
MyTextField.supportedTypes = ["char"];
|
||||
|
||||
The imported `standardFieldProps` contains the standard props passed by the `View` such as
|
||||
the `update` function to update the value, the `type` of the field in the model, the
|
||||
`readonly` boolean, and others.
|
||||
|
||||
#. In the same file, register the component to the fields registry.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`my_text_field.js`
|
||||
|
||||
registry.category("fields").add("my_text_field", MyTextField);
|
||||
|
||||
This maps the widget name in the arch to its actual component.
|
||||
|
||||
#. Add the widget in the view arch as an attribute of the field.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="somefield" widget="my_text_field"/>
|
||||
@@ -0,0 +1,262 @@
|
||||
=====================
|
||||
Customize a view type
|
||||
=====================
|
||||
|
||||
Subclass 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 in a few steps:
|
||||
|
||||
#. Extend the kanban controller/renderer/model and register it in the view registry.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`custom_kanban_controller.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { KanbanController } from "@web/views/kanban/kanban_controller";
|
||||
import { kanbanView } from "@web/views/kanban/kanban_view";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
// the controller usually contains the Layout and the renderer.
|
||||
class CustomKanbanController extends KanbanController {
|
||||
// Your logic here, override or insert new methods...
|
||||
// if you override setup(), don't forget to call super.setup()
|
||||
}
|
||||
|
||||
CustomKanbanController.template = "my_module.CustomKanbanView";
|
||||
|
||||
export const customKanbanView = {
|
||||
...kanbanView, // contains the default Renderer/Controller/Model
|
||||
Controller: CustomKanbanController,
|
||||
};
|
||||
|
||||
// Register it to the views registry
|
||||
registry.category("views").add("custom_kanban", customeKanbanView);
|
||||
|
||||
In our custom kanban, we defined a new template. We can either inherit the kanban controller
|
||||
template and add our template pieces or we can define a completely new template.
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`custom_kanban_controller.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates>
|
||||
<t t-name="my_module.CustomKanbanView" t-inherit="web.KanbanView" owl="1">
|
||||
<xpath expr="//Layout" position="before">
|
||||
<div>
|
||||
Hello world !
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
#. Use the view with the `js_class` attribute in arch.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<kanban js_class="custom_kanban">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<!--Your comment-->
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
|
||||
The possibilities for extending views are endless. While we have only extended the controller
|
||||
here, you can also extend the renderer to add new buttons, modify how records are presented, or
|
||||
customize the dropdown, as well as extend other components such as the model and `buttonTemplate`.
|
||||
|
||||
Create a new view from scratch
|
||||
==============================
|
||||
|
||||
Creating a new view is an advanced topic. This guide highlight only the essential steps.
|
||||
|
||||
#. Create the controller.
|
||||
|
||||
The primary role of a controller is to facilitate the coordination between various components
|
||||
of a view, such as the Renderer, Model, and Layout.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_controller.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Component, onWillStart, useState} from "@odoo/owl";
|
||||
|
||||
export class BeautifulController extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
|
||||
// The controller create the model and make it reactive so whenever this.model is
|
||||
// accessed and edited then it'll cause a rerendering
|
||||
this.model = useState(
|
||||
new this.props.Model(
|
||||
this.orm,
|
||||
this.props.resModel,
|
||||
this.props.fields,
|
||||
this.props.archInfo,
|
||||
this.props.domain
|
||||
)
|
||||
);
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.model.load();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BeautifulController.template = "my_module.View";
|
||||
BeautifulController.components = { Layout };
|
||||
|
||||
The template of the Controller displays the control panel with Layout and also the
|
||||
renderer.
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`beautiful_controller.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="my_module.View" owl="1">
|
||||
<Layout display="props.display" className="'h-100 overflow-auto'">
|
||||
<t t-component="props.Renderer" records="model.records" propsYouWant="'Hello world'"/>
|
||||
</Layout>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
#. Create the renderer.
|
||||
|
||||
The primary function of a renderer is to generate a visual representation of data by rendering
|
||||
the view that includes records.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_renderer.js`
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
export class BeautifulRenderer extends Component {}
|
||||
|
||||
BeautifulRenderer.template = "my_module.Renderer";
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`beautiful_renderer.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="my_module.Renderer" owl="1">
|
||||
<t t-esc="props.propsYouWant"/>
|
||||
<t t-foreach="props.records" t-as="record" t-key="record.id">
|
||||
// Show records
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
#. Create the model.
|
||||
|
||||
The role of the model is to retrieve and manage all the necessary data in the view.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_model.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { KeepLast } from "@web/core/utils/concurrency";
|
||||
|
||||
export class BeautifulModel {
|
||||
constructor(orm, resModel, fields, archInfo, domain) {
|
||||
this.orm = orm;
|
||||
this.resModel = resModel;
|
||||
// We can access arch information parsed by the beautiful arch parser
|
||||
const { fieldFromTheArch } = archInfo;
|
||||
this.fieldFromTheArch = fieldFromTheArch;
|
||||
this.fields = fields;
|
||||
this.domain = domain;
|
||||
this.keepLast = new KeepLast();
|
||||
}
|
||||
|
||||
async load() {
|
||||
// The keeplast protect against concurrency call
|
||||
const { length, records } = await this.keepLast.add(
|
||||
this.orm.webSearchRead(this.resModel, this.domain, [this.fieldsFromTheArch], {})
|
||||
);
|
||||
this.records = records;
|
||||
this.recordsLength = length;
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
For advanced cases, instead of creating a model from scratch, it is also possible to use
|
||||
`RelationalModel`, which is used by other views.
|
||||
|
||||
#. Create the arch parser.
|
||||
|
||||
The role of the arch parser is to parse the arch view so the view has access to the information.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_arch_parser.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { XMLParser } from "@web/core/utils/xml";
|
||||
|
||||
export class BeautifulArchParser extends XMLParser {
|
||||
parse(arch) {
|
||||
const xmlDoc = this.parseXML(arch);
|
||||
const fieldFromTheArch = xmlDoc.getAttribute("fieldFromTheArch");
|
||||
return {
|
||||
fieldFromTheArch,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#. Create the view and combine all the pieces together, then register the view in the views
|
||||
registry.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_view.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { BeautifulController } from "./beautiful_controller";
|
||||
import { BeautifulArchParser } from "./beautiful_arch_parser";
|
||||
import { BeautifylModel } from "./beautiful_model";
|
||||
import { BeautifulRenderer } from "./beautiful_renderer";
|
||||
|
||||
export const beautifulView = {
|
||||
type: "beautiful",
|
||||
display_name: "Beautiful",
|
||||
icon: "fa fa-picture-o", // the icon that will be displayed in the Layout panel
|
||||
multiRecord: true,
|
||||
Controller: BeautifulController,
|
||||
ArchParser: BeautifulArchParser,
|
||||
Model: BeautifulModel,
|
||||
Renderer: BeautifulRenderer,
|
||||
|
||||
props(genericProps, view) {
|
||||
const { ArchParser } = view;
|
||||
const { arch } = genericProps;
|
||||
const archInfo = new ArchParser().parse(arch);
|
||||
|
||||
return {
|
||||
...genericProps,
|
||||
Model: view.Model,
|
||||
Renderer: view.Renderer,
|
||||
archInfo,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("views").add("beautifulView", beautifulView);
|
||||
|
||||
#. Use the view in an arch.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
...
|
||||
<beautiful fieldFromTheArch="res.partner"/>
|
||||
...
|
||||
Reference in New Issue
Block a user