vuvivian's blog

越努力,越幸运.

  1. 1. 小部件(Widget)
  2. 2. 小部件的生命周期
  3. 3. Widget API
  4. 4. 在DOM中插入一个小部件
  5. 5. 小部件指南
  • 小部件(Widget)

widget类实际上是用户界面的一个重要构建块。几乎用户界面中的所有内容都在小部件(widget)的控制之下。widget类在widget.js中的module web.widget中定义。
简而言之,widget类提供的特性包括:

  • 小部件之间的父/子关系(PropertiesMixin)
  • *具有安全功能的广泛生命周期管理 *(e.g. 在销毁父级期间自动销毁子窗口小部件)
  • 自动渲染qweb模板
  • 帮助与外部环境交互的各种实用功能。
    一个计数的小部件例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
},
});

对于本例,假设模板some.template(并且正确加载:模板位于一个文件中,该文件在模块清单中的qweb键中正确定义)如下:

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

这个例子说明了小部件类的一些特性,包括事件系统、模板系统、带有初始父参数的构造函数。

小部件的生命周期

与许多组件系统一样,widget类有一个定义良好的生命周期。通常的生命周期如下:调用init,然后willStart,然后rendering,然后start,最后destroy。

Widget.init(parent)
这是构造函数。init方法应该初始化小部件的基本状态。它是同步的,可以被重写以从小部件的创建者/父对象获取更多参数。

Arguments : parent(Widget())–新的widget的父级,用于处理自动销毁和事件传播。对于没有父级的小部件,可以为null

Widget.willStart()
当一个小部件被创建并被附加到DOM的过程中,框架将调用这个方法一次。willstart方法是一个钩子,它应该返回一个deferred。JS框架将等待这个deferred完成,然后再继续渲染步骤。注意,此时小部件没有dom根元素。willstart钩子主要用于执行一些异步工作,例如从服务器获取数据。

[Rendering]()
此步骤由框架自动完成。框架会检查小部件上是否定义了template键。如果定义了,那么它将在呈现上下文中使用绑定到小部件的widget键呈现该模板(请参见上面的示例:我们在QWeb模板中使用widget.count来读取小部件的值)。如果没有定义模板,则读取 tagName 键并创建相应的DOM元素。渲染完成后,我们将结果设置为小部件的$el属性。在此之后,我们将自动绑定events和custom_events键中的所有事件。

Widget.start()
渲染完成后,框架将自动调用Start方法。这对于执行一些特殊的后期渲染工作很有用。例如,设置库。
必须返回deferred以指示其工作何时完成。

Returns: deferred 对象

Widget.destroy()
这始终是小部件生命周期中的最后一步。当小部件被销毁时,我们基本上执行所有必要的清理操作:从组件树中删除小部件,取消绑定所有事件,…
当小部件的父级被销毁时自动调用,如果小部件没有父级,或者如果它被删除但父级仍然存在,则必须显式调用。

请注意,不必调用willstart和start方法。可以创建一个小部件(将调用init方法),然后销毁(destroy方法),而不需要附加到DOM。如果是这种情况,将不会调用will start和start。

Widget API

Widget.tagName
如果小部件没有定义模板,则使用。默认为DIV,将用作标记名来创建要设置为小部件的dom根的dom元素。可以使用以下属性进一步自定义生成的dom根目录:

Widget.id
用于在生成的dom根上生成id属性。请注意,这是很少需要的,如果一个小部件可以多次使用,这可能不是一个好主意。

Widget.className
用于在生成的dom根上生成class属性。请注意,它实际上可以包含多个css类:“some-class other-class”

Widget.attributes
属性名到属性值的映射(对象文本)。这些k:v对中的每一个都将被设置为生成的dom根上的dom属性。

Widget.el
将原始dom元素设置为小部件的根(仅在start lifecyle方法之后可用)

Widget.$el
jquery封装的el,(仅在Start Lifecyle方法之后可用)

Widget.template
应设置为QWeb模板的名称。如果设置了,模板将在小部件初始化之后但在其启动之前呈现。模板生成的根元素将被设置为小部件的dom根。

Widget.xmlDependencies
呈现小部件之前需要加载的XML文件的路径列表。这不会导致加载已加载的任何内容。如果您想延迟加载模板,或者想要在网站和Web客户机界面之间共享一个小部件,这很有用。

1
2
3
var EditorMenuBar = Widget.extend({
xmlDependencies: ['/web_editor/static/src/xml/editor.xml'],
...

Widget.events
事件是事件选择器(由空格分隔的事件名称和可选CSS选择器)到回调的映射。回调可以是小部件方法或函数对象的名称。在这两种情况下,这都将设置为小部件:

1
2
3
4
5
    'click p.oe_some_class a': 'some_method',
'change input': function (e) {
e.stopPropagation();
}
},

选择器用于jquery的事件委托,回调只对与选择器匹配的dom根的后代触发。如果选择器被省略(只指定了一个事件名),那么事件将直接设置在小部件的dom根上。
注意:不鼓励使用内联函数,将来可能会删除它。

1
Widget.custom_events

returns: true 如果小部件正在或者已经被销毁,否则false

1
2
3
4
5
Widget.$(selector)`
将指定为参数的CSS选择器应用于小部件的dom根:
`this.$(selector);`
功能上与以下相同:
`this.$el.find(selector);

arguments: selector(string)-CSS选择器
return:jQuery 对象

这个助手方法类似于Backbone.View.$

Widget.setElement(element)
将小部件的dom根重新设置为提供的元素,还处理重新设置dom根的各种别名以及取消设置和重新设置委托事件。

arguments: element(Element) -一个DOM元素或者jQuery对象设置为小部件的根DOM

在DOM中插入一个小部件

1
2
3
4
5
6
7
8
Widget.appendTo(element)`
渲染小部件并将它作为子元素插入到目标元素后面,使用`.appentTo()
Widget.prependTo(element)`
渲染小部件并将它作为子元素插入到目标元素前面,使用`.prependTo()
Widget.insertAfter(element)`
渲染小部件并将它作为目标元素的前一个同级插入,使用`.insertAfter()
Widget.insertBefore(element)`
渲染小部件并将其作为目标的后一个同级插入,使用`.insertBefore()

所有这些方法都接受相应jquery方法接受的任何内容(css选择器、dom节点或jquery对象)。他们都会返回一个 deferred,并承担三个任务:

  • 通过以下方式呈现小部件的根元素:
    renderElement()
  • 使用jquery在DOM中插入小部件的根元素
    匹配的方法
  • 启动小部件并返回启动结果

小部件指南

  • 在一般应用和模块中中,标识 (id 属性)应该避免使用,ID限制了组件的可重用性,并使代码更加脆弱。大多数情况下,它们可以替换为Nothing、Classes或保留对dom节点或jquery元素的引用。
    如果ID是绝对必要的(因为第三方库需要一个),则应使用_.uniqueId()部分生成ID,例如:
    this.id = _.uniqueId('my-widget-');
  • 避免使用可预测/通用的CSS类名。类名称(如“content”或“navigation”)可能与所需的含义/语义匹配,但其他开发人员可能也有相同的需求,从而造成命名冲突和意外行为。通用类名的前缀应该是它们所属组件的名称(创建“非正式”名称空间,就像在C或Objective-C中那样)。
  • 应避免使用全局选择器。因为一个组件可以在一个页面中多次使用(ODoo中的一个例子是仪表板),所以查询应该限制在给定组件的范围内。未筛选的选择(如$(selector)document.querySelectorAll(selector))通常会导致意外或错误的行为。odoo web的widget()有一个提供dom根(el)的属性,以及直接选择节点的快捷方式(
  • 更一般地说,不要假设您的组件拥有或控制任何超出其个人$el的东西(因此,避免使用对父部件的引用)。
  • HTML模板/渲染应该使用QWeb,除非非常简单。
  • 所有交互组件(向屏幕显示信息或截取DOM事件的组件)必须继承自widget(),并正确实现和使用其API和生命周期。
本文最后更新于 天前,文中所描述的信息可能已发生改变