-----------
更新日期
15:17 2016-02-16 星期二
-----------
* 用到的js庫
我們可以打開 addons/web/views/webclient_template.xml
看到如下:
<template id="web.assets_common">
<script type="text/javascript" src="/web/static/lib/es5-shim/es5-shim.min.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore/underscore.js"></script>
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
<script type="text/javascript" src="/web/static/lib/datejs/globalization/en-US.js"></script>
<script type="text/javascript" src="/web/static/lib/spinjs/spin.js"></script>
<!-- jQuery stuff -->
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.blockUI/jquery.blockUI.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.hotkeys/jquery.hotkeys.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.placeholder/jquery.placeholder.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.timeago/jquery.timeago.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.form/jquery.form.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script>
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
<script type="text/javascript" src="/web/static/src/js/tour.js"></script>
<link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css"/>
</template>
# es5-shim 給傻逼瀏覽器做兼容性,使得傻逼瀏覽器可以支持一些 es5 的 api
# spinjs ajax異步時,等待出現一個輪的圖片
# datejs 日期處理js
# jQuery庫,這是經典庫了 http://t.mb5u.com/jquery/ 1.8.3
# jquery.blockUI 提示窗口
# jquery.hotkeys 鍵盤js處理
# jquery.placeholder 實現文本框顯示描述文字
# jquery.timeago 時間格式
# jquery.form 表單處理
# jquery.ba-bbq
# underscore庫,彌補了jQuery沒有實現的地方 文檔http://www.css88.com/doc/underscore1.6.0/
所有的功能都封裝在名為"_"命名空間內
常用_.each()來代替javascript中的循環
_.range()產生一個范圍的數列
function sumTotal(){
var x=0;
_.each(_.range(1,101),function(i){
x+=i;
});
console.log("Result",x);
}
*jQuery 簡單操作
#選擇器
@ $("input") 選擇特定的HTML元素
@ $("#content") 選擇指定的id的HTML元素
@ $(".title") 選擇明確的css樣式類的所有元素
@ $("span.title") 組合選擇元素,可以更精確選擇元素
# 事件
@ $("button").click(function){
console.log("someone clicked on the button")
}); // 按鈕上的單點事件
# 修改DOM
@ $(".main_content").html('<div style="color:white">Hello world!' </div>) <!--替換內容-->
@ $(".main_content").append('<div style="color:white">Hello world again!' </div>) <!--追加內容到后面-->
@ $(".main_content").prepend('<div style="color:white">Hello world front!' </div>) <!--追加內容到前面-->
放文本用 text()
$(".main_content").text('The <div> element will appear as-is in the browser.');
# 異步調用
服務端:
@app.route('/service_plus', methods=["POST"])
def service_plus():
data = flask.request.json
a = data["a"]
b = data["b"]
delay = data.get("delay", 0)
time.sleep(delay)
return flask.jsonify(**{
"addition": a + b,
})
客戶端:
$.ajax("/service_plus", {
type: "POST",
dataType: "json",
data: JSON.stringify({
"a": 3,
"b": 5,
}),
contentType: "application/json",
}).then(function(a) {
console.log("3+5=", a.addition);
});
說明:
@ JSON.stringify 把字典轉換為json
@ flask.jsonify 返回json串
在所有的處理異步操作中,總是返回一個deferred
function func1() {
var def1 = $.ajax(...); // A first call to the server.
var def2 = $.ajax(...); // A second call.
var def3 = $.when(def1, def2); // We multiplex all that.
var def4 = def3.then(...); // Some more complexity: we chain it.
// Now we don't forget to return a deferred that represents the complete
operation.
return def4;
};
function func2() {
var def = func1(); // We want to call func1().
// Now if I need to know when func1() has finished all its operations I h
ave a deferred that represents that.
var def2 = def.then(...);
// And finally we don't forget to return a deferred because func2() is, b
y transitivity, a function
// that performs an asynchronous call. So it should return a deferred too.
return def2;
};
*模塊定義
(function() {
app = {};
function main() {
console.log("launch application");
};
app.main = main;
})();
采用匿名函數封裝
* 例子
function openerp_picking_widgets(instance){
var module = instance.stock;
var _t = instance.web._t;
var _lt = instance.web._lt;
var QWeb = instance.web.qweb;
// This widget makes sure that the scaling is disabled on mobile devices.
// Widgets that want to display fullscreen on mobile phone need to extend this
// widget.
module.MobileWidget = instance.web.Widget.extend({
start: function(){
if(!$('#oe-mobilewidget-viewport').length){
$('head').append('<meta id="oe-mobilewidget-viewport" name="viewport" content="initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">');
}
return this._super();
},
destroy: function(){
$('#oe-mobilewidget-viewport').remove();
return this._super();
},
});
#var _t = instance.web._t; 翻譯
* 采用異步 做 (a+b)*(c+d) 這個功能 ,這是例子,主要參考模式思想
(function(){
app = {};
function main(){
$("button").click(function(){
plusmultplus(1,2,3,4).then(function(result){
console.log("(1+2)*(3+4)=",result.multiplication);
});
});
}
app.main = main;
function plusmultplus(a,b,c,d){
var def1 = $.ajax("/service_plus",{
type:"POST",
dataType:"json",
data:JSON.stringify({
"a":a,
"b":b,
}),
contentType:"application/json",
});
var def2 = $.ajax("/service_plus",{
type:"POST",
dataType:"json",
data:JSON.stringify({
"c":c,
"d":d,
}),
contentType:"application/json",
});
return $.when(def1,def2).then(function(result1,result2){
return $.ajax("/service_mult",{
type:"POST",
dataType:"json",
data:JSON.stringify({
"a":result1[0].addition,
"b":result2[0].addition,
}),
contentType:"application/json",
});
});
}
})();
--------------------------------
@app.route('/service_plus', methods=["POST"])
def service_plus():
data = flask.request.json
a = data["a"]
b = data["b"]
delay = data.get("delay", 0)
time.sleep(delay)
return flask.jsonify(**{
"addition": a + b,
})
@app.route('/service_mult', methods=["POST"])
def service_mult():
data = flask.request.json
a = data["a"]
b = data["b"]
delay = data.get("delay", 0)
time.sleep(delay)
return flask.jsonify(**{
"multiplication": a * b,
})
* Web Framework 構造圖形化的javascript 應用
# 基礎框架js所在位置 addons/web/static/src/js/openerpframework
#js 在odoo中的運用
openerp.oepetstore = function(instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.oepetstore = {};
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
console.log("pet store home page loaded");
},
});
instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
}
@ odoo運行時會把所有的javascript文件連接為一個文件,然后壓縮,若要調試,必須采用debug模式
@ 上面創建了, oepetstore 模塊,且做為屬性放在全局變量openerp中;模塊名和addon的模塊名要一
致,否則不能運行
@ 當載入addon這個模塊時,該js模塊調用,傳入instance參數,這個參數代表當前 OpenERP 的 Web 客戶端實例,
包含了所有相關當前會話數據, 以及所有 Web 模塊的變量
@ instance.oepetstore = {}; 這是命名空間,用來聲明我們模塊內自己使用的所有類和變量
# js在odoo定義一個新類
instance.oepetstore.MyClass = instance.web.Class.extend({
say_hello: function(){
console.log("hello");
}
});
@ 調用instance.web.Class.extend() 傳入一個dictionary 參數
@ 在方法內,用this訪問屬性值
instance.oepetstore.MyClass = instance.web.Class.extend({
say_hello: function() {
console.log("hello", this.name);
},
});
var my_object = new instance.oepetstore.MyClass();
my_object.name = "Nicolas";
my_object.say_hello();
@ 類可以有一個構造函數 init()
instance.oepetstore.MyClass = instance.web.Class.extend({
init: function(name) {
this.name = name;
},
say_hello: function() {
console.log("hello", this.name);
},
});
var my_object = new instance.oepetstore.MyClass("Nicolas");
my_object.say_hello();
@ 重載方法時,使用 this._super()調用原來的方法
instance.oepetstore.MySpanishClass = instance.oepetstore.MyClass.extend({
say_hello: function() {
this._super();
console.log("translation in Spanish: hola", this.name);
},
});
var my_object = new instance.oepetstore.MySpanishClass("Nicolas");
my_object.say_hello();
* Widgets
# 第一個部件
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
console.log("pet store home page loaded");
},
});
instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
這句是把這個部件注冊為客戶端的action
@ start 這個方法部件初始化會自動調用
@ 改造一下用上jQuery 的$el
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
this.$el.append("<div>Hello dear OpenERP user!</div>")
},
});
# 實例化部件
instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
start: function() {
this.$el.addClass("oe_petstore_greetings");
this.$el.append("<div>We are so happy to see you again in this menu!</div>");
},
});
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
this.$el.addClass("oe_petstore_homepage");
this.$el.append("<div>Hello dear OpenERP user!</div>");
var greeting = new instance.oepetstore.GreetingsWidget(this);
greeting.appendTo(this.$el);
},
});
顯示結果:
<div class="oe_petstore_homepage">
<div>Hello dear OpenERP user!</div>
<div class="oe_petstore_greetings">
<div>We are so happy to see you again in this menu!</div>
</div>
</div>
@ new instance.oepetstore.GreetingsWidget(this); 實例化部件
this 參數,在這里代表 HomePage實例,部件有父子關系
instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
start: function() {
console.log(this.getParent().$el );
// will print "div.oe_petstore_homepage" in the console
},
});
----------
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
var greeting = new instance.oepetstore.GreetingsWidget(this);
greeting.appendTo(this.$el);
console.log(this.getChildren()[0].$el);
// will print "div.oe_petstore_greetings" in the console
},
});
@ 當重載部件的init()時,必須以父部件作為第一參數傳入,並調用傳入給 this._super()
instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
init: function(parent, name) {
this._super(parent);
this.name = name;
},
});
當一個部件沒有父部件時,實例化傳null參數
# 銷毀部件
greeting.destory()
# 事件
instance.oepetstore.ConfirmWidget = instance.web.Widget.extend({
start: function() {
var self = this;
this.$el.append("<div>Are you sure you want to perform this action?<
/div>" +
"<button class='ok_button'>Ok</button>" +
"<button class='cancel_button'>Cancel</button>");
this.$el.find("button.ok_button").click(function() {
self.trigger("user_choose", true);
});
this.$el.find("button.cancel_button").click(function() {
self.trigger("user_choose", false);
});
},
});
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
var widget = new instance.oepetstore.ConfirmWidget(this);
widget.on("user_choose", this, this.user_choose);
widget.appendTo(this.$el);
},
user_choose: function(confirm) {
if (confirm) {
console.log("The user agreed to continue");
} else {
console.log("The user refused to continue");
}
},
});
@ this 隱式傳入到所有的函數,每個已聲明的函數都有自己的 this 。
所以,當我們在一個函數內聲明了另一個函數,這個新功能將有自己的 this ,
這和父函數 this 含義不同,采用了 var self = this; 保存
@ self.trigger("user_choose", true); 定義user_choose為名事件的觸發器
Widget.trigger(event_name [, ...]) 方法的第一個參數是待觸發的事件名,
也接受任何數量的其他參數。這些參數將被傳遞到所有的事件偵聽器
@ widget.on("user_choose", this, this.user_choose); 監聽user_choose 事件
Widget.on(event_name, object, func) 允許綁定一個事件 event_name 觸發時調
用的函數 func 。 如果 func) 是個方法,則 object 是 func) 函數的引用關聯
對象。 當 func) 被調用時, trigger() 的其他參數會傳遞給它
# 屬性
和普通對象屬性一樣,但多了一個功能,會觸發事件
start: function() {
this.widget = ...
this.widget.on("change:name", this, this.name_changed);
this.widget.set("name", "Nicolas");
},
name_changed: function() {
console.log("The new value of the property 'name' is", this.widget.get("name"));
}
@ Widget.set(name, value) 方法設置某個屬性的值。如果該值改變(或以前沒有
值) , 對象將觸發一個事件 change:xxx : xxx 是屬性名稱。
Widget.get(name) 讀取屬性值。
# 部件輔助工具
選擇器 this.$el.find("input.my_input") <=> this.$("input.my_input")
要分析jQuery事件和部件事件
簡化jQuery事件寫法
start: function() {
var self = this;
this.$(".my_button").click(function() {
self.button_clicked();
});
}
--------
events: {
"click .my_button": "button_clicked",
},
# 翻譯
記得js源碼有兩行
var _t = instance.web._t,
_lt = instance.web._lt;
這是導入翻譯功能
this.$el.text(_t("Hello dear user!"));
和對應python代碼中 _() 翻譯
_lt()返回一個函數
var text_func = _lt("Hello dear user!");
this.$el.text(text_func());
class openerp.Widget()
是所有可視化組件的基礎類
# DOM Root
Widget() 得到DOM Root
openerp.Widget.el Root
openerp.Widget.$el jQuery打包
openerp.Widget.template 生成Root
openerp.Widget.tagName 生成元素 默認是div
openerp.Widget.id 生成Root的id屬性
openerp.Widget.className
openerp.Widget.renderElement() 渲染是生成Root
# 使用widget
openerp.Widget.init(parent)
加元素
openerp.Widget.appendTo(element) 加到后面
openerp.Widget.prependTo(element) 加到前面
openerp.Widget.insertAfter(element) 插在元素的后面
openerp.Widget.insertBefore(element) 插在元素的前面
openerp.Widget.destory()清理
openerp.Widget.alive(deferred[, reject=false]) 狀態操作
openerp.Widget.isDestroyed() 檢測有沒有銷毀
# 訪問DOM內容
openerp.Widget.$(selector)
this.$(selector)
this.$el.find(selector)
# 重設DOM Root
openerp.Widget.setElement(element)
element 可以是元素,也可以jQuery對象
# DOM事件處理
openerp.Widget.events
如
events:{
'click p.oe_some_class a':'some_method',
'change input':function(e){
e.stopPropagation()
}
}
openerp.Widget.delegateEvents() 代理綁定到DOM上面
openerp.Widget.undelegateEvents() 解綁
# 子類Widget
通過extend
var MyWidget = openerp.Widget.extend({
// QWeb template to use when rendering the object
template: "MyQWebTemplate",
events: {
// events binding example
'click .my-button': 'handle_click',
},
init: function(parent) {
this._super(parent);
// insert code to execute before rendering, for object
// initialization
},
start: function() {
var sup = this._super();
// post-rendering initialization code, at this point
// allows multiplexing deferred objects
return $.when(
// propagate asynchronous signal from parent class
sup,
// return own's asynchronous signal
this.rpc(/* … */))
}
});
// Create the instance
var my_widget = new MyWidget(this);
// Render and insert into DOM
my_widget.appendTo(".some-div");
my_widget.destroy();
# 開發指南
@ 盡量少用id ,萬一要用id 要用 _.uniqueId() 生成
this.id=_.uniqueId()
@ 盡量少用普通css名字 如 content navigator
@ 盡量少用全局選擇器 用 Widget.$el 或 Widget.$()
@ 所有的組件都要繼承 Widget()
@ 要用QWeb進行HTML模板和渲染
* QWeb
instance.web.Widget 特別支持 QWeb
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="HomePageTemplate">
<div style="background-color: red;">This is some simple HTML</div>
</t>
</templates>
前面js源碼,有 var QWeb = instance.web.qweb; 這樣可以用QWeb功能了
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
this.$el.append(QWeb.render("HomePageTemplate"));
},
});
@ 渲染了 HomePageTemplate 模板
@ 也可以采用集成方式
instance.oepetstore.HomePage = instance.web.Widget.extend({
template: "HomePageTemplate",
start: function() {
},
});
發生在start方法之前,會用模板的根標簽替換部件的默認根標簽
# 傳遞參數
<t t-name="HomePageTemplate">
<div>Hello <t t-esc="name"/></div>
</t>
------
QWeb.render("HomePageTemplate", {name: "Nicolas"});
當采用集成式,要用變量 widget
<t t-name="HomePageTemplate">
<div>Hello <t t-esc="widget.name"/></div>
</t>
--------
instance.oepetstore.HomePage = instance.web.Widget.extend({
template: "HomePageTemplate",
init: function(parent) {
this._super(parent);
this.name = "Nicolas";
},
start: function() {
},
});
# 模板基礎 https://doc.odoo.com/trunk/web/qweb/
<templates>
<t t-name="HomePageTemplate">
<div>This is some simple HTML</div>
</t>
</templates>
@ <templates> 模板的根元素
前綴 t-
@ t-name 標識模板, QWeb.render() 可以調用指定模板
@ t-esc 在HTML中放置文本 會轉義html標簽
@ t-raw 保持原有內容輸出
@ t-if 條件語句
<t t-if="true==true">
true is true
</t>
@ t-foreach t-as 循環
<t t-foreach="names" t-as="name">
<div>
Hello <t t-esc="name" />
</div>
</t>
@ t-att-xx 設置屬性值
* 部件調用數據
class message_of_the_day(osv.osv):
_name = "message_of_the_day"
def my_method(self, cr, uid, context=None):
return {"hello": "world"}
_columns = {
'message': fields.text(string="Message"),
'color': fields.char(string="Color", size=20),
}
-----------
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
var self = this;
var model = new instance.web.Model("message_of_the_day");
model.call("my_method", [], {context: new instance.web.CompoundConte
xt()}).then(function(result) {
self.$el.append("<div>Hello " + result["hello"] + "</div>");
// will show "Hello world" to the user
});
},
});
#連接模型用 instance.web.Model
#采用model.call()來調用數據 call(name, args, kwargs) 是 Model 的方法
args 是對象方法的參數列表
kwargs 命名參數列表,這里只傳了context
# 模型中的方法始終有一個參數 context , context 是一個包含多個key的dictonary
# CompoundContext 這個類用來傳遞用戶上下文(語言,時區等..) 給服務器的
其構造函數的參數是任意數量的 dictionary
* 部件較好的例子
openerp.oepetstore = function(instance) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb;
instance.oepetstore = {};
instance.oepetstore.HomePage = instance.web.Widget.extend({
template: "HomePage",
start: function() {
var pettoys = new instance.oepetstore.PetToysList(this);
pettoys.appendTo(this.$(".oe_petstore_homepage_left"));
var motd = new instance.oepetstore.MessageOfTheDay(this);
motd.appendTo(this.$(".oe_petstore_homepage_right"));
},
});
instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
instance.oepetstore.MessageOfTheDay = instance.web.Widget.extend({
template: "MessageofTheDay",
init: function() {
this._super.apply(this, arguments);
},
start: function() {
var self = this;
new instance.web.Model("message_of_the_day").query(["message"]).
first().then(function(result) {
self.$(".oe_mywidget_message_of_the_day").text(result.message);
});
},
});
instance.oepetstore.PetToysList = instance.web.Widget.extend({
template: "PetToysList",
start: function() {
var self = this;
new instance.web.Model("product.product").query(["name", "image"])
.filter([["categ_id.name", "=", "Pet Toys"]]).limit(5).all().
then(function(result) {
_.each(result, function(item) {
var $item = $(QWeb.render("PetToy", {item: item}));
self.$el.append($item);
});
});
},
});
}
----------------
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="HomePage">
<div class="oe_petstore_homepage">
<div class="oe_petstore_homepage_left"></div>
<div class="oe_petstore_homepage_right"></div>
</div>
</t>
<t t-name="MessageofTheDay">
<div class="oe_petstore_motd">
<p class="oe_mywidget_message_of_the_day"></p>
</div>
</t>
<t t-name="PetToysList">
<div class="oe_petstore_pettoyslist">
</div>
</t>
<t t-name="PetToy">
<div class="oe_petstore_pettoy">
<p><t t-esc="item.name"/></p>
<p><img t-att-src="'data:image/jpg;base64,'+item.image"/></p>
</div>
</t>
</templates>
.oe_petstore_homepage {
display: table;
}
.oe_petstore_homepage_left {
display: table-cell;
width : 300px;
}
.oe_petstore_homepage_right {
display: table-cell;
width : 300px;
}
.oe_petstore_motd {
margin: 5px;
padding: 5px;
border-radius: 3px;
background-color: #F0EEEE;
}
.oe_petstore_pettoyslist {
padding: 5px;
}
.oe_petstore_pettoy {
margin: 5px;
padding: 5px;
border-radius: 3px;
background-color: #F0EEEE;
}
====================
* RPC
采用異步的方式來調用
#高級API
訪問對象方法用 openerp.Module
映射服務端對象用 call() 和 query()
var Users = new openerp.Model('res.users');
Users.call('change_password', ['oldpassword', 'newpassword'],
{context: some_context}).then(function (result) {
// do something with change_password result
});
query()是 search+read 在后端操作
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
});
openerp.Model.call(method[, args][, kwargs])
method 是rpc的方法名
args 傳入方法的參數
kwargs 關鍵詞參數
openerp.Model.query(fields)
fields 字段列表
first() 取第一個記錄
class openerp.web.Query(fields)類下方法
openerp.web.Query.all() 得到上面query() 集的所有
openerp.web.Query.first() 要第一條,沒有就是null
openerp.web.Query.count() 得到的記錄總數
openerp.web.Query.group_by(grouping...) 分組來列表
openerp.web.Query.context(ctx) 添加上下文
openerp.web.Query.filter(domain) 條件過濾domain表達式
opeenrp.web.Query.offset(offset) 設定起點
openerp.web.Query.limit(limit) 設定要返回的數量
openerp.web.Query.order_by(fields…) 記錄排序
#聚合
some_query.group_by(['field1', 'field2']).then(function (groups) {
// do things with the fetched groups
})
openerp.web.QueryGroup.get(key)
得到key的值,key可以為:
@ grouped_on
@ value
@ length
@ aggregates
openerp.web.QueryGroup.query([fields...]) 等價於
openerp.web.Model.query()
openerp.web.QueryGroup.subgroups()
#低級API RPC訪問python
opeenrp.session()
如:
openerp.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 客戶端
寫測試用例
# 斷言
ok(state[, message])
strictEqual(actual, expected[, message]) 相當於 ok(actual === expected, message))
notStrictEqual(actual, expected[, message]) 相當於 ok(actual !== expected, message))
deepEqual(actual, expected[, message])
notDeepEqual(actual, expected[, message])
throws(block[, expected][, message]) 拋出異常
equal(actual, expected[, message]) 寬松相等
notEqual(actual, expected[, message])
示例:
{
'name': "Demonstration of web/javascript tests",
'category': 'Hidden',
'depends': ['web'],
'js': ['static/src/js/demo.js'],
'test': ['static/test/demo.js'],
}
// src/js/demo.js
openerp.web_tests_demo = function (instance) {
instance.web_tests_demo = {
value_true: true,
SomeType: instance.web.Class.extend({
init: function (value) {
this.value = value;
}
})
};
};
// test/demo.js
test('module content', function (instance) {
ok(instance.web_tests_demo.value_true, "should have a true value");
var type_instance = new instance.web_tests_demo.SomeType(42);
strictEqual(type_instance.value, 42, "should have provided value");
});
* 顯示圖片
<img class="oe_kanban_image" src ="data:image/png; base64,${replace this by base64}" />
* Web Components (Action manager)
# 看一列子:
<record model="ir.actions.act_window" id="message_of_the_day_action">
<field name="name">Message of the day</field>
<field name="res_model">message_of_the_day</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="message_day" name="Message of the day" parent="petstore_menu"
action="message_of_the_day_action"/>
對應下的js 處理,沒人xml快捷,但用js更靈活
instance.oepetstore.PetToysList = instance.web.Widget.extend({
template: "PetToysList",
start: function() {
var self = this;
new instance.web.Model("product.product").query(["name", "image"])
.filter([["categ_id.name", "=", "Pet Toys"]]).limit(5).all().the
n(function(result) {
_.each(result, function(item) {
var $item = $(QWeb.render("PetToy", {item: item}));
self.$el.append($item);
$item.click(function() {
self.item_clicked(item);
});
});
});
},
item_clicked: function(item) {
this.do_action({
type: 'ir.actions.act_window',
res_model: "product.product",
res_id: item.id,
views: [[false, 'form']],
target: 'current',
context: {},
});
},
});
#上面是窗體Action 下面看焉 client Action
instance.oepetstore.HomePage = instance.web.Widget.extend({
start: function() {
console.log("pet store home page loaded");
},
});
instance.web.client_actions.add('petstore.homepage',
'instance.oepetstore.HomePage');
@ instance.web.client_actions 是一個 Registry類的實例
要打開關鍵字是petstore.home的action ,就實例化instance.oepetstore.HomePage
@對應的菜單
<record id="action_home_page" model="ir.actions.client">
<field name="tag">petstore.homepage</field>
</record>
<menuitem id="home_page_petstore_menu" name="Home Page" parent="petstore_menu"
action="action_home_page"/>