在OpenERP 7 和 Odoo 8下測試都可。
1.相關庫/框架
主要:jQuery(使用1.8.3,若是使用新版本,其餘jQuery插件也要升級或修改)、Underscore、Qweb
其餘:都在addons\web\static\lib路徑下。
2.示例框架
下載(須要先安裝bzr):bzr branch lp:~niv-openerp/+junk/oepetstore -r 1
下載後將路徑加到OpenERP服務器的addons_path參數中,重啓服務器、更新模塊列表再安裝。
在__openerp__.py中經過:
[python]javascript
- 'js': ['static/src/js/*.js'],
- 'css': ['static/src/css/*.css'],
- 'qweb': ['static/src/xml/*.xml'],
將全部js/css/xml(QWeb模板)文件包含進來。
oepetstore/static/js/petstore.js註釋說明:
[javascript]css
- openerp.oepetstore = function(instance) { // OpenERP模型,必須和模塊名稱相同。instance參數是OpenERP Web Client自動加載模塊時傳入的實例。
- var _t = instance.web._t,
- _lt = instance.web._lt; // 翻譯函數
- var QWeb = instance.web.qweb; // QWeb實例
-
- instance.oepetstore = {}; // instance實例裏面的模塊命名空間(namespace),好比和模塊名稱相同。
-
- 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');
- // 將自定義首頁部件與菜單動做綁定
- }
能夠在網址後面加「?debug」參數使腳本不壓縮以便於調試,例如:
[javascript]html
- http://localhost:8069/?debug
3.類的定義
從instance.web.Class基類擴展:
[javascript]java
- instance.oepetstore.MyClass = instance.web.Class.extend({
- say_hello: function() {
- console.log("hello");
- },
- });
- var my_object = new instance.oepetstore.MyClass();
- my_object.say_hello();
構造函數名爲init();使用this訪問對象實例的屬性或方法:
[javascript]python
- 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();
類能夠經過extend()方法繼承;使用this._super()調用基類被覆蓋的方法。
[javascript]web
- 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();
4.部件(Widget)
從instance.web.Widget擴展自定義部件。HomePage首頁部件見petstore.js。
在自定義部件中,this.$el表示部件實例的jQuery對象,能夠調用jQuery方法,例如:
[javascript]數據庫
- this.$el.append("
Hello dear OpenERP user!
");
往部件中添加一個數組
塊及內容。
部件中能夠插入其餘部件進行組合:
[javascript]
- instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
- start: function() {
- this.$el.addClass("oe_petstore_greetings");
- this.$el.append("
We are so happy to see you again in this menu!
");
- },
- });
- instance.oepetstore.HomePage = instance.web.Widget.extend({
- start: function() {
- this.$el.addClass("oe_petstore_homepage");
- this.$el.append("
Hello dear OpenERP user!
");
- var greeting = new instance.oepetstore.GreetingsWidget(this); // 建立部件的時候傳入父部件的實例做爲構造參數。
- greeting.appendTo(this.$el);
- },
- });
父子部件能夠經過getChildren()、getParent()進行互相訪問。若是重載部件的構造函數,第一個參數必須是父部件,而且必須傳遞給基類。
[javascript]
- instance.oepetstore.GreetingsWidget = instance.web.Widget.extend({
- init: function(parent, name) {
- this._super(parent);
- this.name = name;
- },
- });
若是做爲頂層部件建立,parent參數應該是null。
部件實例能夠調用destroy()方法銷燬。
5.QWeb模板引擎
QWeb模板在XML屬性上加前綴「t-」表示:
t-name:模板名稱;
t-esc:引用實例參數,可使用任意JavaScript表達式;
t-raw:引用原始實例參數,若是有html標記則保留。
QWeb模板下面的根元素最好只有一個。
oepetstore/static/src/xml/petstore.xml:
[html]
-
-
-
-
-
-
Hello
-
-
-
-
-
- 定義一個名爲「HomePageTemplate」的模板。
使用方法1:
[javascript]
- instance.oepetstore.HomePage = instance.web.Widget.extend({
- start: function() {
- this.$el.append(QWeb.render("HomePageTemplate"));
- },
- });
使用方法2:
[javascript]
- instance.oepetstore.HomePage = instance.web.Widget.extend({
- template: "HomePageTemplate",
- start: function() {
- ...
- },
- });
模板裏面的條件控制t-if:
[html]
-
- true is true
-
-
- true is not true
-
枚舉t-foreach和t-as:
[html]
-
-
- Hello
-
-
屬性賦值,在屬性名前加前綴「t-att-」:
[html]
-
將input控件的value屬性賦值爲「defaultName」。
部件開發示例,顯示產品列表。
JavaScript腳本:
[javascript]
- 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() {
- var products = new instance.oepetstore.ProductsWidget(this, ["cpu", "mouse", "keyboard", "graphic card", "screen"], "#00FF00");
- products.appendTo(this.$el);
- },
- });
-
- instance.oepetstore.ProductsWidget = instance.web.Widget.extend({
- template: "ProductsWidget",
- init: function(parent, products, color) {
- this._super(parent);
- this.products = products;
- this.color = color;
- },
- });
-
- instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
- }
QWeb模板:
[html]
-
-
-
-
-
-
-
-
-
-
-
CSS樣式:
[css]
- .oe_products_item {
- display: inline-block;
- padding: 3px;
- margin: 5px;
- border: 1px solid black;
- border-radius: 3px;
- }
6.部件事件與特性
[javascript]
- instance.oepetstore.ConfirmWidget = instance.web.Widget.extend({
- start: function() {
- var self = this;
- this.$el.append("
Are you sure you want to perform this action?
" +
- "<button class='ok_button'>Ok" +
- "<button class='cancel_button'>Cancel");
- this.$el.find("button.ok_button").click(function() { // 在按鈕上綁定click事件
- self.trigger("user_choose", true); // 觸發自定義user_choose事件,傳遞事件參數true/false
- });
- 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); // 在部件上綁定user_choose事件到響應函數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");
- }
- },
- });
部件特性(Properties)的使用:
[javascript]
- this.widget.on("change:name", this, this.name_changed); //綁定name特性的change事件
- this.widget.set("name", "Nicolas"); // 設置特性值
- var getedname = this.widget.get("name"); // 讀取特性值
7.部件訪問 簡化jQuery選擇器:
[javascript]
- this.$el.find("input.my_input")
等於
[javascript]
- this.$("input.my_input")
所以事件的綁定:
[javascript]
- this.$el.find("input").change(function() {
- self.input_changed();
- });
能夠簡化爲:
[javascript]
- this.$(".my_button").click(function() {
- self.button_clicked();
- });
進一步,能夠經過部件提供的events字典屬性簡化爲:
[javascript]
- instance.oepetstore.MyWidget = instance.web.Widget.extend({
- events: {
- "click .my_button": "button_clicked",
- },
- button_clicked: function() {
- ..
- }
- });
注意:這種方法只是綁定jQuery提供的DOM事件機制,不能用於部件的on語法綁定部件自身的事件。 event屬性的鍵名由兩部分組成:事件名稱和jQuery選擇器,用空格分開。屬性值是響應事件的函數(方法)。
事件使用示例:
JavaScript腳本:
[javascript]
- openerp.oepetstore = function(instance) {
- var _t = instance.web._t,
- _lt = instance.web._lt;
- var QWeb = instance.web.qweb;
-
- instance.oepetstore = {};
-
- instance.oepetstore.ColorInputWidget = instance.web.Widget.extend({
- template: "ColorInputWidget",
- start: function() {
- var self = this;
- this.$el.find("input").change(function() {
- self.input_changed();
- });
- self.input_changed();
- },
- input_changed: function() {
- var color = "#";
- color += this.$el.find(".oe_color_red").val();
- color += this.$el.find(".oe_color_green").val();
- color += this.$el.find(".oe_color_blue").val();
- this.set("color", color);
- },
- });
-
- instance.oepetstore.HomePage = instance.web.Widget.extend({
- template: "HomePage",
- start: function() {
- this.colorInput = new instance.oepetstore.ColorInputWidget(this);
- this.colorInput.on("change:color", this, this.color_changed);
- this.colorInput.appendTo(this.$el);
- },
- color_changed: function() {
- this.$el.find(".oe_color_div").css("background-color", this.colorInput.get("color"));
- },
- });
-
- instance.web.client_actions.add('petstore.homepage', 'instance.oepetstore.HomePage');
- }
QWeb模板:
[html]
-
-
-
-
-
- Red: <br >
- Green: <br >
- Blue: <br >
-
-
-
-
-
-
-
-
CSS樣式:
[css]
- .oe_color_div {
- width: 100px;
- height: 100px;
- margin: 10px;
- }
8.修改已有的部件和類
能夠用include()方法重載已有的部件和類,這個和繼承機制相似,是一種插入的方法:
[javascript]
- var TestClass = instance.web.Class.extend({
- testMethod: function() {
- return "hello";
- },
- });
-
- TestClass.include({
- testMethod: function() {
- return this._super() + " world";
- },
- });
-
- console.log(new TestClass().testMethod());
- // will print "hello world"
應儘可能避免使用這種機制致使的複雜性。
9.與服務器的交互-讀取數據模型
客戶端使用Ajax與服務器交互,不過OpenERP框架提供了簡化的方法,經過數據模型進行訪問。
OpenERP自動將服務端的數據模型轉化爲客戶端端模型,直接調用便可。服務器上petstore.py裏面的模型:
[python]
- 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),
- }
客戶端調用例子:
[javascript]
- 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.CompoundContext()}).then(function(result) {
- self.$el.append("
Hello " + result["hello"] + "
");
- // will show "Hello world" to the user
- });
- },
- });
模型的call()方法參數:
第一個參數name是方法的名稱;
第二個參數args是按照順序排列的參數數組。OpenERP定義的模型方法前三個參數(self, cr, uid)是固定的,由框架產生,也就是說傳遞的參數數組從第四個開始插入。而context又是特殊的。例子:
方法定義:
[python]
- def my_method2(self, cr, uid, a, b, c, context=None):
調用:
[javascript]
- model.call("my_method", [1, 2, 3], ...// with this a=1, b=2 and c=3
第三個參數kwargs爲命名參數,按照名稱傳遞給Python的方法參數。例如:
[javascript]
- model.call("my_method", [], {a: 1, b: 2, c: 3}, ...// with this a=1, b=2 and c=3
OpenERP模型中的context是一個特殊參數,表示調用者的上下文,通常就使用客戶端Web Client實例提供的instance.web.CompoundContext()類新建一個對象實例便可。
CompoundContext類提供用戶的語言和時區信息。也能夠在構造函數中添加另外的數據:
[javascript]
- model.call("my_method", [], {context: new instance.web.CompoundContext({'new_key': 'key_value'})})def display_context(self, cr, uid, context=None): print context // will print: {'lang': 'en_US', 'new_key': 'key_value', 'tz': 'Europe/Brussels', 'uid': 1}
(OpenERP服務器端數據模型的方法必須提供4個參數:self, cr, uid, context=None,分別表示模型實例、數據庫指針(Cursor)、用戶id和用戶上下文)
10.與服務器的交互-查詢
客戶端數據模型提供了search()、read()等方法,組合爲query()方法,使用例子:
[javascript]
- model.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()方法的參數是須要讀取的模型字段名稱列表;該方法返回的是一個instance.web.Query()類型的查詢對象實例,包括一些進一步定義查詢結果的方法,這些方法返回的是同一個查詢對象自身,所以能夠連接:
filter():指定OpenERP 域(domain),也即過濾查詢結果;
limit():限制返回的記錄數量。
最後調用查詢對象的all()方法執行查詢。
查詢異步執行,all()返回的是一個deferred,所以要用then()提供回調函數來處理結果。
數據模型的查詢是經過rpc調用實現的。
示例1:顯示每日提示
JavaScript腳本:
[javascript]
- 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 motd = new instance.oepetstore.MessageOfTheDay(this);
- motd.appendTo(this.$el);
- },
- });
-
- 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);
- });
- },
- });
-
- }
QWeb模板:
[html]
-
-
-
-
-
-
-
-
-
-
-
-
-
CSS樣式:
[css]
- .oe_petstore_motd {
- margin: 5px;
- padding: 5px;
- border-radius: 3px;
- background-color: #F0EEEE;
- }
示例2:組合顯示每日提示和產品列表
服務器端從OpenERP的產品表繼承一個類(模型):
[python]
- class product(osv.osv):
- _inherit = "product.product"
-
- _columns = {
- 'max_quantity': fields.float(string="Max Quantity"),
- }
所以數據是保存在product.product表中的,只是擴充了一個「max_quantity」字段;這個例子結合前面的每日提示信息,顯示二列,左面一列顯示產品列表,右面顯示提示信息。
JavaScript腳本:
[javascript]
- 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);
- });
- });
- },
- });
-
- }
QWeb模板:
[html]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<img t-att- src="'data:image/jpg;base64,'+item.image">服務器
-
-
-
CSS樣式:
[css]
- .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;
- }