widget类实际上是用户界面的一个重要构建块。几乎用户界面中的所有内容都在小部件(widget)的控制之下。widget类在widget.js中的module web.widget中定义。
简而言之,widget类提供的特性包括:
- 小部件之间的父/子关系(PropertiesMixin)
- *具有安全功能的广泛生命周期管理 *(e.g. 在销毁父级期间自动销毁子窗口小部件)
- 自动渲染qweb模板
- 帮助与外部环境交互的各种实用功能。
一个计数的小部件例子:
1 | var Widget = require('web.Widget'); |
对于本例,假设模板some.template(并且正确加载:模板位于一个文件中,该文件在模块清单中的qweb键中正确定义)如下:
1 | <div t-name="some.template"> |
这个例子说明了小部件类的一些特性,包括事件系统、模板系统、带有初始父参数的构造函数。
小部件的生命周期
与许多组件系统一样,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 | var EditorMenuBar = Widget.extend({ |
Widget.events
事件是事件选择器(由空格分隔的事件名称和可选CSS选择器)到回调的映射。回调可以是小部件方法或函数对象的名称。在这两种情况下,这都将设置为小部件:
1 | 'click p.oe_some_class a': 'some_method', |
选择器用于jquery的事件委托,回调只对与选择器匹配的dom根的后代触发。如果选择器被省略(只指定了一个事件名),那么事件将直接设置在小部件的dom根上。
注意:不鼓励使用内联函数,将来可能会删除它。
1 | Widget.custom_events |
returns: true 如果小部件正在或者已经被销毁,否则false
1 | Widget.$(selector)` |
arguments: selector(string)-CSS选择器
return:jQuery 对象
这个助手方法类似于
Backbone.View.$
Widget.setElement(element)
将小部件的dom根重新设置为提供的元素,还处理重新设置dom根的各种别名以及取消设置和重新设置委托事件。
arguments: element(Element) -一个DOM元素或者jQuery对象设置为小部件的根DOM
在DOM中插入一个小部件
1 | Widget.appendTo(element)` |
所有这些方法都接受相应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的东西(因此,避免使用对父部件的引用)。
- HTML模板/渲染应该使用QWeb,除非非常简单。
- 所有交互组件(向屏幕显示信息或截取DOM事件的组件)必须继承自widget(),并正确实现和使用其API和生命周期。