转载:http://book.odoomommy.com/chapter3/README3.html
Widget是所有可视化部件的基类,提供了多种用来管理局部DOM的方法。其功能主要有如下几点:
我们先来看一个最简单的Widget示例:
var MyWidget = Widget.extend({
templte: "MyQwebTemplate",
init: funtion(parent){
this._super(parent);
},
willStart: function(){
},
start: function(){
this.$(".my_button").click();
var promise = this._rpc(...);
return promise;
},
});
var myWidget = new Widget(this);
myWidget.appendTo($(".some-div"));
一个简单的Widget生命周期包含了初始化(init),将要启动(willStart)和启动(start)三个阶段。在开始学习Widget的生命周期之前,我们先来看一下Widget的属性列表。
初始化过程主要是初始化Mixin对象,设置父类对象,并自动绑定属性中以on_或者do_开头的函数。
init: function (parent) {
mixins.PropertiesMixin.init.call(this);
this.setParent(parent);
// Bind on_/do_* methods to this
// We might remove this automatic binding in the future
for (var name in this) {
if(typeof(this[name]) === "function") {
if((/^on_|^do_/).test(name)) {
this[name] = this[name].bind(this);
}
}
}
}
Mixin作用是用来构建对象的父子继承关系,每个对象都可以有一个父类对象和若干的子类对象。当对象被销毁,那些依赖它的子类对象也将一同被销毁并释放出所占用的资源。关于Mixin的更多内容,参见Mixin一章。这里我们只需要记住,Widget在初始化过程中,调用了Mixin的初始化方法(Widget也是Mixin的子类)。
willStart方法在init方法之后,start方法之前被调用,主要处理一些页面渲染必须的一些请求,然后调用start方法,willsStart方法返回一个promise对象,且promise对象被释放之后才可以执行start方法。
willStart: function () {
var proms = [];
if (this.xmlDependencies) {
proms.push.apply(proms, _.map(this.xmlDependencies, function (xmlPath) {
return ajax.loadXML(xmlPath, core.qweb);
}));
}
if (this.jsLibs || this.cssLibs || this.assetLibs) {
proms.push(ajax.loadLibs(this));
}
return Promise.all(proms);
}
正如我们最开始学习odoo开发时,要把xml定义在_mainfest_.py文件中的data节点中一样,xmlDependencies包含的就是Widget启动时需要加载的xml文件路径,其中不包含已经加载的文件。jsLibs、cssLibs和assetsLibs加载的是Widget启动的资源库文件。
start方法在Widget被渲染之后启动,主要任务是绑定动作,触发异步调用等工作。依据惯例,start方法应该返回一个可以被promise.resolve()的对象,用来通知调用者,Widget已经初始化完成。需要注意的是,因为历史原因,很多widget的仍旧选择在start方法而非willStart方法中处理任务,虽然可能那些工作放在willStart中更为合适。
start: function () {
return Promise.resolve();
}
Widget销毁的同时会一并销毁子Widget。
destroy: function () {
mixins.PropertiesMixin.destroy.call(this);
if (this.$el) {
this.$el.remove();
}
}
虽然我们知道了Widget的生命周期包含willStart和Start的方法,但是willStart并非在init方法中启动的,而是在公开方法中被调用,然后启动了start方法。通常这些公开方法是在视图(view)中使用的。
appendTo
渲染当前的Widget并将Widget插入到给出的jQuery对象之后。此方法接受一个参数target,给定的jQuery目标对象。
appendTo: function (target) {
var self = this;
return this._widgetRenderAndInsert(function (t) {
self.$el.appendTo(t);
}, target);
}
prependTo
prependTo: function (target) {
var self = this;
return this._widgetRenderAndInsert(function (t) {
self.$el.prependTo(t);
}, target);
}
同appendTo相反,渲染部件并将部件添加到指定的元素之前。
insertAfter
渲染部件并将部件插入到指定的元素之后。
insertAfter: function (target) {
var self = this;
return this._widgetRenderAndInsert(function (t) {
self.$el.insertAfter(t);
}, target);
}
insertBefore
渲染部件并将部件插入到指定的元素之前。
insertBefore: function (target) {
var self = this;
return this._widgetRenderAndInsert(function (t) {
self.$el.insertBefore(t);
}, target);
}
replace
渲染Widget并替代给出的jQuery对象target。
replace: function (target) {
return this._widgetRenderAndInsert(_.bind(function (t) {
this.$el.replaceAll(t);
}, this), target);
}
上面5个方法的核心,都是在方法内部调用了私有方法_widgetRenderAndInsert,_widgetRenderAndInsert才是真正调用willStart并启动start的方法。
attachTo
将给出的元素附加到DOM文档中,接受一个参数target,jQuery目标对象。
attachTo: function (target) {
var self = this;
this.setElement(target.$el || target);
return this.willStart().then(function () {
return self.start();
});
}
attachTo方法也实现了先启动willStart再调用start的逻辑。
do_hide
隐藏Widget
do_hide: function () {
this.$el.addClass('o_hidden');
}
do_show
显示部件
do_show: function () {
this.$el.removeClass('o_hidden');
}
do_toggle
隐藏或显示部件
do_toggle: function (display) {
if (_.isBoolean(display)) {
display ? this.do_show() : this.do_hide();
} else {
this.$el.hasClass('o_hidden') ? this.do_show() : this.do_hide();
}
}
从上面三个方法的内容可以看出,隐藏和显示的方法是通过给元素添加或删除o_hidden样式实现的。
renderElement
渲染元素。默认使用QWeb框架渲染,指定的template必须是已经定义的模板,此方法会将部件以widget的关键字传入到QWeb中。因此,在template中,用户可以使用widget来引用必要的属性或方法。
renderElement: function () {
var $el;
if (this.template) {
$el = $(core.qweb.render(this.template, {widget: this}).trim());
} else {
$el = this._makeDescriptive();
}
this._replaceElement($el);
}
举例,在我们开发的14.0版本的销售历史价格模块中,需要自定义按钮的图标,我们不能将图标固定在QWeb中,需要动态指定图标样式,因此,可以使用widget变量来获取activity_exception_icon所指定的图标:
setElement
参数:element
使用给定的元素重新设置widget的根元素。此方法会重新委托事件处理,重新绑定子元素,如果存在根元素,会将之前的元素一并替换。
setElement: function (element) {
if (this.$el) {
this._undelegateEvents();
}
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];
this._delegateEvents();
return this;
}
$
如果指定了选择器,则在当前部件的元素内查找符合选择器的元素,否则返回当前部件的元素。
$: function (selector) {
if (selector === undefined) {
return this.$el;
}
return this.$el.find(selector);
}
_delegateEvents
附加Widget的events属性中指定的事件处理函数。
_delegateEvents: function () {
var events = this.events;
if (_.isEmpty(events)) { return; }
for(var key in events) {
if (!events.hasOwnProperty(key)) { continue; }
var method = this.proxy(events[key]);
var match = /^(\S+)(\s+(.*))?$/.exec(key);
var event = match[1];
var selector = match[3];
event += '.widget_events';
if (!selector) {
this.$el.on(event, method);
} else {
this.$el.on(event, selector, method);
}
}
}
细心的同学可能会发现,Odoo官方的模块中,不仅出现了event而且还有另外一种custom_events,而custom_events并没有在Widget的源代码中定义。那么custom_events是何方神圣呢?
custom_events的定义不在Widget中,而是在Widget的父类对象Mixin中,关于Mixin的更多内容参见相关章节,这里只要知道custom_events也同样能实现事件代理的作用就OK了。
_makeDescriptive
根据Widget的声明构建一个潜在的根元素。
_makeDescriptive: function () {
var attrs = _.extend({}, this.attributes || {});
if (this.id) {
attrs.id = this.id;
}
if (this.className) {
attrs['class'] = this.className;
}
var $el = $(document.createElement(this.tagName));
if (!_.isEmpty(attrs)) {
$el.attr(attrs);
}
return $el;
}
_replaceElement
重置Widget的根元素,并用DOM中的新元素取代旧的根元素
_replaceElement: function ($el) {
var $oldel = this.$el;
this.setElement($el);
if ($oldel && !$oldel.is(this.$el)) {
if ($oldel.length > 1) {
$oldel.wrapAll('');
$oldel.parent().replaceWith(this.$el);
} else {
$oldel.replaceWith(this.$el);
}
}
return this;
}
_undelegateEvents
移除Widget所有的事件委托。
_undelegateEvents: function () {
this.$el.off('.widget_events');
}
_widgetRenderAndInsert
渲染Widget的低阶方法,这是一个私有方法,除了widget本身不应该被任何对象调用。
_widgetRenderAndInsert: function (insertion, target) {
var self = this;
return this.willStart().then(function () {
if (self.__parentedDestroyed) {
return;
}
self.renderElement();
insertion(target);
return self.start();
});
}
本章我们了解了Widget的基本构成和它的生命周期,而且了解了它的公开方法和私有方法。我们可以得到这样一个结论,Widget初始化之后,会被添加到DOM中,从而触发willStart方法和start方法,添加到DOM中的方式有两种,一种是通过attachTo方法,另外一种是通过调用了低阶方法_widgetRenderAndInsert的另外4种DOM操作方法。我们还知道了,给Widget添加事件的方式也有两种,一种是通过Widget本身定义的events属性,另外一种是通过custom_events属性。
Widget是Odoo前端构建的核心部件之一,之后我们会继续学习抽象字段,抽象字段是在Widget基础上构建的所有字段部件的基类,它是Widget的一个典型的拓展应用。继续加油吧!