Bài viết này sẽ giúp bạn tinh chỉnh widgets và classes trong odoo với giả định bạn đã có kiến thức ít nhiều về việc phát triển Odoo.

Để giữ được tính toàn vẹn về ngôn ngữ lập trình, chúng tôi đăng trọn vẹn bài viết bằng tiếng Anh để phục vụ bạn đọc.

 

Classes

Much as modules, and contrary to most object-oriented languages, javascript does not build in classes1 although it provides roughly equivalent (if lower-level and more verbose) mechanisms.

For simplicity and developer-friendliness Odoo web provides a class system based on John Resig’s Simple JavaScript Inheritance.

New classes are defined by calling the extend() method of openerp.web.Class():

var MyClass = instance.web.Class.extend({
    say_hello: function() {
        console.log("hello");
    },
});

The extend() method takes a dictionary describing the new class’s content (methods and static attributes). In this case, it will only have a say_hello method which takes no parameters.

Classes are instantiated using the new operator:

var my_object = new MyClass();
my_object.say_hello();
// print "hello" in the console

And attributes of the instance can be accessed via this:

var MyClass = instance.web.Class.extend({
    say_hello: function() {
        console.log("hello", this.name);
    },
});

var my_object = new MyClass();
my_object.name = "Bob";
my_object.say_hello();
// print "hello Bob" in the console

Classes can provide an initializer to perform the initial setup of the instance, by defining an init() method. The initializer receives the parameters passed when using the new operator:

var MyClass = instance.web.Class.extend({
    init: function(name) {
        this.name = name;
    },
    say_hello: function() {
        console.log("hello", this.name);
    },
});

var my_object = new MyClass("Bob");
my_object.say_hello();
// print "hello Bob" in the console

It is also possible to create subclasses from existing (used-defined) classes by calling extend() on the parent class, as is done to subclass Class():

var MySpanishClass = MyClass.extend({
    say_hello: function() {
        console.log("hola", this.name);
    },
});

var my_object = new MySpanishClass("Bob");
my_object.say_hello();
// print "hola Bob" in the console

When overriding a method using inheritance, you can use this._super() to call the original method:

var MySpanishClass = MyClass.extend({
    say_hello: function() {
        this._super();
        console.log("translation in Spanish: hola", this.name);
    },
});

var my_object = new MySpanishClass("Bob");
my_object.say_hello();
// print "hello Bob \n translation in Spanish: hola Bob" in the console

Warning

_super is not a standard method, it is set on-the-fly to the next method in the current inheritance chain, if any. It is only defined during the synchronous part of a method call, for use in asynchronous handlers (after network calls or in setTimeout callbacks) a reference to its value should be retained, it should not be accessed via this:

// broken, will generate an error
say_hello: function () {
    setTimeout(function () {
        this._super();
    }.bind(this), 0);
}

// correct
say_hello: function () {
    // don't forget .bind()
    var _super = this._super.bind(this);
    setTimeout(function () {
        _super();
    }.bind(this), 0);
}

Widgets Basics

The Odoo web client bundles jQuery for easy DOM manipulation. It is useful and provides a better API than standard W3C DOM, but insufficient to structure complex applications leading to difficult maintenance.

Much like object-oriented desktop UI toolkits (e.g. Qt, Cocoa or GTK), Odoo Web makes specific components responsible for sections of a page. In Odoo web, the base for such components is the Widget() class, a component specialized in handling a page section and displaying information for the user.

Your First Widget

The initial demonstration module already provides a basic widget:

local.HomePage = instance.Widget.extend({
    start: function() {
        console.log("pet store home page loaded");
    },
});

It extends Widget() and overrides the standard method start(), which — much like the previous MyClass — does little for now.

This line at the end of the file:

instance.web.client_actions.add(
    'petstore.homepage', 'instance.oepetstore.HomePage');

registers our basic widget as a client action. Client actions will be explained later, for now this is just what allows our widget to be called and displayed when we select the Pet Store ‣ Pet Store ‣ Home Page menu.

Warning

because the widget will be called from outside our module, the web client needs its “fully qualified” name, not the local version.

Display Content

Widgets have a number of methods and features, but the basics are simple:

  • set up a widget
  • format the widget’s data
  • display the widget

The HomePage widget already has a start() method. That method is part of the normal widget lifecycle and automatically called once the widget is inserted in the page. We can use it to display some content.

All widgets have a $el which represents the section of page they’re in charge of (as a jQuery object). Widget content should be inserted there. By default, $el is an empty <div> element.

A <div> element is usually invisible to the user if it has no content (or without specific styles giving it a size) which is why nothing is displayed on the page when HomePage is launched.

Let’s add some content to the widget’s root element, using jQuery:

local.HomePage = instance.Widget.extend({
    start: function() {
        this.$el.append("<div>Hello dear Odoo user!</div>");
    },
});

That message will now appear when you open Pet Store ‣ Pet Store ‣ Home Page

Note

to refresh the javascript code loaded in Odoo Web, you will need to reload the page. There is no need to restart the Odoo server.

The HomePage widget is used by Odoo Web and managed automatically. To learn how to use a widget “from scratch” let’s create a new one:

local.GreetingsWidget = instance.Widget.extend({
    start: function() {
        this.$el.append("<div>We are so happy to see you again in this menu!</div>");
    },
});

We can now add our GreetingsWidget to the HomePage by using the GreetingsWidget‘s appendTo() method:

local.HomePage = instance.Widget.extend({
    start: function() {
        this.$el.append("<div>Hello dear Odoo user!</div>");
        var greeting = new local.GreetingsWidget(this);
        return greeting.appendTo(this.$el);
    },
});
  • HomePage first adds its own content to its DOM root
  • HomePage then instantiates GreetingsWidget
  • Finally it tells GreetingsWidget where to insert itself, delegating part of its $el to the GreetingsWidget.

When the appendTo() method is called, it asks the widget to insert itself at the specified position and to display its content. Thestart() method will be called during the call to appendTo().

To see what happens under the displayed interface, we will use the browser’s DOM Explorer. But first let’s alter our widgets slightly so we can more easily find where they are, by adding a class to their root elements:

local.HomePage = instance.Widget.extend({
    className: 'oe_petstore_homepage',
    ...
});
local.GreetingsWidget = instance.Widget.extend({
    className: 'oe_petstore_greetings',
    ...
});

If you can find the relevant section of the DOM (right-click on the text then Inspect Element), it should look like this:

<div class="oe_petstore_homepage">
    <div>Hello dear Odoo user!</div>
    <div class="oe_petstore_greetings">
        <div>We are so happy to see you again in this menu!</div>
    </div>
</div>

Which clearly shows the two <div> elements automatically created by Widget(), because we added some classes on them.

We can also see the two message-holding divs we added ourselves

Finally, note the <div class="oe_petstore_greetings"> element which represents the GreetingsWidget instance is inside the<div class="oe_petstore_homepage"> which represents the HomePage instance, since we appended

Widget Parents and Children

In the previous part, we instantiated a widget using this syntax:

new local.GreetingsWidget(this);

The first argument is this, which in that case was a HomePage instance. This tells the widget being created which other widget is its parent.

As we’ve seen, widgets are usually inserted in the DOM by another widget and inside that other widget’s root element. This means most widgets are “part” of another widget, and exist on behalf of it. We call the container the parent, and the contained widget the child.

Due to multiple technical and conceptual reasons, it is necessary for a widget to know who is its parent and who are its children.

getParent()
can be used to get the parent of a widget:

local.GreetingsWidget = instance.Widget.extend({
    start: function() {
        console.log(this.getParent().$el );
        // will print "div.oe_petstore_homepage" in the console
    },
});
getChildren()
can be used to get a list of its children:

local.HomePage = instance.Widget.extend({
    start: function() {
        var greeting = new local.GreetingsWidget(this);
        greeting.appendTo(this.$el);
        console.log(this.getChildren()[0].$el);
        // will print "div.oe_petstore_greetings" in the console
    },
});

When overriding the init() method of a widget it is of the utmost importance to pass the parent to the this._super() call, otherwise the relation will not be set up correctly:

local.GreetingsWidget = instance.Widget.extend({
    init: function(parent, name) {
        this._super(parent);
        this.name = name;
    },
});

Finally, if a widget does not have a parent (e.g. because it’s the root widget of the application), null can be provided as parent:

new local.GreetingsWidget(null);

Destroying Widgets

If you can display content to your users, you should also be able to erase it. This is done via the destroy() method:

greeting.destroy();

When a widget is destroyed it will first call destroy() on all its children. Then it erases itself from the DOM. If you have set up permanent structures in init() or start() which must be explicitly cleaned up (because the garbage collector will not handle them), you can override destroy().

Danger

when overriding destroy(), _super() must always be called otherwise the widget and its children are not correctly cleaned up leaving possible memory leaks and “phantom events”, even if no error is displayed.

Tham khảo: https://www.odoo.com/documentation/8.0/howtos/web.html#existing-web-components

BÌNH LUẬN

Please enter your comment!
Please enter your name here