高度封装的前后端框架-odoo回顾(二): 挂件系统的翻译

前言: CSDN应该给我10分,这篇文章翻译了三个小时,翻译果然是个力气活,直接看外文的话20分钟就能结束,翻译好累

Widgets

挂件

The Widget class is really an important building block of the user interface. Pretty much everything in the user interface is under the control of a widget.
挂件类是用户界面的真正重要的一块积木
The Widget class is defined in the module web.Widget, in widget.js.
挂件类定义在web.Widget模块

In short, the features provided by the Widget class include:

长话短说,挂件类提供的特性包括:

  • parent/child relationships between widgets (PropertiesMixin)
  • 挂件们之间的父子关系(属性混合(译者注释:应该指的是面向对象中的继承多态))
  • extensive lifecycle management with safety features (e.g. automatically destroying children widgets during the destruction of a parent)
  • 具有安全特性的广泛生命周期管理(例如当销毁父挂件的时候自动销毁子挂件)
  • automatic rendering with qweb
  • 被QWEB引擎自动渲染(译者注释: qweb引擎由python写成,odoo页面将会被渲染两次,一次是将qweb标签渲染成页面,另外一次就是渲染挂件)
  • various utility functions to help interacting with the outside environment.
  • 多效用函数去协助喝外部环境交互

Here is an example of a basic counter widget:

这里是一个基础计数器挂件的例子:

var Widget = require('web.Widget');

var Counter = Widget.extend({
    template: 'some.template',
    events: {
        'click button': '_onClick',
    },
    init: function (parent, value) {
        this._super(parent);
        this.count = value;
    },
    _onClick: function () {
        this.count++;
        this.$('.val').text(this.count);
    },
});

For this example, assume that the template some.template (and is properly loaded: the template is in a file, which is properly defined in the qweb key in the module manifest) is given by:
在这个例子里面,假定模板some.template被给定以下的值(并且被适当加载: 模板是一个文件,这个文件在module manifest里面由qweb标签定义):

<div t-name="some.template">
    <span class="val"><t t-esc="widget.count"/></span>
    <button>Increment</button>
</div>

This example widget can be used in the following manner:
这个例子挂件可以按照下面的方式使用:

// Create the instance
var counter = new Counter(this, 4);
// Render and insert into DOM
counter.appendTo(".some-div");

This example illustrates a few of the features of the Widget class, including the event system, the template system, the constructor with the initial parent argument.
这个例子说明挂件类的几条特性,包括事件系统,模板系统,初始化父参数的构造器

Widget Lifecycle

挂件的生命周期

Like many component systems, the widget class has a well defined lifecycle.
就像很多组件系统,挂件类有一个定义好的生命周期
The usual lifecycle is the following: init is called, then willStart, then the rendering takes place, then start and finally destroy.
通常生命周期如下:init被调用,然后willStart,然后rendering发生,然后start,最后destroy

Widget.init(parent)

this is the constructor.
这里是构造器
The init method is supposed to initialize the base state of the widget.
这个初始化方法应该初始化挂件的基本状态
It is synchronous and can be overridden to take more parameters from the widget’s creator/parent
它是同步的并且可以被重写,以从挂件的创造者/父亲那里增加更多参数

Arguments: parent (Widget()) – the new widget’s parent, used to handle automatic destruction and event propagation. Can be null for the widget to have no parent.
**参数:**父类(Widget())-新的挂件的父类,习惯于自动处理销毁喝事件传播,对于挂件来说,可以是空以不包含父类

Widget.willStart()

this method will be called once by the framework when a widget is created and in the process of being appended to the DOM.
当挂件被创造和被挂在到DOM树上的时候, 这个方法将只会被框架调用一次
The willStart method is a hook that should return a promise.
这个willStart方法是一个钩子,应该返回一个promise对象
The JS framework will wait for this promise to complete before moving on to the rendering step.
在继续渲染步骤之前,js框架将会等待promise对象完成
Note that at this point, the widget does not have a DOM root element. The willStart hook is mostly useful to perform some asynchronous work, such as fetching data from the server
记住这点,挂件没有一个根DOM元素,willStart钩子对于完成一些同步工作最便利,例如从数据库拿数据

[Rendering]()

(译者注释,用中括号扩起rendring的意思大概是有这个步骤,但是没有该方法,也无法重写)

This step is automatically done by the framework.
这一步由框架自动完成
What happens is that the framework checks if a template key is defined on the widget.
发生的事情是: 框架检查是否有一个模板键值被定义在挂件上
If that is the case, then it will render that template with the widget key bound to the widget in the rendering context (see the example above: we use widget.count in the QWeb template to read the value from the widget).
在这种情况下,然后,系统会依据挂件键渲染模板,在渲染上下文中绑定挂件(译者注:前面说过,挂件会根特定的模板相互绑定,这里就依据挂件的值找到模板,渲染模板,然后绑定到上下文中)
If no template is defined, we read the tagName key and create a corresponding DOM element.
如果没有定义模板,我们会阅读标签名称键并且创建一个相应的DOM元素(译者注释:不理解这句话?)
When the rendering is done, we set the result as the $el property of the widget.
当渲染完成,我们会设置结果为挂件的$el属性(译者注:即,$el是渲染后的dom对象)
After this, we automatically bind all events in the events and custom_events keys.
当完成这些,我们自动绑定所有事件到事件和客户事件键(译者注:大概就是绑定事件的意思)

Widget.start()

when the rendering is complete, the framework will automatically call the start method.
当渲染完成,框架会自动调用start方法
This is useful to perform some specialized post-rendering work.
这对于执行一些专门的后渲染工作很有用

For example, setting up a library.
举例子,建立一个库
Must return a promise to indicate when its work is done.
当他的工作完成时候,必须返回一个promise对象去申明

Returns promise

(译者注:这一段还是没看懂)

Widget.destroy()

This is always the final step in the life of a widget.
这个通常是挂件生命周期的最后一步
When a widget is destroyed, we basically perform all necessary cleanup operations: removing the widget from the component tree, unbinding all events, …
当一个挂件被销毁,我们基本上执行所有必须的清理步骤:从组件树种移除这个挂件,解绑事件…
Automatically called when the widget’s parent is destroyed, must be called explicitly if the widget has no parent or if it is removed but its parent remains.
当挂件的父亲被销毁的时候自动调用,如果挂件没有父亲或者它被移除但是父亲仍然存在的时候,必须手动调用(译者注:查询odoo14源码,发现除了一些test类里面手动调用,其他类很少手动调用,暂时没弄懂手动调用场景,大概很少手动调用)

Note that the willStart and start method are not necessarily called.
主义,willStart和start方法都不一定需要被调用
A widget can be created (the init method will be called) and then destroyed (destroy method) without ever having been appended to the DOM.
一个挂件可以被创建(init方法被调用),然后销毁(destroy方法),全程没有挂载到DOM树上.
If that is the case, the willStart and start will not even be called.
在这个例子里面,willStart和Start方法没有被调用
(译者注释: 总结,创造销毁的时候init,destroy,挂在到DOM树前后willStart,start)

Widget API

  • Widget.tagName
    Used if the widget has no template defined.
    当挂件没有定义模板的时候有用
    Defaults to div, will be used as the tag name to create the DOM element to set as the widget’s DOM root.
    默认是div,将会被用作标签名称取创建DOM元素,设置为挂件的DOM根
    It is possible to further customize this generated DOM root with the following attributes:
    更进一步定制化生成的DOM根元素,可以使用下面的属性

  • Widget.id
    Used to generate an id attribute on the generated DOM root.
    用于生成id属性到生成好的DOM根元素上
    Note that this is rarely needed, and is probably not a good idea if a widget can be used more than once.
    记住这很少被需要,并且这大概不是一个好主意,如果一个挂件被使用超过一次

  • Widget.className
    Used to generate a class attribute on the generated DOM root. Note that it can actually contain more than one css class: ‘some-class other-class’
    用于生成class属性到生成好的DOM根上.记住事实上可以包括超过一个css class:‘some-class other-class’

  • Widget.attributes
    Mapping (object literal) of attribute names to attribute values. Each of these k:v pairs will be set as a DOM attribute on the generated DOM root.
    属性的映射(对象字面量),从名称到值(译者注:就是键值对). 每一个k:v对将会被设置成一个dom属性到生成好的DOM根元素上.

  • Widget.el
    raw DOM element set as root to the widget (only available after the start lifecycle method)
    被设置成挂件根的原始DOM元素

  • Widget.$el
    jQuery wrapper around el. (only available after the start lifecycle method)
    包装了el的jQuery对象

  • Widget.template
    Should be set to the name of a QWeb template.
    应该被设置成QWeb模板的名称
    If set, the template will be rendered after the widget has been initialized but before it has been started.
    如果设置了,模板将会在挂件被初始化之后,start之前被渲染(译者注:即上文提的到[renderering]())
    The root element generated by the template will be set as the DOM root of the widget.
    依据模板生成的根元素将会被设置到小挂件的DOM根

  • Widget.xmlDependencies
    List of paths to xml files that need to be loaded before the widget can be rendered.
    在挂件被渲染之前,需要被加载的xml文件路径列表
    This will not induce loading anything that has already been loaded.
    这不会导致加载任何已经被加载完的东西
    This is useful when you want to load your templates lazily, or if you want to share a widget between the website and the web client interface.
    当你想要懒加载模板,或者你想要在模板和网页客户端界面分享widget的时候,这会很有用

    var EditorMenuBar = Widget.extend({
        xmlDependencies: ['/web_editor/static/src/xml/editor.xml'],
        ...
  • Widget.events
    Events are a mapping of an event selector (an event name and an optional CSS selector separated by a space) to a callback.
    事件是事件选择器的一个映射(一个事件名称和一个可选的CSS选择器,由空格分分隔)
    The callback can be the name of a widget’s method or a function object. In either case, the this will be set to the widget:
    回调可以是挂件的方法名称或者函数对象.在任何情况下,this都会被设置成这个小挂件
    events: {
        'click p.oe_some_class a': 'some_method',
        'change input': function (e) {
            e.stopPropagation();
        }
    },

The selector is used for jQuery’s event delegation, the callback will only be triggered for descendants of the DOM root matching the selector.
选择器用于jQuery事件的代理,回调只能够被DOM根下面符合选择器的后代触发(译者注:终于明白怎么在odoo里面绑定事件了,“事件名 选择器”: “方法名”)
If the selector is left out (only an event name is specified), the event will be set directly on the widget’s DOM root.
如果选择器被忽略(只有事件名被给定),这个事件将会被直接设置到挂件的DOM根上

Note: the use of an inline function is discouraged, and will probably be removed sometimes in the future.
记住: 不推荐使用内联函数,将来可能会被移除

Widget.custom_events

this is almost the same as the events attribute, but the keys are arbitrary strings.
这几乎和事件属性相同,但是键是随意的字符串
They represent business events triggered by some sub widgets.
他们代表业务事件将会被子挂件触发
When an event is triggered, it will ‘bubble up’ the widget tree (see the section on component communication for more details).
当一个事件被触发,它将会"冒泡"到挂件树(有关详细信息,请参阅组件通信部分)

Widget.isDestroyed()

Returns true if the widget is being or has been destroyed, false otherwise
返回true 如果挂件正在/已经被销毁,否则false

Widget.$(selector)

Applies the CSS selector specified as parameter to the widget’s DOM root:
提供CSS选择器为指的的挂件根部

    this.$(selector);

is functionally identical to:
相当于

    this.$el.find(selector);

Arguments: selector (String) – CSS selector
参数: 选择器(字符串)-css选择器
Returns jQuery object
返回: jQuery对象

Note
提醒
this helper method is similar to Backbone.View.$
这个帮助方法和Backbone.View.$类似

Widget.setElement(element)

Re-sets the widget’s DOM root to the provided element, also handles re-setting the various aliases of the DOM root as well as unsetting and re-setting delegated events.
将小部件的 DOM 根重新设置为提供的元素,还处理重新设置 DOM 根的各种别名以及取消设置和重新设置委托事件。

Arguments
参数

        element (Element) – a DOM element or jQuery object to set as the widget’s DOM root
	 元素-一个dom元素或者jQuery对象,用于设置成挂件的DOM根

Inserting a widget in the DOM

向DOM中插入一个挂件

  • Widget.appendTo(element)

    Renders the widget and inserts it as the last child of the target, uses .appendTo()
    渲染挂件并且将它插入为最后一个元素

  • Widget.prependTo(element)

    Renders the widget and inserts it as the first child of the target, uses .prependTo()
    渲染一个挂件并且将它作为第一个子元素插入到目标

  • Widget.insertAfter(element)

    Renders the widget and inserts it as the preceding sibling of the target, uses .insertAfter()
    渲染挂件并且将它作为目标元素的兄弟插入到之后

  • Widget.insertBefore(element)

    Renders the widget and inserts it as the following sibling of the target, uses .insertBefore()
    渲染一个挂件并且将它作为兄弟元素插入到目标之后

All of these methods accept whatever the corresponding jQuery method accepts (CSS selectors, DOM nodes or jQuery objects). They all return a promise and are charged with three tasks:
所有的这些方法接收所有的jQuery方法接收的参数(CSS选择器,DOM节点,jQuery对象)他们都返回一个promis对象并且被下面三个任务改变

rendering the widget’s root element via renderElement()
通过renderElement()方法渲染挂件根元素
inserting the widget’s root element in the DOM using whichever jQuery method they match
插入挂件根元素到DOM用任意一种jQuery方法
starting the widget, and returning the result of starting it
开始挂件,返回开始它的结果

Widget Guidelines

挂件导航

  • Identifiers (id attribute) should be avoided.
  • 身份信息(id属性)应该被避免
    In generic applications and modules, id limits the re-usability of components and tends to make code more brittle.
    在通用的应用和模块,id限制了一个组件的复用能力,并且倾向于是的代码更脆弱
    Most of the time, they can be replaced with nothing, classes or keeping a reference to a DOM node or jQuery element.
    大多数时间, 他们会被这些代替: 空,类,或者保持一个通向DOM节点或者jQuery对象的索引
    If an id is absolutely necessary (because a third-party library requires one), the id should be partially generated using .uniqueId() e.g.:
    如果必须一个id(因为第三方库需要一个),那么id应该使用
    .uniqueId()局部生成
    this.id = _.uniqueId('my-widget-');
  • Avoid predictable/common CSS class names.

  • 避免可预见的/通用的CSS名称(避免重名)
    Class names such as “content” or “navigation” might match the desired meaning/semantics, but it is likely an other developer will have the same need, creating a naming conflict and unintended behavior.
    类名称例如"content"或者"navigation"可能匹配了你想要的语义/特征,但是很可能另外一个开发者也有相似需求,造成命名冲突和意外
    Generic class names should be prefixed with e.g. the name of the component they belong to (creating “informal” namespaces, much as in C or Objective-C).
    通用的类名称应该包含前缀,例如他们属于的组件名称(创建"非正式"命名控件,如同C或者Objective-C)

  • Global selectors should be avoided.
    应该避免全局选择器
    Because a component may be used several times in a single page (an example in Odoo is dashboards), queries should be restricted to a given component’s scope.
    因为在一个单页面中, 一个组件可能被使用数次(在odoo中的一个例子是报表),查询应该被限制在一个给定的范围
    Unfiltered selections such as $(selector) or document.querySelectorAll(selector) will generally lead to unintended or incorrect behavior.
    未过滤的选择器例如|$(selector)或者document.querySelectorAll(selector)将会通常导致想不到的或者不正确的行为
    Odoo Web’s Widget() has an attribute providing its DOM root ($el), and a shortcut to select nodes directly ($()).
    odoo网页的Widget()有一个属性用于提供DOM根($el), 和一个快速选择节点的方法

  • More generally, never assume your components own or controls anything beyond its own personal $el (so, avoid using a reference to the parent widget)
    更通用的,不要假定你的组件拥有或者控制它私人的$el之外的所有一切东西(所以,避免使用索引到父挂件)

  • Html templating/rendering should use QWeb unless absolutely trivial.
    网页模板/渲染应该用QWeb除非绝对的微不足道

  • All interactive components (components displaying information to the screen or intercepting DOM events) must inherit from Widget() and correctly implement and use its API and life cycle.
    一个交互组件(组件展示信息到屏幕,拦截dom事件),必须继承自Widget()并且正确的实现和使用它生命周期的API

  • Make sure to wait for start to be finished before using $el e.g.:
    确保在使用前等待到开始完成

        var Widget = require('web.Widget');

        var AlmostCorrectWidget = Widget.extend({
            start: function () {
                this.$el.hasClass(....) 
                // in theory, $el is already set, but you don't know what the parent will do with it, better call super first
                //在理论上,\$el已经被设置,但是你不知道父亲是否会使用到,最好首先调用super
                return this._super.apply(arguments);
            },
        });

        var IncorrectWidget = Widget.extend({
            start: function () {
                this._super.apply(arguments); 
                // the parent promise is lost, nobody will wait for the start of this widget
                //父promise丢失,没有人能等到不见start结束了
                this.$el.hasClass(....)
            },
        });

        var CorrectWidget = Widget.extend({
            start: function () {
                var self = this;
                return this._super.apply(arguments).then(function() {
                    self.$el.hasClass(....) 
                    // this works, no promise is lost and the code executes in a controlled order: first super, then our code.
                	// 这就生效了,没有promise结束,并且代码在一个受控的顺序中执行,首先调用super,然后是自己的代码
                });
            },
        });
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值