(19)odoo中的javascript

时间:2024-04-05 21:35:53

-----------
更新日期
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"/>