zoukankan      html  css  js  c++  java
  • 【odoo14】【好书学习】第十六章、odoo web库(OWL)

    老韩头的开发日常【好书学习】系列

    odoo14引入了名为OWL(Odoo Web Library)的JavaScript框架。OWL是以组件为基础的UI框架,通过QWeb模板作为架构。OWL与传统的组件系统相比更快,并引入了一些新的特性,包括hooks、reactivity、the autoinstantiation of subcomponents等。在这章中,我们将学习如何使用OWL创建可交互的UI元素。我们将从最小的OWL组件开始,然后学习组件的生命周期。最后,我们将创建一个新的form视图下的字段控件。本章将包含如下内容:

    • 创建OWL组件
    • 在OWL组件中管理用户动作
    • TODO: Making OWL components reactive
    • 理解OWL的生命周期
    • 向form视图中添加OWL字段

    注意
    为什么odoo不使用一些比较知名的JavaScript框架,比如React.js、Vue.js呢?你可以在https://github.com/odoo/owl了解到OWL的更多知识。

    技术要求

    OWL组件是以ES6定义的。在这章中,我们将使用ES6语法。但是一些ES6语法在一些老的浏览器中有问题。请确保使用最新的Chrome或者Firefox浏览器。

    创建OWL组件

    本节的目标是学习OWL组件的基础知识。我们将创建最小的OWL组件并把它添加到Odoo的Web客户端中。
    本节,我们会创建一个小的带有文字的水平条。

    准备

    本节,我们将使用my_library模块。

    步骤

    我们将添加一个小的组件,用于展示水平文字的长条。

    1. 添加/my_library/static/src/js/component.js的JavaScript文件并定义新的命名空间:
    odoo.define('my.component', function (require) {
    	"use strict";
    	// Place steps 3, 4, 5 here
    });
    
    1. 添加/my_library/views/tempaltes.xml的项目xml文件并载入js文件:
    <template id="assets_end" inherit_id="web.assets_backend">
    	<xpath expr="." position="inside">
    		<script src="/my_library/static/src/js/component.js" type="text/javascript" />
    	</xpath>
    </template>
    
    1. 在步骤1中创建js文件中新增OWL实用程序
    const { Component } = owl;
    const { xml } = owl.tags;
    
    1. 在步骤1中创建的js文件中添加OWL组件及基础的模板:
    class MyComponent extends Component {
    	static template = xml`
    		<div class="bg-info text-center p-2">
    		<b> Welcome to Odoo </b>
    		</div>`
    }
    
    1. 初始化组件并添加到网页客户端:
    owl.utils.whenReady().then(() => {
    	const app = new MyComponent();
    	app.mount(document.body);
    });
    

    安装/更新my_library模块应用更改。当我们的模块完成安装后,我们可以看到水平条。

    这只是一个简单的组件。它不能响应用户事件,你也不能移除他。

    原理

    步骤1、步骤2,我们添加了js文件并将其添加到后台资源(assets)中。如果想学习assets的内容,可参考14章、CMS网站开发、静态资源管理。
    步骤3,我们通过OWL初始化了一个变量。所有通过OWL实例化的变量在全局变量owl中都是可见的。在我们的例子中,我们使用了OWL实例。首先,我们定义了Component,然后通过owl.tags定义了xml。对于OWL组件而言,Component是核心类,通过扩展它,我们可以创建我们自己的组件。
    步骤4,我们创建了组件,MyComponent。简单起见,我们仅添加了QWeb的模板。如果你观察的比较仔细,可以看到我们使用xml...定义了模板。这就是内联模板(inline template)。然后,你也可以载入QWeb模板。

    小贴士
    内联QWeb模板并不支持翻译及通过继承进行修改。因此,尽量使用单独的QWeb文件。

    步骤5,我们实例化了MyComponent并把它追加到body中。OWL组件是ES6的类,所以你能够通过new创建实体。然后通过mount()函数添加到页面中。我们把我们的代码写在了whenReady()的回调函数中。这可以确保在使用OWL组件前,所有的OWL功能都被加载完成。

    更多

    OWL在odoo中是单独加载的库,就像其他的JS库一样。你能够使用OWL构建其他的项目。

    在OWL组件中管理用户行为

    为了确保用户接口具有可交互性,组件需要响应用户的点击、悬停及表格的提交。
    在本节中,我们将添加一个按钮并处理点击事件。

    准备

    步骤

    本节,我们将添加删除按钮。通过点击删除按钮,可以移除组件。如下:

    1. 更新QWeb模板并添加icon图标。
    static template = xml`
    	<div class="bg-info text-center p-2">
    		<b> Welcome to Odoo </b>
    		<i class="fa fa-close p-1 float-right" style="cursor: pointer;" t-on-click="onRemove"> </i>
    </div>`
    
    1. 添加onRemove处理函数
    class MyComponent extends Component {
    	static template = xml`
    		<div class="bg-info text-center p-2">
    		<b> Welcome to Odoo </b>
    		<i class="fa fa-close p-1 float-right"
    		style="cursor: pointer;"
    		t-on-click="onRemove"> </i>
    		</div>`
    	onRemove(ev) {
    	this.destroy();
    	}
    }
    

    更新模块后,视图如下:

    点击移除的图标后,组件将被删除。当刷新页面后,水平条将再次出现。

    原理

    步骤1,我们添加了移除的图标,并且添加了t-on-click属性。这将绑定点击事件。属性的值就是响应方法的名称。在我们的例子中,onRemove是我们的响应函数。组件的事件语法如下

    t-on-<name of event>="<method name in component>"
    

    比如,当我们想当鼠标移至组件上时进行响应,则可以

    t-on-mouseover="onMouseover"
    

    在添加响应代码后,当我们的鼠标悬停在组件上时,OWL将会调用onMouseover方法。
    步骤2,我们添加了onRemove方法。当我们点击移除图标时调用。在这个方法中,我们调用了destory()方法,这将会移除组件。在destory()函数中,我们接收JavaScript事件对象。destory()是OWL组件的默认方法之一。

    更多

    事件并不局限于DOM事件。你可以添加自己的事件。比如,你触发了名为my-custom-event的方法,你可以使用t-on-my-custom-event捕获事件。

    Making OWL 组件reactive

    OWL是一个强有力的框架,可根据钩子自动更新UI。有了更新钩子,当组件的内部状态发生变化后,组件的UI可自动更新。在本节中,我们将更新展示在组件UI中的内容。

    准备

    步骤

    本节中,我们在文本两边添加了箭头的图标。通过点击箭头,我们可以改变文本内容。如下:

    1. 更新XML的模板。添加两个绑定事件的按钮。可以从列表中动态检索文本。
    static template = xml`
    	<div class="bg-info text-center p-2">
    		<i class="fa fa-arrow-left p-1"
    		style="cursor: pointer;"
    		t-on-click="onPrevious"> </i>
    		<b t-esc="messageList[Math.abs(
    		state.currentIndex%4)]"/>
    		<i class="fa fa-arrow-right p-1"
    		style="cursor: pointer;"
    		t-on-click="onNext"> </i>
    		<i class="fa fa-close p-1 float-right"
    		style="cursor: pointer;"
    		t-on-click="onRemove"> </i>
    	</div>`
    
    1. 在JavaScript文件中引入userState钩子:
    const { Component, useState } = owl;
    
    1. 添加constructor方法并初始化一些变量
    constructor() {
    	super(...arguments);
    	this.messageList = [
    		'Hello World',
    		'Welcome to Odoo',
    		'Odoo is awesome',
    		'You are awesome too'
    	];
    	this.state = useState({ currentIndex: 0 });
    }
    
    1. 在组件类中,添加用户点击事件
    onNext(ev) {
    	this.state.currentIndex++;
    }
    onPrevious(ev) {
    	this.state.currentIndex--;
    }
    

    更新模块,展示如下:

    原理

    步骤1,我们更新了XML模板。我们做了两个改动。我们通过消息的列表渲染文本消息,我们基于在state变量中的currentIndex的值选择消息。我们在文本框两边添加了两个箭头。并通过t-on-click属性绑定了点击事件。
    步骤2,我们引入了useState钩子。将用于处理组件的状态。
    步骤3,我们添加了构造函数(constructor)。当我们创建对象实体时,构造函数将会被调用。在构造函数中,我们添加了消息的列表。然后通过useState钩子新增了state的变量。当state变化的时候,UI也将更新。在我们的例子中,我们在useState钩子中使用了currentIndex。当currentIndex变化了,UI也将随之变化。

    重要信息
    在定义钩子的时候只有一条规则,只有在构造函数中定义了钩子,钩子才会生效。几个其他钩子可以在https://github.com/odoo/owl/ blob/master/doc/reference/hooks.md详细了解。

    步骤4,我们添加了箭头的点击事件。通过点击箭头,我们可以改变组件的状态。因为我们再state上使用了钩子,UI也将随之变化。

    理解OWL的生命周期

    OWL组件有几个方法帮助开发人员创建强有力的组件。本节,我们将了解组件重要的方法及组件的生命周期。本节,我们添加了几个方法,我们将在console中输出日志以了解组件的生命周期。

    准备

    步骤

    1. 在构造函数(constructor)中添加日志
    constructor() {
    	console.log('CALLED:> constructor');
    ...
    
    1. 添加willStart方法
    async willStart() {
    	console.log('CALLED:> willStart');
    }
    
    1. 添加mounted方法
    mounted() {
    	console.log('CALLED:> mounted');
    }
    
    1. 添加willPatch方法
    willPatch() {
    	console.log('CALLED:> willPatch');
    }
    
    1. 添加patched方法
    patched() {
    	console.log('CALLED:> patched');
    }
    
    1. 添加willUnmount()方法
    willUnmount() {
    	console.log('CALLED:> willUnmount');
    }
    

    更新模块后如下图:

    原理

    constructor(): 构造函数,最先被调用。将在这里设置组件的初始状态。
    willStart(): 在构造函数之后,渲染元素之前。这是异步函数,可以进行诸如RPC的异步操作。
    mounted(): 在元素渲染、DOM添加之后调用。
    willPatch(): 在组将的状态发生变化之后调用。这个方法将在元素被根据新的状态重新渲染前调用。例如,当我们点击箭头的时候,该函数被调用。但是这时dom依旧是老的值。
    patched(): 与willPatched()类似。在组件的状态发生变化的时候调用。不同点是,函数在元素基于新的状态渲染后调用。
    willUnmount(): 在元素被移除前调用。
    以上是组件的生命周期,你可以根据实际需要编写相应函数。比如,mounted和willUnmount方法可以用来绑定和解绑事件监听。

    更多

    还有一个重要的方法,他在你使用子组件的使用调用。OWL传递通过props参数传递父组件的状态给子组件,当props变化的时候,willUpdateProps方法将被调用。这是一个异步方法,意味着你可以进行诸如RPC的异步操作。

    为form视图添加OWL字段

    至此,我们学习了OWL的基础知识。现在我们创建一个form视图下的字段展示组件。我们将创建一个颜色部件,通过选择颜色保存数值。
    为了让例子更丰富,我们使用了OWL的先进理念。我们将创建复杂的组件,用户事件,扩展的QWeb模板等。

    准备

    步骤

    1. 在library.book模型中添加颜色的整数型字段
    color = fields.Integer()
    
    1. 在form视图中添加相同的字段
    <field name="color" widget="int_color"/>
    
    1. 在static/src/xml/qweb_tempalte.xml中添加字段的QWeb模板
    <?xml version="1.0" encoding="UTF-8"?>
    <templates>
        <t t-name="OWLColorPill" owl="1">
            <span t-attf-class="o_color_pill o_color_{{props.pill_no}} {{props.active and'active' or ''}}" t-att-data-val="props.pill_no" t-on-click="pillClicked"t-attf-title="This color is used in {{props.book_count or 0 }} books." />
        </t>
        <span t-name="OWLFieldColorPills" owl="1" class="o_int_colorpicker" t-on-color-updated="colorUpdated">
            <t t-foreach="totalColors" t-as='pill_no'>
                <ColorPill t-if="mode === 'edit' or value == pill_no" pill_no='pill_no' active='value == pill_no' book_count="colorGroupData[pill_no]"/>
            </t>
        </span>
    </templates>
    
    1. 在manifest文件中添加QWeb文件
    "qweb": [
    	'static/src/xml/qweb_template.xml',
    ],
    
    1. 现在我们在static/src/scss/field_widget.scss中添加一些SCSS。文件太长了,可直接在https://github.com/ PacktPublishing/Odoo-13-Development-Cookbook-Fourth- Edition/blob/master/Chapter16/05_owl_field/my_library/ static/src/scss/field_widget.scss中查看。
    2. 添加static/src/js/field_widget.js
    odoo.define('my_field_widget', function (require) {
        "use strict";
        const { Component } = owl;
        const AbstractField = require(
        'web.AbstractFieldOwl');
        const fieldRegistry = require(
        'web.field_registry_owl');
    // Place steps 7 and 8 here
    });
    

    步骤7,添加颜色选择组件

    class ColorPill extends Component {
        static template = 'OWLColorPill';
        pillClicked() {
        this.trigger('color-updated', {val:
        this.props.pill_no});
        }
    }
    

    步骤8,扩展AbstractField

    class FieldColor extends AbstractField {
        static supportedFieldTypes = ['integer'];
        static template = 'OWLFieldColorPills';
        static components = { ColorPill };
        // Add methods from step 9 here
        }
    fieldRegistry.add('int_color', FieldColor);
    

    步骤9,添加方法

    constructor(...args) {
        super(...args);
        this.totalColors = Array.from({ length: 10 },
            (_, i) => (i + 1).toString());
    }
    async willStart() {
        this.colorGroupData = {};
        var colorData = await this.rpc({
            model: this.model, method: 'read_group',
            domain: [], fields: ['color'],
            groupBy: ['color'],
        });
        colorData.forEach(res => {
            this.colorGroupData[res.color] =
                res.color_count;
        });
    }
    colorUpdated(ev) {
        this._setValue(ev.detail.val);
    }
    

    步骤10,将js、scss文件添加到后台资源。

    <template id="assets_backend" inherit_id="web.assets_ backend">
        <xpath expr="." position="inside">
            <script src="/my_library/static/src/js
    /component.js" type="text/javascript" />
            <script src="/my_library/static/src/js
    /field_widget.js" type="text/javascript" />
            <link href="/my_library/static/src/scss
    /field_widget.scss" rel="stylesheet" type="text/scss" />
        </xpath>
    </template>
    

    更新模块,如下图

    这个字段看起来就像上一章中的color小部件,但实际的区别在于它的底层。这个新字段是用OWL构建的,而前一个字段是用小部件构建的。

    原理

    步骤1,我们创建了整型字段。
    步骤2,我们添加到form视图。
    步骤3,我们添加了QWeb模板。我们添加了两个模板,一个是颜色的选择,另一个是字段本身。我们使用两个模板是为了更好的理解子组件的概念。仔细查看模板,可以发现我们使用了标签。浙江实例化子组件。在标签中,我们传递active和pill_no属性。这些属性的值将通过子组件参数props获取。同时,t-on-color-updated属性被用来监听子组件的自定义事件。

    重要信息
    odoo14使用widget系统和OWL框架。两个都使用QWeb模板。为了将OWL QWeb模板与传统的QWeb 模板区分,我们需使用owl="1"来标识

    步骤4,添加文件到manifest中。
    步骤5,添加SCSS的样式。
    步骤6,添加JS。我们引入OWL实用程序,并导入AbstractField和fieldRegistry。AbstractField是抽象的OWL组件。他包含所有基础元素。fieldRegistry被用来展示OWL组件。
    步骤7,我们创建了ColorPill组件。组件中template变量是从外部XML 文件中加载的模板的名称。ColorPill组件有pillClicked方法,用于用户在颜色上的点击。在方法内部,当我们在FieldColor组件上使用t-on-color-updated的触发的color-updated事件将会被父组件FieldColor组件捕获。
    步骤8、9,我们创建了FieldColor组件,它是AbstractField的拓展。我们之所以使用AbstractField组件,是因为它具备创建字段小部件的所有要素。我们再一开始使用了components静态变量。当你在模板中使用子组件的时候,你需要通过components静态变量列出所有的组件。我们添加了willStart方法。willStart方法是异步方法,我们可以调用RPC实现获取特定颜色组图书的数量。然后,我们添加了colorUpdated方法,在我们点击是调用。所以,当我们变化了字段的值时。setValue方法将会设置字段的值。注意,由子组件触发的数据,在event参数下的detail属性中是可以访问到的。最后,我们在fieldRegistry中注册了小部件,这意味着今后我们将能够通过表单视图中的小部件属性使用字段。
    在步骤10中,我们将JavaScript和SCSS文件加载到后端资产中。

    本文来自博客园,作者:老韩头的开发日常,转载请注明原文链接:https://www.cnblogs.com/xushuotec/p/14466633.html

  • 相关阅读:
    常见网络攻击手段原理分析
    admins.py总结比较,转
    django的表与表之间的关系详细讲解
    django中的@login_required
    安装指定版本的第三方库
    在django中使用logging
    django的manytomany总结
    manyToManyField理解和用法
    django的多对一,一对一,多对多关系
    python 的os的总结
  • 原文地址:https://www.cnblogs.com/xushuotec/p/14466633.html
Copyright © 2011-2022 走看看