Javascript
Widgets
從web.Widget
輸出class Widget()诅诱,是所有可視組件的基類偶妖,相當(dāng)于mvc的view層昌执,提供一系列的處理頁(yè)面的方法
- 處理多widget之間的繼承奈梳、被繼承關(guān)系
- 提供可擴(kuò)展的生命周期安全管理(當(dāng)父類被destruct時(shí)自動(dòng)將對(duì)應(yīng)子類清除)
- 自動(dòng)使用qweb引擎渲染
- 與backbone兼容的快捷方法
DOM根元素
Widget()負(fù)責(zé)的是根DOM下的一部分widget頁(yè)面,可以通過(guò)兩個(gè)屬性來(lái)獲取widget的DOM:
- Widget.el - widget對(duì)應(yīng)的原始根DOM
- Widget.$el - 使用jQuery選擇的el
有兩種方法來(lái)定義生成DOM根元素:
- Widget.template - qweb的模板名射富,指定模板會(huì)在widget初始化之后、實(shí)際渲染之前渲染盆色。該模板生成的根元素就會(huì)作為對(duì)應(yīng)widget的DOM根元素
- Widget.tagName - 當(dāng)沒(méi)有設(shè)置模板名的時(shí)候使用灰蛙,默認(rèn)是
div
祟剔,它會(huì)被設(shè)置成widget的DOM根元素,可以通過(guò)以下屬性來(lái)自定義對(duì)應(yīng)DOM根元素: - Widget.id - 在DOM根元素上生成一個(gè)id屬性
- Widget.className - 在DOM根元素上生成一個(gè)class屬性
- Widget.attributes - 屬性映射表摩梧,會(huì)自動(dòng)將里面的鍵值對(duì)設(shè)置為根元素的對(duì)應(yīng)屬性
- 當(dāng)設(shè)置了模板名物延,上述參數(shù)將不能使用
- Widget.renderElement() - 可以通過(guò)此方法來(lái)渲染widget的根DOM并設(shè)置,使用的是template或tagName仅父,并調(diào)用setElement() 來(lái)設(shè)置
- 可以覆蓋renderElement() 方法來(lái)實(shí)現(xiàn)自定義的渲染叛薯,但是如果沒(méi)有在里面調(diào)用
__super
的話必須要調(diào)用setElement() 方法
使用widget
widget的生命周期分三個(gè)階段:
- 1.創(chuàng)建并初始化
Widget.init(parent) - widget的初始化方法,可以接收更多的參數(shù)來(lái)覆蓋父級(jí)widget
參數(shù):
parent (Widget()) - 新創(chuàng)建的widget的父級(jí)笙纤,如果某widget沒(méi)有父級(jí)可傳null
- 2.注入DOM并啟動(dòng)案训,通過(guò)調(diào)用以下方法中的一個(gè)來(lái)完成
- Widget.appendTo(element) - 渲染widget并用jquery的appendTo添加到對(duì)應(yīng)DOM最后一個(gè)后元素前
- Widget.prependTo(element) 渲染widget并用jquery的prependTo插入到對(duì)應(yīng)DOM第一個(gè)子元素前
- Widget.insertAfter(element) - 渲染widget并用jquery的insertAfter添加到對(duì)應(yīng)dom之后
- Widget.insertBefore(element) - 渲染widget并用jquery的insertBefore添加到對(duì)應(yīng)dom之前
上述方法接收的參數(shù)和對(duì)應(yīng)jquery方法接收的參數(shù)一致,會(huì)返回一個(gè) deferred延遲執(zhí)行對(duì)象粪糙,并賦予三個(gè)任務(wù):
1.使用renderElement()來(lái)渲染widget的根元素
2.用對(duì)應(yīng)的jquery方法將widget插入到dom中
3.啟動(dòng)widget并將啟動(dòng)的結(jié)果返回
Widget.start()
:當(dāng)widget被插入到DOM之后異步啟動(dòng)强霎,一般用于異步的rpc調(diào)用以獲取遠(yuǎn)端數(shù)據(jù)用于widget中,完成后需要返回一個(gè)deferred對(duì)象蓉冈。在start方法執(zhí)行完成之前widget的功能不一定是完整的城舞。
- 3.銷毀并清除widget對(duì)象
Widget.destroy() - 銷毀它的子類,解綁所有事件寞酿,將它的根元素從DOM移除家夺。當(dāng)父widget被銷毀時(shí)會(huì)自動(dòng)調(diào)用,如果它沒(méi)有父類 或需要將當(dāng)前widget移除但保留父級(jí)widget時(shí) 就必須顯示調(diào)用
與widget銷毀相關(guān)的函數(shù):
-
Widget.alive(deferred[, reject=false])
由于RPC調(diào)用一般比較耗時(shí)伐弹,可能在它執(zhí)行完成的時(shí)候widget已經(jīng)被銷毀了拉馋,這時(shí)在會(huì)在一個(gè)無(wú)效的widget對(duì)象上做操作,alive可用于處理rpc調(diào)用惨好,并保證RPC調(diào)用返回后只在有效的widget上執(zhí)行對(duì)應(yīng)操作:
this.alive(this.model.query().all()).then(function (records) {
// would break if executed after the widget is destroyed, wrapping
// rpc in alive() prevents execution
_.each(records, function (record) {
self.$el.append(self.format(record));
});
});
參數(shù):
deferred - deferred對(duì)象
reject - 默認(rèn)情況下如rpc調(diào)用完成后widget已被銷毀的話對(duì)應(yīng)的deferred對(duì)象只是被封鎖了煌茴,如果設(shè)置為True的話會(huì)將其調(diào)用拒絕
-
Widget.isDestroyed()
如果widget已經(jīng)被銷銷毀了,會(huì)返回true日川,否則返回false
獲取DOM內(nèi)容
由于widget負(fù)責(zé)其DOM元素下的內(nèi)容蔓腐,可以用一個(gè)簡(jiǎn)便的方法去獲取它DOM元素內(nèi)的子片段:
Widget.$(selector)
將css選擇器應(yīng)用到widget的根DOM上
this.$(selector);
相當(dāng)于this.$el.find(selector);
重置DOM根元素
Widget.setElement(element)
將widget的根DOM設(shè)置為指定的DOM,參數(shù)element需為一個(gè)DOM元素或相應(yīng)的jquery對(duì)象
DOM事件處理
widget一般需要在相應(yīng)頁(yè)面內(nèi)響應(yīng)用戶的動(dòng)作龄句,這需要通過(guò)將事件綁定到DOM元素上來(lái)實(shí)現(xiàn)回论。
- Widget.events
事件是一個(gè)事件選擇器(事件名和css選擇器之間以空格分開)- 回調(diào)函數(shù)的映射,回調(diào)函數(shù)可以是widget內(nèi)置函數(shù)或一個(gè)函數(shù)對(duì)象分歇,this表示相應(yīng)widget
events: {
'click p.oe_some_class a': 'some_method',
'change input': function (e) {
e.stopPropagation();
}
},
回調(diào)函數(shù)只會(huì)被對(duì)應(yīng)根DOM的匹配子元素觸發(fā)傀蓉。如果事件選擇器留空的話,該事件是會(huì)被自動(dòng)綁定到widget的根DOM上职抡。
Widget.delegateEvents()
該方法用于將事件綁定到DOM葬燎,當(dāng)設(shè)置好widget的根dom后會(huì)自動(dòng)被調(diào)用,可以通過(guò)重寫它來(lái)設(shè)置比events映射表指定的更為復(fù)雜的事件,但父級(jí)方法必須被顯式調(diào)用萨蚕,否則events不會(huì)被處理。Widget.undelegateEvents()
用于在dom被銷毀或重設(shè)時(shí)解綁events蹄胰,當(dāng)delegateEvents被覆蓋時(shí)岳遥,它也需要進(jìn)行覆蓋。
該方法需要與backbone的delegateEvents相兼容
Widget子類
可以通過(guò)extend來(lái)創(chuàng)建Widget()的子類裕寨,并提供了一些抽象方法和具體方法用于使用浩蓉。
var MyWidget = Widget.extend({
// 渲染對(duì)象時(shí)使用的qweb模板
template: "MyQWebTemplate",
events: {
// 事件綁定示例
'click .my-button': 'handle_click',
},
init: function(parent) {
this._super(parent);
// 在渲染之前執(zhí)行的內(nèi)容
// initialization
},
start: function() {
var sup = this._super();
// 渲染初始化邏輯
// 允許多重deferred對(duì)象
return $.when(
// 從父類獲取異步信號(hào)
sup,
// 返回自己的異步信號(hào)
this.rpc(/* … */))
}
});
##使用
// 創(chuàng)建實(shí)例
var my_widget = new MyWidget(this);
// 渲染并插入到dom
my_widget.appendTo(".some-div");
##銷毀
my_widget.destroy();
開發(fā)規(guī)范
- 避免使用id屬性,用id會(huì)讓部件重用變得很麻煩宾袜∧硌蓿可以使用class、dom節(jié)點(diǎn)或jquery來(lái)替代使用庆猫。如果一定要用的情況下认轨,需要使用_.uniqueId()來(lái)特別聲明:
this.id = _.uniqueId('my-widget-')
- 避免使用很通用的css名如content、navigation月培,以防止命名沖突嘁字。一般以根據(jù)它對(duì)應(yīng)的部分來(lái)命名。
- 避免使用全局選擇器杉畜,因?yàn)槟硞€(gè)組件可能在同個(gè)頁(yè)面重復(fù)使用如儀表板纪蜒,像$(selector) or document.querySelectorAll(selector) 可能會(huì)導(dǎo)致錯(cuò)誤的操作,使用widget對(duì)應(yīng)的
($el)
或$()
來(lái)選擇 - 不要認(rèn)為你的部件擁有或控制它自己的
$el
- html模板和渲染需要合用qweb
- 所有用于顯示信息或處理事件的交互性質(zhì)的組件必須繼承自Widget() 并且使用它的api正確的實(shí)現(xiàn)
RPC
為了進(jìn)行顯示和交互此叠,需要使用rpc與odoo服務(wù)器通信纯续,odoo提供兩種api來(lái)處理:
- 底層基于JSON rpc與模塊對(duì)應(yīng)python片段通信
- 高級(jí)別的直接odoo模塊調(diào)用
所有api都是異步調(diào)用的,所以它們都會(huì)返回deferred延期執(zhí)行對(duì)象
高級(jí)別API 直接調(diào)用odoo模塊
通過(guò)Model()
來(lái)訪問(wèn)odoo的對(duì)象方法灭袁,通過(guò)call方法(來(lái)自web.Model)和 query方法(來(lái)自web.DataModel)來(lái)訪問(wèn)odoo服務(wù)器對(duì)象
-
call()
是直接被映射到odoo服務(wù)端對(duì)象的同名方法的猬错,用法跟odoo模型的api使用只有以下三點(diǎn)區(qū)別: - 交互是異步的,所以在rpc中是返回的deferred延期對(duì)象(它們會(huì)自己處理對(duì)應(yīng)rpc調(diào)用結(jié)果)
- 由于javascript規(guī)范沒(méi)有
__getattr__
和method_missing
特性茸歧,需要指定調(diào)度rpc的方法 - 沒(méi)有池的概念兔魂,當(dāng)需要用的時(shí)候就實(shí)例化model代理,而不是從另一個(gè)(如全局)中獲取
var Users = new Model('res.users');
Users.call('change_password', ['oldpassword', 'newpassword'],
{context: some_context}).then(function (result) {
// do something with change_password result
});
-
query()
方法是搜索(odoo的search和read)的接口举娩,返回一個(gè)Query()
對(duì)象析校,該對(duì)象不可改變但是可以基于它創(chuàng)建新的query對(duì)象,添加新的屬性和方法到原始對(duì)象铜涉。
Users.query(['name', 'login', 'user_email', 'signature'])
.filter([['active', '=', true], ['company_id', '=', main_company]])
.limit(15)
.all().then(function (users) {
// do work with users records
});
query在調(diào)用all()
或first()
方法之前是不會(huì)實(shí)際執(zhí)行的智玻,這兩個(gè)方法每次調(diào)用都會(huì)觸發(fā)一個(gè)rpc請(qǐng)求≤酱可以用來(lái)進(jìn)行實(shí)時(shí)查詢吊奢。
class Model(name)
- Model.name 對(duì)象所綁定的model名
- Model.call(method[, args][, kwargs]) 使用對(duì)應(yīng)參數(shù)調(diào)用當(dāng)前模型的對(duì)應(yīng)方法
參數(shù):
- method (String) 通過(guò)rpc調(diào)用的模型方法
- args (Array<>) 位置匹配的參數(shù)列表
- kwargs (Object<>) 傳遞的關(guān)鍵字參數(shù)
- Model.query(fields)
參數(shù):fields (Array<String>) 搜索時(shí)需要獲取的字段列表
class odoo.web.Query(fields)
第一部分方法是讀取方法,它們使用所調(diào)用對(duì)象的數(shù)據(jù)來(lái)響應(yīng)rpc請(qǐng)求
- odoo.web.Query.all() - 讀取當(dāng)前Query對(duì)象所對(duì)應(yīng)的數(shù)據(jù),返回一個(gè)deferred的數(shù)組
- odoo.web.Query.first() - 讀取當(dāng)前Query對(duì)象對(duì)應(yīng)的第一個(gè) 結(jié)果页滚,如果沒(méi)有結(jié)果返回null召边,有結(jié)果返回deferred對(duì)象
- odoo.web.Query.count() - 獲取當(dāng)前Query對(duì)象可得到的記錄數(shù)量
- odoo.web.Query.group_by(grouping...) - 獲取查詢數(shù)據(jù)的分組,
參數(shù): grouping (Array<String>) - 分組列表
返回:Deferred<Array<odoo.web.QueryGroup>> | null
第二部分方法是設(shè)置方法裹驰,它們會(huì)創(chuàng)建一個(gè)新Query對(duì)象并對(duì)相關(guān)屬性進(jìn)行擴(kuò)展或替換
- odoo.web.Query.context(ctx) 添指定的環(huán)境變量添加到搜索中
- odoo.web.Query.filter(domain) 將指定的domain表達(dá)式添加到查詢條件中隧熙,通過(guò)and與已存在的domain聯(lián)接
- odoo.web.Query.offset(offset) 設(shè)置查詢的起始位置,會(huì)將原有的offset替換
- odoo.web.Query.limit(limit) 設(shè)置查詢的數(shù)據(jù)量幻林,會(huì)將原來(lái)的limit替換
- odoo.web.Query.order_by(fields…) 覆蓋原來(lái)的排序規(guī)則贞盯,像Django的QuerySet.order_by一樣
- 接收多個(gè)排序字段,按重要性高到低排序沪饺,第一個(gè)優(yōu)先級(jí)最高躏敢,字段以字符串提供
- 每個(gè)字段默認(rèn)是按升序,可以在字段前加
-
表示倒序
與django不同整葡,它沒(méi)有用?來(lái)進(jìn)行隨機(jī)亂序和對(duì)關(guān)聯(lián)字段排序方法
**分組聚合 **
odoo有非常強(qiáng)大的分組運(yùn)算功能件余,但是它是遞歸的,并且第N+1層依賴于第n層所提供的數(shù)據(jù)遭居,所以當(dāng)odoo.models.Model.read_group()工作時(shí)這個(gè)api就不是那么直觀蛾扇。
odoo一般用Query()方法來(lái)代替 read_group()方法。
some_query.group_by(['field1', 'field2']).then(function (groups) {
// do things with the fetched groups
});
該方法可以接受一個(gè)字段列表參數(shù)魏滚、也可以不帶參數(shù)執(zhí)行镀首,無(wú)參數(shù)時(shí)直接返回null而不是deferred對(duì)象
當(dāng)分組條件從其他地方來(lái)的時(shí)候,可以通過(guò)兩種方法來(lái)測(cè)試:
- 對(duì)group_by所得到的結(jié)果進(jìn)行檢查:
var groups;
if (groups = some_query.group_by(gby)) {
groups.then(function (gs) {
// groups
});
}
// no groups
- 使用
when()
將返回值強(qiáng)制轉(zhuǎn)換為deferred對(duì)象
$.when(some_query.group_by(gby)).then(function (groups) {
if (!groups) {
// No grouping
} else {
// grouping, even if there are no groups (groups
// itself could be an empty array)
}
});
成功的情況下group_by返回的結(jié)果是一個(gè) QueryGroup()數(shù)組
class odoo.web.QueryGroup()
返回分組的屬性key鼠次,有以下幾種
- grouped_on -- 基于哪個(gè)字段進(jìn)行分組計(jì)算
- value -- 當(dāng)前分組的grouped_on的值
- length -- 分組內(nèi)的記錄數(shù)量
- aggregates -- 分組聚合結(jié)果的 {field: value} 映射
odoo.web.QueryGroup.query([fields...])
相當(dāng)于Model.query() 更哄,但只包含當(dāng)前分組內(nèi)的記錄,返回一個(gè)Query對(duì)象供后續(xù)使用
odoo.web.QueryGroup.subgroups()
返回一個(gè)指向當(dāng)前的子QueryGroup()的數(shù)組的deferred對(duì)象
底層API:RPC調(diào)用python程序
Session()
對(duì)象(通過(guò)web.Session實(shí)例化)的rpc方法提供了一個(gè)低級(jí)別api用來(lái)直接調(diào)用python程序腥寇,該方法接收一個(gè)完整URL成翩、一個(gè)參數(shù)key=>value映射表 作為參數(shù),并將對(duì)應(yīng)獲取的結(jié)果轉(zhuǎn)換成json格式
session.rpc('/web/dataset/resequence', {
model: some_model,
ids: array_of_ids,
offset: 42
}).then(function (result) {
// resequence didn't error out
}, function () {
// an error occured during during call
});
web client
javascript模塊系統(tǒng)
從odoo v8開始使用一套跟requirejs類似的js模塊系統(tǒng)赦役,它有以下優(yōu)點(diǎn):
- 依賴關(guān)系可以保證按順序加載
- 更容易將文件分割成更小的邏輯單元
- 沒(méi)有全局變量
- 很容易檢查依賴關(guān)系麻敌,讓重構(gòu)變得容易很多
缺點(diǎn):
- 如果要通過(guò)odoo交互就必須用模塊系統(tǒng)加載,因?yàn)橛泻芏鄬?duì)象只在模塊系統(tǒng)中能用
- 不支持循環(huán)依賴
在這種模式下掂摔,通過(guò)require來(lái)導(dǎo)入需要的模塊术羔,并且顯示聲明所輸出的對(duì)象。
odoo.define('addon_name.service', function (require) {
var utils = require('web.utils');
var Model = require('web.Model');
// do things with utils and Model
var something_useful = 15;
return {
something_useful: something_useful,
};
});
上面的代碼創(chuàng)建了一個(gè)名叫addon_name.service的模塊乙漓,使用odoo.define函數(shù)定義级历。
odoo.define函數(shù)有兩個(gè)參數(shù):
1.name - 新定義的模塊名
2.function - 在里面定義該模塊實(shí)際包含的內(nèi)容,接收一個(gè)require參數(shù)叭披,如果需要輸出內(nèi)容就需要有對(duì)應(yīng)返回寥殖,require函數(shù)用于獲取依賴的模塊。
用javascript來(lái)導(dǎo)入需要的模塊就聲明輸出內(nèi)容,web客戶端會(huì)自動(dòng)進(jìn)行加載嚼贡。模塊在文件中定義熏纯,一般最好一個(gè)文件對(duì)應(yīng)一個(gè)模塊。模塊可以返回一個(gè)deferred對(duì)象粤策,這樣該模塊只在deferred執(zhí)行后才加載樟澜,而且模塊可以被廢棄,并在控制臺(tái)記錄對(duì)應(yīng)信息:
Missing dependencies - 該模塊不會(huì)出現(xiàn)在頁(yè)面中掐场,可能是javascript文件不在頁(yè)面中或模塊名有錯(cuò)誤
Failed modules - 有javascript錯(cuò)誤
Rejected modules - 模塊返回的是一個(gè)廢棄的deferred
Rejected linked modules - 該模塊依賴于已廢棄的模塊
Non loaded modules - 模塊所依賴的模塊不存在或有錯(cuò)誤
Web client結(jié)構(gòu)
-
framework/
文件夾包含所有底層的模塊 -
web.ajax
用于處理rpc調(diào)用 -
web.core
核心模塊往扔,給出很多有用的對(duì)象如qweb
,_t
-
web.Widget
包含widget類 -
web.Model
抽象化的web.ajax
贩猎,用于直接調(diào)用服務(wù)端模型的方法 -
web.session
如odoo.session
-
web.utils
有用的代碼段 -
web.time
時(shí)間相關(guān)函數(shù) -
views/
文件夾包含所有視圖定義 -
widgets/
包含獨(dú)立的部件 -
js/
文件夾包含一些重要的文件 -
action_manager.js
ActionManager 類 -
boot.js
模塊系統(tǒng)的入口 -
menu.js
頂級(jí)菜單的定義 -
web_client.js
部件WebClient -
view_manager.js
包含ViewManager
還有其他兩個(gè)文件:tour.js
用于tour熊户,compatibility.js
用于將舊系統(tǒng)與新系統(tǒng)兼容,在這個(gè)文件中每個(gè)模塊名被輸出到全局變量odoo中吭服。理論上模塊可以不通過(guò)變量odoo使用嚷堡。
javascript習(xí)慣
- 在模塊最前面聲明所有依賴關(guān)系,一般按模塊名字母順序來(lái)排列
- 在最后聲明所有輸出
- 在模塊開始時(shí)添加
use strict
聲明 - 以合適的名字命名模塊如:
addon_name.description
- 類名首字母大寫如:ActionManager 艇棕,但其他的小寫如web.ajax
- 每個(gè)文件只定義一個(gè)模塊
odoo web client測(cè)試
詳見:http://www.odoo.com/documentation/10.0/reference/javascript.html#testing-in-odoo-web-client
譯自odoo官方文檔:http://www.odoo.com/documentation/10.0/reference/javascript.html 蝌戒,不當(dāng)之處歡迎批評(píng)指正。
內(nèi)容發(fā)布自http://www.reibang.com/u/6fdae8ec06bc沼琉,轉(zhuǎn)載請(qǐng)注明出處