ウェブクライアントのカスタマイズ

危険

このチュートリアルは時代遅れです。

このガイドでは、Odoo の Web クライアント用のモジュールを作成します。

Odoo でウェブサイトを作成するには、 ウェブサイトのテーマを作成 を参照してください。ビジネス機能を追加したり、Odoo の既存のビジネスシステムを拡張したりするには、 モジュールの構築 を参照してください。

警告

このガイドは以下の知識を前提としています。

an installed Odoo, and Git_も必要です。

シンプルなモジュール

基本的なWebコンポーネントの設定を保持し、WebフレームワークをテストできるシンプルなOdooモジュールから始めましょう。

サンプルモジュールはオンラインで利用でき、以下のコマンドを使用してダウンロードできます。

$ git clone http://github.com/odoo/petstore

これにより petstore フォルダが作成されます。 そのフォルダを Odoo の addons パス に追加し、新しいデータベースを作成し、oepetstore モジュールをインストールする必要があります。

petstore フォルダを参照すると、次の内容が表示されます。

oepetstore
|-- images
|   |-- alligator.jpg
|   |-- ball.jpg
|   |-- crazy_circle.jpg
|   |-- fish.jpg
|   `-- mice.jpg
|-- __init__.py
|-- oepetstore.message_of_the_day.csv
|-- __manifest__.py
|-- petstore_data.xml
|-- petstore.py
|-- petstore.xml
`-- static
    `-- src
        |-- css
        |   `-- petstore.css
        |-- js
        |   `-- petstore.js
        `-- xml
            `-- petstore.xml

モジュールにはすでにさまざまなサーバーのカスタマイズがあります。これらについては、static フォルダにあるウェブ関連のコンテンツに焦点を当ててみましょう。

Odoo モジュールの "web" 側で使用されるファイルは static フォルダに保存されている必要があります。 そのフォルダ以外のファイルはブラウザで取得できません。 src/css, src/js および src/xml サブフォルダは従来のものであり、厳密には必要ありません。

oepetstore/static/css/petstore.css

現在空の場合、ペットストアのコンテンツ用の CSS が保持されます

oepetstore/static/xml/petstore.xml

たいてい空の場合、 :ref:`reference/qweb`テンプレートが保持されます

oepetstore/static/js/petstore.js

最も重要な(そして興味深い)部分には、javascriptとしてアプリケーション(または少なくともそのWebブラウザ側)のロジックが含まれています。

odoo.oepetstore = function(instance, local) {
    var _t = instance.web._t,
        _lt = instance.web._lt;
    var QWeb = instance.web.qweb;

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

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

これはブラウザーのコンソールに小さなメッセージしか出力しません。

static フォルダ内のファイルはモジュール内で正しく読み込むために定義する必要があります。 src/xml ではすべてが __manifest__.py で定義されていますが、src/csssrc/js の内容は petstore.xml で定義されています。

警告

すべてのJavaScriptファイルは連結され、 minified はアプリケーションの読み込み時間を向上させます。

個々のファイルが消失し、コードが読めなくなるにつれて、デバッグが難しくなることが欠点の1つです。 「開発者モード」を有効にすることで、このプロセスを無効にすることができます。Odoo インスタンス(デフォルトではユーザー admin パスワード admin )にログインし、ユーザーメニュー(Odoo 画面の右上隅)を開き、 About Odo開発者モードを有効にする を選択します。

../../_images/about_odoo.png ../../_images/devmode.png

これにより、ウェブクライアントは再読み込みされ、最適化は無効になり、開発とデバッグは大幅に快適になります。

Odoo JavaScript Module

Javascriptにはモジュールが組み込まれていません。結果として、異なるファイルで定義された変数はすべて一緒にマッシュアップされ、競合する可能性があります。 これにより、クリーンな名前空間を構築し、命名競合のリスクを制限するために使用されるさまざまなモジュールパターンが生じています。

Odoo フレームワークは、名前空間コードと読み込み順序の両方のために、Web アドオン内のモジュールを定義するために、このような 1 つのパターンを使用します。

oepetstore/static/js/petstore.js にはモジュール宣言が含まれています:

odoo.oepetstore = function(instance, local) {
    local.xxx = ...;
}

Odoo Web では、モジュールはグローバル odoo 変数に設定された関数として宣言されます。 関数の名前はアドオンと同じでなければなりません (この場合 oepetstore) フレームワークがそれを見つけ、自動的に初期化します。

Webクライアントがモジュールをロードすると、ルート関数を呼び出し、2つのパラメータを提供します。

  • 最初のパラメータは、Odoo Webクライアントの現在のインスタンスであり、Odoo (翻訳) によって定義された様々な機能へのアクセスを提供します。 ネットワーク サービス) およびコアまたは他のモジュールによって定義されたオブジェクト。

  • 2番目のパラメータは、Webクライアントによって自動的に作成される独自のローカル名前空間です。 モジュールの外部からアクセスできるオブジェクトと変数(Odoo Web クライアントがそれらを呼び出す必要があるため、または他のユーザーがそれらをカスタマイズしたいと思う可能性があるため)は、その名前空間内で設定する必要があります。

クラス

ほとんどのオブジェクト指向言語とは異なり、モジュールとして使用できます。 javascriptは*classes*[#classes]_では構築されませんが、ほぼ等価なメカニズムを提供します (下位レベル以上であれば)。

シンプルで開発者に優しいため Odoo Web は John Resig の Simple JavaScript Inheritance に基づいたクラスシステムを提供します。

新しいクラスは、 odoo.web.Class.extend() メソッドの :class:`odoo.web.Class`を呼び出すことで定義されます。

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

extend() メソッドは、新しいクラスのコンテンツ(メソッドと静的属性)を説明する辞書を取ります。 この場合、パラメータを取らない``say_hello`` メソッドのみを持ちます。

クラスは new 演算子を使用してインスタンス化されます。

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

インスタンスの属性は 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

クラスは、init() メソッドを定義することで、インスタンスの初期設定を実行する初期化子を提供できます。 初期化子は new 演算子を使って渡されたパラメータを受け取ります。

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

継承を使ってメソッドをオーバーライドする場合は、 this._super() を使って元のメソッドを呼び出すことができます:

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

警告

_super は標準的なメソッドではありません。もしあれば、現在の継承チェーンの次のメソッドにオンザフライで設定されます。 これはメソッド呼び出しの 同期 部分でのみ定義されています を使用して非同期ハンドラで使用します (ネットワーク呼び出しの後や setTimeout コールバックの後) 値への参照を保持する必要があります。 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);
}

ウィジェットの基本

Odoo web クライアントは jQuery をバンドルしており、DOM 操作を簡単に行えます。 これは便利で、標準の `W3C DOM`__ 2 より優れた API を提供しますが、複雑なアプリケーションを構築するには不十分で、メンテナンスが困難になります。

オブジェクト指向のデスクトップUIツールキット(QtCocoa、GTKなど)と同様に、Odoo Webはページのセクションを担当する特定のコンポーネントを作成します。 Odoo Web では、そのようなコンポーネントのベースは :class:`~odooです。 idgetクラス(idget)は、ページセクションの処理やユーザの情報の表示に特化したコンポーネントです。

最初のウィジェット

最初のデモモジュールはすでに基本的なウィジェットを提供しています:

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

Widget() を拡張し、標準メソッド start() を上書きします。これは、以前の MyClass と同じく、今のところほとんど変わりません。

ファイルの末尾にあるこの行:

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

基本的なウィジェットをクライアントアクションとして登録します。 クライアントアクションは後で説明されます 今のところこれは、 Pet Store ‣ Pet Store ‣ Home Page メニューを選択したときにウィジェットを呼び出して表示することを可能にするものです。

警告

ウィジェットは私たちのモジュールの外から呼び出されるため、Webクライアントはローカルバージョンではなく、その「完全修飾」名を必要とします。

コンテンツを表示

ウィジェットにはいくつかのメソッドと機能がありますが、基本は簡単です:

  • ウィジェットの設定

  • ウィジェットのデータを書式設定する

  • ウィジェットを表示

HomePage ウィジェットはすでに start() メソッドを持っています。 このメソッドは通常のウィジェットのライフサイクルの一部であり、ウィジェットがページに挿入されると自動的に呼び出されます。 いくつかのコンテンツを表示するために使用できます。

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.

<div> 要素は通常、コンテンツがない場合(または特定のスタイルでサイズを指定しない場合)にはユーザーには見えません。このため、HomePage を起動するとページに何も表示されません。

jQuery:: を使用して、ウィジェットのルート要素にいくつかのコンテンツを追加しましょう。

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

このメッセージは、 Pet Store ‣ Pet Store -> Home Page を開いたときに表示されます。

注釈

Odoo Web にロードされた javascript コードを更新するには、ページを再読み込みする必要があります。Odoo サーバーを再起動する必要はありません。

HomePage ウィジェットは、Odoo Webで使用され、自動的に管理されます。ウィジェットの使い方を「一から」学ぶためには、新しいものを作成してみましょう:

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

GreetingsWidgetappendTo() メソッドを使用して、HomePageGreetingsWidget を追加することができます:

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 は最初に DOM ルートに独自のコンテンツを追加します

  • HomePageGreetingsWidget をインスタンス化します

  • 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. The start() 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',
    ...
});

DOM の関連するセクションが見つかった場合(テキストを右クリックして Inspect Element )、次のようになります。

<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.

自分自身で追加した2つのメッセージ保持divも見ることができます

最後に、GreetingsWidget インスタンスを表す``<div class="oe_petstore_greetings">``要素は、HomePage インスタンスを表す``<div class="oe_petstore_homepage">`` の中に*あります。 我々は追加されたので

ウィジェットの親と子供

前の部分では、この構文を使用してウィジェットをインスタンス化しました。

new local.GreetingsWidget(this);

最初の引数は this で、その場合は HomePage インスタンスでした。これは parent である他のウィジェットが作成されたことを示します。

これまで見てきたように、ウィジェットは通常、別のウィジェットと 内部 ウィジェットによって挿入されます。 これは、ほとんどのウィジェットが別のウィジェットの一部であり、ウィジェットの代わりに存在することを意味します。 コンテナを*親*と呼び、含まれるウィジェットを*子*と呼びます。

複数の技術的および概念的な理由により、ウィジェットは誰がその親であり、誰がその子であるかを知ることが必要です。

getParent()

ウィジェットの親を取得するのに使用できます。

local.GreetingsWidget = instance.Widget.extend({
    start: function() {
        console.log(this.getParent().$el );
        // will print "div.oe_petstore_homepage" in the console
    },
});
getChildren()

は、その子要素のリストを取得するのに使用できます。

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
    },
});

ウィジェットの init() メソッドをオーバーライドする場合、親を``thisに渡すためには、*最も重要な*です。 super()`` を呼び出します。そうしないとリレーションが正しく設定されません:

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

最後に、ウィジェットに親がない場合 (例えば、アプリケーションのルートウィジェットであるため)、null を親として提供することができます:

new local.GreetingsWidget(null);

ウィジェットの破壊

ユーザーにコンテンツを表示できる場合は、削除することもできます。これは :func:`~odoo.Widget.destroy`メソッドによって行われます。

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().

危険

Widgetをオーバーライドしたとき estroy(), _super() *必ず呼び出す必要があります*そうでなければウィジェットとその子は正しくクリーンアップされず、メモリリークと「幻のイベント」が発生します。 たとえエラーが表示されなくても

QWeb テンプレート エンジン

前のセクションでは、ウィジェットのDOMを直接操作(追加)することで、ウィジェットにコンテンツを追加しました。

this.$el.append("<div>Hello dear Odoo user!</div>");

これにより、あらゆる種類のコンテンツを生成および表示できますが、かなりの量の DOM を生成する際に扱いにくくなります (多くの重複、問題の引用など)。

他の多くの環境で、Odoo の解決策は、 template engine を使用することです。Odoo のテンプレートエンジンは QWeb テンプレート と呼ばれます。

QWeb は GenshiThymeleaf,Facelets に似た、XMLベースのテンプレート言語です。以下の特性を持ちます。

  • JavaScriptで完全に実装され、ブラウザでレンダリングされます

  • 各テンプレート ファイル (XML ファイル) には複数のテンプレートが含まれています

  • It has special support in Odoo Web's Widget(), though it can be used outside of Odoo's web client (and it's possible to use Widget() without relying on QWeb)

注釈

既存の javascript テンプレートエンジンの代わりに QWeb を使用する理由は、Odoo views のように、既存の (サードパーティ) テンプレートの拡張性です。

ほとんどのjavascriptテンプレートエンジンはテキストベースで、XMLベースのテンプレートエンジンを一般的に変更できる構造拡張性を排除します。例えば、 XPath または CSS と ツリー変更の DSL (あるいは単に XSLT) です。 この柔軟性と拡張性は、Odoo の中核的な特徴であり、それを失うことは容認できないと考えられていました。

QWeb の使用

最初に、ほとんど空の oepetstore/static/src/xml/petstore.xml ファイル内に単純な 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>

HomePage ウィジェットの中でこのテンプレートを使用できるようになりました。 ページの上部に定義された QWeb ローダー変数を使用して、XML ファイル内で定義されたテンプレートを呼び出すことができます:

local.HomePage = instance.Widget.extend({
    start: function() {
        this.$el.append(QWeb.render("HomePageTemplate"));
    },
});

QWeb.render() は指定したテンプレートを探し、文字列にレンダリングし、結果を返します。

しかし、 Widget() は QWeb に特別な統合を持っているため、テンプレートは template 属性を介してウィジェットに直接設定できます:

local.HomePage = instance.Widget.extend({
    template: "HomePageTemplate",
    start: function() {
        ...
    },
});

結果は似ていますが、これらの使用法には2つの違いがあります。

  • 2つ目のバージョンでは、 start() の直前にテンプレートがレンダリングされます。

  • 最初のバージョンでは、テンプレートのコンテンツがウィジェットのルート要素に追加されます。 一方、2 番目のバージョンでは、テンプレートのルート要素は直接 *ウィジェットのルート要素として設定されます。 「挨拶」サブウィジェットも赤い背景を取得する理由です

警告

テンプレートは t 以外のルート要素を一つ持つ必要があります。特にウィジェットの template として設定されている場合。 複数の "ルート要素" がある場合、結果は未定義です (通常、最初のルート要素のみが使用され、他の要素は無視されます)

QWeb コンテキスト

QWebテンプレートは、データを与えることができ、基本的な表示ロジックを含むことができます。

QWeb.render() への明示的な呼び出しの場合、テンプレートデータは 2 番目のパラメータとして渡されます:

QWeb.render("HomePageTemplate", {name: "Klaus"});

を使用します。

<t t-name="HomePageTemplate">
    <div>Hello <t t-esc="name"/></div>
</t>

will result in :

<div>Hello Klaus</div>

When using Widget()'s integration it is not possible to provide additional data to the template. The template will be given a single widget context variable, referencing the widget being rendered right before start() is called (the widget's state will essentially be that set up by init()):

<t t-name="HomePageTemplate">
    <div>Hello <t t-esc="widget.name"/></div>
</t>
local.HomePage = instance.Widget.extend({
    template: "HomePageTemplate",
    init: function(parent) {
        this._super(parent);
        this.name = "Mordecai";
    },
    start: function() {
    },
});

結果:

<div>Hello Mordecai</div>

テンプレート宣言

QWeb テンプレートを render する方法を見てきました。では、テンプレート自体の構文を見てみましょう。

QWeb テンプレートは QWeb ディレクティブ と混合された通常の XML で構成されています。QWeb ディレクティブは t- で始まるXML属性で宣言されます。

最も基本的なディレクティブは t-name で、テンプレートファイル内で新しいテンプレートを宣言します。

<templates>
    <t t-name="HomePageTemplate">
        <div>This is some simple HTML</div>
    </t>
</templates>

t-name は定義されているテンプレートの名前を取り、 QWebを使用して呼び出すことができることを宣言します。 エンダー()。テンプレートファイルのトップレベルでのみ使用できます。

Escaping

t-esc ディレクティブはテキストを出力するために使用できます。

<div>Hello <t t-esc="name"/></div>

評価された Javascript の式を取り、式の結果は HTML エスケープされ、文書に挿入されます。 式なので、上記のように変数名だけ、または計算のようなより複雑な式を提供することができます:

<div><t t-esc="3+5"/></div>

またはメソッド呼び出し:

<div><t t-esc="name.toUpperCase()"/></div>

HTML出力

レンダリングされるページに HTML を挿入するには、 t-raw を使用します。 t-esc のように、任意のJavascript式をパラメータとしますが、HTMLエスケープステップは実行しません。

<div><t t-raw="name.link(user_account)"/></div>

危険

cross-site scripting の脆弱性につながるため、t-raw は、エスケープされていないユーザーが提供するコンテンツを含むデータでは使用しないでください。

条件

QWeb は t-if を使って条件付きブロックを持つことができます。 このディレクティブは任意の式を受け取り、式が false の場合 (false, null) 0 または空の文字列) はブロック全体が抑制され、それ以外の場合は表示されます。

<div>
    <t t-if="true == true">
        true is true
    </t>
    <t t-if="true == false">
        true is not true
    </t>
</div>

注釈

QWeb には、"else" 構造体はありません。元の状態を反転させた 2 番目の t-if を使用します。 複雑な式や高価な式の場合は、条件をローカル変数に格納することができます。

イテレーション

リストを反復するには、t-foreacht-as を使用します。 t-foreach は、 t-as で反復するためにリストを返す式を取ります。反復中に各アイテムにバインドする変数名を取ります。

<div>
    <t t-foreach="names" t-as="name">
        <div>
            Hello <t t-esc="name"/>
        </div>
    </t>
</div>

注釈

t-foreach は数字やオブジェクト(辞書)でも使用できます

属性の定義

QWeb は t-att-namet-attf-name に関連する二つのディレクティブを提供します。 どちらの場合でも、name は作成する属性の名前です(例えば、レンダリング後に id は属性 id を定義します)。

t-att- は、結果が属性の値として設定された javascript 式を取ります。すべての属性の値が計算される場合に最も便利です:

<div>
    Input your name:
    <input type="text" t-att-value="defaultName"/>
</div>

t-attf-フォーマット文字列 を取ります。 フォーマット文字列は、内部に補間ブロックを持つリテラルテキストです。 補間ブロックは {{}} の間のjavascript式で、式の結果に置き換えられます。 これは、クラスのように部分的にリテラルで部分的に計算された属性に最も便利です:

<div t-attf-class="container {{ left ? 'text-left' : '' }} {{ extra_class }}">
    insert content here
</div>

他のテンプレートを呼び出す

テンプレートはサブテンプレートに分割することができます (シンプルさ、メンテナンス性、再利用性、過度のマークアップ入れ子を避けるため)。

これは t-call ディレクティブを使用して行われ、テンプレートの名前をレンダリングします。

<t t-name="A">
    <div class="i-am-a">
        <t t-call="B"/>
    </div>
</t>
<t t-name="B">
    <div class="i-am-b"/>
</t>

A テンプレートをレンダリングすると次のようになります:

<div class="i-am-a">
    <div class="i-am-b"/>
</div>

サブテンプレートは呼び出し元のレンダリングコンテキストを継承します。

QWeb についてもっと詳しく知るには

QWeb 参照については、 QWeb テンプレート を参照してください。

演習

Exercise

QWebをウィジェットで使用する

parent``とは別に、コンストラクタが2つのパラメータを取るウィジェットを作成します: ``product_namescolor

  • product_names は文字列の配列で、それぞれ製品の名前を指定する必要があります

  • color は、CSS カラーフォーマットの色を含む文字列です(例: 黒の場合は #000000 )。

ウィジェットは、与えられた製品名を他下に表示する必要があります。 それぞれが別々のボックスにあり、背景色は color の値と境界線を持っています。 HTMLをレンダリングするにはQWebを使用する必要があります。必要なCSSは``oepetstore/static/src/css/petstore.css`` でなければなりません。

HomePage のウィジェットを半ダースの製品で使用します。

ウィジェットヘルパー

Widget の jQuery Selector

ウィジェット内で DOM 要素を選択することは、ウィジェットの DOM ルート上の find() メソッドを呼び出すことで行うことができます:

this.$el.find("input.my_input")...

しかし、一般的な操作であるため、 Widget() は :func:`~odoo.Widget.$`メソッドで同等のショートカットを提供します。

local.MyWidget = instance.Widget.extend({
    start: function() {
        this.$("input.my_input")...
    },
});

警告

グローバルjQuery関数 $() は、絶対に必要な場合を除き、*絶対に使用しないでください。ウィジェットのルート上の選択は、ウィジェットとそれにローカルでスコープされます。 しかし、$() を持つ選択はページ/アプリケーションのグローバルなものであり、他のウィジェットやビューの一部と一致する可能性があり、奇数や危険な副作用につながります。 ウィジェットは通常、所有する DOM セクションのみで動作する必要があるため、グローバル選択の原因はありません。

DOMイベントのバインディングを簡単にする

ウィジェット要素では、通常の jQuery イベントハンドラ(例えば .click().change() など)を使用して DOM イベントをバインドしています。

local.MyWidget = instance.Widget.extend({
    start: function() {
        var self = this;
        this.$(".my_button").click(function() {
            self.button_clicked();
        });
    },
    button_clicked: function() {
        ..
    },
});

これは動作しますが、いくつかの問題があります:

  1. どちらかと言うと

  2. バインディングは start() が実行されたとき(ウィジェットの初期化中)にのみ実行されるため、ウィジェットのルート要素の置き換えはサポートされません。

  3. ``this``バインディングの問題を扱う必要があります

このようにウィジェットは、 :attr:`~odoo.Widget.events`を介してDOMイベントバインディングへのショートカットを提供します。

local.MyWidget = instance.Widget.extend({
    events: {
        "click .my_button": "button_clicked",
    },
    button_clicked: function() {
        ..
    }
});

events は、イベントがトリガーされたときに呼び出す関数またはメソッドへのイベントのオブジェクト (マッピング) です。

  • キーはイベント名です おそらくCSSセレクタで洗練され、選択されたサブ要素でイベントが発生した場合にのみ関数またはメソッドが実行されます。click はウィジェット内のすべてのクリックを処理します。 でも``click . y_button`` は my_button クラスを持つ要素内のクリックのみを処理します

  • 値は、イベントがトリガーされたときに実行するアクション

    これは関数のいずれかになります。

    events: {
        'click': function (e) { /* code here */ }
    }
    

    またはオブジェクト上のメソッドの名前 (上記の例を参照してください)。

    いずれの場合も、this はウィジェットインスタンスであり、ハンドラにはイベントの jQuery イベントオブジェクト というパラメータが与えられます。

ウィジェットのイベントとプロパティ

イベント

ウィジェットはイベントシステムを提供します (上記のDOM/jQueryイベントシステムとは別に): ウィジェットはそれ自体でイベントを発生させることができます。 そして他のウィジェット(またはそれ自体)は、自分自身をバインドして、これらのイベントをリッスンできます。

local.ConfirmWidget = instance.Widget.extend({
    events: {
        'click button.ok_button': function () {
            this.trigger('user_chose', true);
        },
        'click button.cancel_button': function () {
            this.trigger('user_chose', false);
        }
    },
    start: function() {
        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>");
    },
});

このウィジェットはファサードとして機能し、(DOM イベントを通じて) ユーザー入力を親ウィジェットが自分自身をバインドできるドキュメント可能な内部イベントに変換します。

:func:`~odoo.Widget. rigger`は最初の(必須)引数としてトリガーするイベントの名前を取ります これ以上の引数はイベントデータとして扱われリスナーに直接渡されます。

その後、一般的なウィジェットをインスタンス化した親イベントを設定し、 on`を使用して ``user_chose`() イベントをリッスンすることができます:

local.HomePage = instance.Widget.extend({
    start: function() {
        var widget = new local.ConfirmWidget(this);
        widget.on("user_chose", this, this.user_chose);
        widget.appendTo(this.$el);
    },
    user_chose: function(confirm) {
        if (confirm) {
            console.log("The user agreed to continue");
        } else {
            console.log("The user refused to continue");
        }
    },
});

on() binds a function to be called when the event identified by event_name is. The func argument is the function to call and object is the object to which that function is related if it is a method. The bound function will be called with the additional arguments of trigger() if it has any. Example:

start: function() {
    var widget = ...
    widget.on("my_event", this, this.my_event_triggered);
    widget.trigger("my_event", 1, 2, 3);
},
my_event_triggered: function(a, b, c) {
    console.log(a, b, c);
    // will print "1 2 3"
}

注釈

他のウィジェットでイベントをトリガーすることは一般的に悪い考えです。このルールの主な例外は odoo.web です。 ブロードキャストに具体的に存在するus は、任意のウィジェットがOdoo Webアプリケーション全体に興味を持っている可能性があります。

属性

プロパティはウィジェットインスタンスにデータを格納できるという点で、通常のオブジェクト属性と非常によく似ています。 ただし、設定時にイベントをトリガーする追加機能があります。

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"));
}
  • set() sets the value of a property and triggers change:propname (where propname is the property name passed as first parameter to set()) and change

  • get() はプロパティの値を取得します。

演習

Exercise

ウィジェットのプロパティとイベント

Create a widget ColorInputWidget that will display 3 <input type="text">. Each of these <input> is dedicated to type a hexadecimal number from 00 to FF. When any of these <input> is modified by the user the widget must query the content of the three <input>, concatenate their values to have a complete CSS color code (ie: #00FF00) and put the result in a property named color. Please note the jQuery change() event that you can bind on any HTML <input> element and the val() method that can query the current value of that <input> could be useful to you for this exercise.

次に、HomePage ウィジェットを変更して ColorInputWidget をインスタンス化して表示します。HomePage ウィジェットも空の長方形を表示する必要があります。 その長方形はいつでも、ColorInputWidget インスタンスの color プロパティの色と同じ背景色を持つ必要があります。

QWeb を使用してすべての HTML を生成します。

既存のウィジェットとクラスを変更する

Odoo Webフレームワークのクラス・システムでは、 include() メソッドを使用して既存のクラスを直接変更できます。

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"

このシステムは継承メカニズムと似ていますが、新しいクラスを作成する代わりにターゲットクラスが変更されます。

In that case, this._super() will call the original implementation of a method being replaced/redefined. If the class already had sub-classes, all calls to this._super() in sub-classes will call the new implementations defined in the call to include(). This will also work if some instances of the class (or of any of its sub-classes) were created prior to the call to include().

翻訳

PythonとJavaScriptのコードでテキストを翻訳するプロセスはとても似ています。 これらの行は petstore.js ファイルの先頭にあります。

var _t = instance.web._t,
    _lt = instance.web._lt;

これらの行は、現在のJavaScriptモジュールの翻訳関数をインポートするために使用されます。

this.$el.text(_t("Hello user!"));

Odooでは、翻訳ファイルはソースコードをスキャンすることで自動的に生成されます。 特定の関数を呼び出すすべてのコードが検出され、その内容が翻訳者に送信される翻訳ファイルに追加されます。 Python では _() です。JavaScript では _t() (および _lt() )です。

_t() は与えられたテキストに対して定義された翻訳を返します。 そのテキストに対して翻訳が定義されていない場合は、元のテキストをそのまま返します。

注釈

翻訳可能な文字列にユーザーが提供する値を注入するには、 _.str.sprintf という名前の引数を*翻訳後*で使うことをお勧めします:

this.$el.text(_.str.sprintf(
    _t("Hello, %(user)s!"), {
    user: "Ed"
}));

これにより、翻訳可能な文字列が翻訳者に読みやすくなり、パラメータの順序変更や無視がより柔軟になります。

:func:`~odoo.web. lt`("lazy translate")は似ていますが、もう少し複雑です。パラメータを即座に変換する代わりに。 これは、文字列に変換されると翻訳を行うオブジェクトを返します。

翻訳システムを初期化する前に、翻訳可能な用語を定義するために使用されます。 for class attributes for instance(as modules are loaded before the user's language and translation are downloaded).

Odoo サーバーとの通信

モデルに連絡中

Odoo とのほとんどの操作では、ビジネス上の懸念を実装する models と通信する必要があります。これらのモデルは(潜在的に)いくつかのストレージエンジン(通常は PostgreSQL)と相互作用します。

jQuery$.ajax 関数をネットワークの相互作用に提供しますが、Odoo と通信するには、すべての呼び出しが冗長でエラーが発生しやすいように設定する追加のメタデータが必要です。 その結果、Odooウェブは高レベルの通信プリミティブを提供します。

これを実証するために、ファイル petstore.py にはサンプルメソッドを持つ小さなモデルがすでに含まれています。

class message_of_the_day(models.Model):
    _name = "oepetstore.message_of_the_day"

    @api.model
    def my_method(self):
        return {"hello": "world"}

    message = fields.Text(),
    color = fields.Char(size=20),

これは2つのフィールドを持つモデルと、リテラル辞書を返すメソッド my_method() を宣言します。

以下は、my_method() を呼び出し、結果を表示するサンプルウィジェットです。

local.HomePage = instance.Widget.extend({
    start: function() {
        var self = this;
        var model = new instance.web.Model("oepetstore.message_of_the_day");
        model.call("my_method", {context: new instance.web.CompoundContext()}).then(function(result) {
            self.$el.append("<div>Hello " + result["hello"] + "</div>");
            // will show "Hello world" to the user
        });
    },
});

Odoo モデルを呼び出すために使用されるクラスは odoo.Model() です。Odoo モデルの名前を最初のパラメータ (oepetstore.message_of_the_day )としてインスタンス化します。

call() は、Odooモデルの任意の(public)メソッドを呼び出すために使用できます。以下の位置の引数を取ります。

name

呼び出すメソッドの名前 my_method

args

メソッドに提供する positional arguments の配列。例に与える位置引数がないため、args パラメータは提供されません。

位置引数を持つ他の例を示します。

@api.model
def my_method2(self, a, b, c): ...
model.call("my_method", [1, 2, 3], ...
// with this a=1, b=2 and c=3
kwargs

keyword 引数 のマッピングを渡します。この例では単一の名前付き引数 context を提供します。

@api.model
def my_method2(self, a, b, c): ...
model.call("my_method", [], {a: 1, b: 2, c: 3, ...
// with this a=1, b=2 and c=3

call() はモデルのメソッドが最初の引数として返す値を返します。

CompoundContext

前のセクションではメソッド呼び出しで説明されていなかった context 引数を使用しています。

model.call("my_method", {context: new instance.web.CompoundContext()})

コンテキストは、メソッドを呼び出すときにWebクライアントが常にサーバーに与える「魔法」引数のようなものです。 コンテキストは複数のキーを含む辞書です。 最も重要なキーの一つは、アプリケーションのすべてのメッセージを翻訳するためにサーバーによって使用されるユーザーの言語です。 別のものは、Odooが異なる国の人々によって使用されている場合、正しく日付と時刻を計算するために使用されるユーザーのタイムゾーンです。

argument はすべてのメソッドで必要です。そうでなければ、悪いことが起こる可能性があります (アプリケーションが正しく翻訳されていないなど)。 そのため、モデルのメソッドを呼び出すときは、常にその引数を指定する必要があります。その解決策は、 odoo.web.CompoundContext() を使用することです。

CompoundContext() is a class used to pass the user's context (with language, time zone, etc...) to the server as well as adding new keys to the context (some models' methods use arbitrary keys added to the context). It is created by giving to its constructor any number of dictionaries or other CompoundContext() instances. It will merge all those contexts before sending them to the server.

model.call("my_method", {context: new instance.web.CompoundContext({'new_key': 'key_value'})})
@api.model
def my_method(self):
    print(self.env.context)
    // will print: {'lang': 'en_US', 'new_key': 'key_value', 'tz': 'Europe/Brussels', 'uid': 1}

引数の context には、Odoo の現在のユーザーの設定に関連するキーと odooのインスタンス化時に追加された ``new_key`() キーが含まれています。 eb.CompoundContext`

クエリ

While call() is sufficient for any interaction with Odoo models, Odoo Web provides a helper for simpler and clearer querying of models (fetching of records based on various conditions): query() which acts as a shortcut for the common combination of search() and :read(). It provides a clearer syntax to search and read models:

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
});

versus:

model.call('search', [['active', '=', true], ['company_id', '=', main_company]], {limit: 15})
    .then(function (ids) {
        return model.call('read', [ids, ['name', 'login', 'user_email', 'signature']]);
    })
    .then(function (users) {
        // do work with users records
    });
  • query() takes an optional list of fields as parameter (if no field is provided, all fields of the model are fetched). It returns a odoo.web.Query() which can be further customized before being executed

  • Query() represents the query being built. It is immutable, methods to customize the query actually return a modified copy, so it's possible to use the original and the new version side-by-side. See Query() for its customization options.

クエリが必要に応じて設定された場合は、単純に Queryを呼び出します。 ll() を実行し、結果に遅延を返します。結果は read()'sは、各辞書が要求されたレコードで、要求されたフィールドごとに辞書キーを持つ辞書の配列です。

演習

Exercise

今日のメッセージ

oepetstore.message_of_the_day モデルの最後のレコードを表示する MessageOfTheDay ウィジェットを作成します。ウィジェットは表示されるとすぐにレコードを取得します。

ウィジェットをペットストアのホームページに表示します。

Exercise

ペットのおもちゃリスト

5つのおもちゃを表示する PetToysList ウィジェットを作成します (名前と画像を使用)。

ペットのおもちゃは新しいモデルには保存されず、特別なカテゴリ ペット玩具 を使用して product.product に保存されます。 Pet Store ‣ Pet Store ‣ Pet Toys に移動すると、あらかじめ生成されたおもちゃを見ることができます。 ペットのおもちゃだけを選択するために適切なドメインを作成するには product.product を調べる必要があります。

In Odoo, images are generally stored in regular fields encoded as base64, HTML supports displaying images straight from base64 with <img src="data:mime_type;base64,base64_image_data"/>

MessageOfTheDay ウィジェットの右側にあるホームページに PetToysList ウィジェットを表示します。 これを実現するには、CSS でいくつかのレイアウトを作成する必要があります。

既存のWebコンポーネント

アクションマネージャー

Odoo では、多くの操作は :doc:`action <../reference/backend/actions>`から始まります: メニューアイテムを開いて(ビューに)、レポートを印刷します。

アクションとは、クライアントがコンテンツの一部を有効化する際にどのように反応するかを記述するデータのことです。 アクションを保存して(モデルを介して読み込む)、またはその場で生成することもできます(javascriptコードによってクライアントにローカルで)。 あるいはモデルの方法で遠隔操作することができます

Odoo Web では、これらのアクションの処理と反応を担当するコンポーネントは Action Manager です。

アクションマネージャーの使用

アクションマネージャは、 アクション とアクションマネージャーインスタンスを呼び出します。

do_action()Widget() のショートカットで、「現在の」アクションマネージャーを検索してアクションを実行します:

instance.web.TestWidget = instance.Widget.extend({
    dispatch_to_new_action: function() {
        this.do_action({
            type: 'ir.actions.act_window',
            res_model: "product.product",
            res_id: 1,
            views: [[false, 'form']],
            target: 'current',
            context: {},
        });
    },
});

最も一般的なアクション typeir.actions.actions.action_window で、モデルにビューを提供します (様々な方法でモデルを表示します)、最も一般的な属性は次のとおりです。

res_model

ビューに表示するモデル

res_id (任意)

フォームビューの場合、res_model で事前に選択されたレコードがあります

views

アクションで使用可能なビューが一覧表示されます。 [view_id, view_type], view_id のリストは、適切な型のビューのデータベース識別子になります。 指定された型にデフォルトでビューを使用するには false を使用します。 ビュータイプは複数回存在することはできません。アクションはデフォルトでリストの最初のビューを開きます。

target

ウェブクライアントの「コンテンツ」セクションをアクションで置き換える``current`` (デフォルト) ダイアログボックスでアクションを開くには、 new を使用します。

context

アクション内で使用する追加のコンテキスト データ。

Exercise

商品にジャンプ

PetToysList コンポーネントを変更すると、おもちゃをクリックすると、ホームページがフォームビューで置き換えられます。

クライアントアクション

このガイドでは、右のメニュー項目を選択するとウェブクライアントが自動的に開始するシンプルな HomePage ウィジェットを使用しました。 しかし、どのようにしてOdooウェブはこのウィジェットを開始することが分かったのでしょうか? ウィジェットは*クライアントアクション*として登録されているからです。

クライアントアクションは(その名前が示すように)クライアントでほぼ完全に定義されたアクションタイプで、Odoo Web の javascript です。 サーバは単にアクションタグ(任意の名前)を送信し、必要に応じていくつかのパラメータを追加します。 しかし、それを超えた everything はカスタムクライアントコードによって処理されます。

ウィジェットは、これを介してクライアントアクションのハンドラとして登録されています。

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

instance.web.client_actions is a Registry() in which the action manager looks up client action handlers when it needs to execute one. The first parameter of add() is the name (tag) of the client action, and the second parameter is the path to the widget from the Odoo web client root.

クライアントアクションを実行する必要がある場合、アクションマネージャはレジストリ内のタグを検索します。 指定したパスを歩き、最後に見つけたウィジェットが表示されます。

注釈

クライアントアクションハンドラは通常の関数にすることもできます。 その場合、それが呼び出され、その結果(もしあれば)は次の実行アクションとして解釈されます。

サーバー側では、 ir.actions.client アクションを定義しました。

<record id="action_home_page" model="ir.actions.client">
    <field name="tag">petstore.homepage</field>
</record>

そして、アクションを開くメニュー:

<menuitem id="home_page_petstore_menu" parent="petstore_menu"
          name="Home Page" action="action_home_page"/>

ビューのアーキテクチャ

多くの Odoo Web の有用性(および複雑性)はビューに存在します。各ビュータイプは、クライアントにモデルを表示する方法です。

ビューマネージャー

ActionManager インスタンスが ir.actions 型のアクションを受け取った場合。 ct_window はビュー自体の同期と処理を ビューマネージャー に委任します。 その後、元のアクションの要件に応じて1つまたは複数のビューを設定します。

../../_images/viewarchitecture.png

ビュー

ほとんどの :doc:`Odoo views <../reference/user_interface/view_records>`は :class:`odooのサブクラスを介して実装されています。 イベントを処理したり、モデル情報を表示したりするための一般的な基本構造を提供する eb.View。

検索ビュー は、メインの Odoo フレームワークによってビュータイプと見なされます。 しかし、Webクライアントによって別々に処理されます(それはより永続的なフィクスチャーであり、他のビューとやり取りすることができます)。

ビューは、独自の説明 XML (fields_view_get)や必要なその他のデータソースをロードする責任があります。 そのために、ビューには view_id 属性として設定されたオプションのビュー識別子が用意されています。

ビューには、最も必要なモデル情報(モデル名とおそらく様々なレコードID)を保持する :class:`~odoo.web.DataSet`インスタンスも用意されています。

ビューは、 do_search() をオーバーライドし、必要に応じて DataSet() を更新することで、検索クエリを処理することもできます。

フォーム表示フィールド

一般的な必要性は、項目を表示する新しい方法を追加するためのWebフォームビューの拡張です。

すべての組み込みフィールドにはデフォルトの表示実装があります。新しいフォームウィジェットは、新しいフィールドタイプと正しく対話するために必要な場合があります (例えば。 GIS フィールド) または、既存のフィールドタイプと対話する新しい表現や方法を提供します (例: validate Char fields。メールアドレスを含み、メールリンクとして表示します。

フィールドを表示するために使用するフォームウィジェットを明示的に指定するには、ビューの XML 記述内で widget 属性を使用します。

<field name="contact_mail" widget="email"/>

注釈

  • フォームビューの「ビュー」モードと「編集」モードの両方で同じウィジェットが使用されます。 一方のウィジェットと他方のウィジェットを使うことはできません

  • と指定されたフィールド(名前)は同じ形式で複数回使用することはできません

  • ウィジェットは、フォームビューの現在のモードを無視し、ビューと編集の両方のモードで同じままにすることができます

フィールドは XML 記述を読み込み、その説明を表す対応する HTML を構築した後にフォームビューによってインスタンス化されます。 その後、フォームビューはいくつかのメソッドを使用してフィールドオブジェクトと通信します。 これらのメソッドは FieldInterface インターフェイスで定義されています。ほとんどすべてのフィールドは AbstractField 抽象クラスを継承しています。 このクラスは、ほとんどのフィールドで実装される必要のあるいくつかのデフォルトメカニズムを定義します。

フィールドクラスの責任は次のとおりです。

  • フィールドクラスは、ユーザがフィールドの値を編集できるようにする必要があります。

  • Odooのすべてのフィールドで利用可能な3つのフィールド属性を正しく実装する必要があります。 AbstractField クラスはすでにこれらの属性の値を動的に計算するアルゴリズムを実装しています (他のフィールドの値に応じて値が変更されるため、いつでも変更できます)。 値は ウィジェットのプロパティ に保存されます (ウィジェットのプロパティはこのガイドで説明しています)。 これらのウィジェットのプロパティをチェックし、値に応じて動的に適応するのは、各フィールドクラスの責任です。 これらの属性の説明は以下のとおりです。

    • required: 保存する前に値を持たなければなりません。 requiredtrue で、フィールドに値がない場合、フィールドの is_valid() メソッドは false を返す必要があります。

    • invisible: true の場合、フィールドは不可視でなければなりません。AbstractField クラスには、ほとんどのフィールドに適合するこの動作の基本的な実装がすでにあります。

    • readonly: true の場合、フィールドはユーザーによって編集できません。 Odoo のほとんどのフィールドは、readonly の値によって全く異なる動作をします。 例えば、 FieldChar は編集可能な場合は HTML の<input>`` を表示し、読み取り専用の場合はテキストを表示します。 これはまた、1つの動作のみを実装する必要がある多くのコードを持っていることを意味します。 しかし、これは良いユーザーエクスペリエンスを確保するために必要です。

  • フィールドには set_value()get_value() の 2 つのメソッドがあります。 これはフォームビューによって呼び出され、ユーザーによって入力された新しい値を表示および取り戻すために値を与えます。 これらのメソッドは、モデル上で read() が実行され、write() の有効な値が返された場合、Odoo サーバーによって与えられた値を扱うことができなければなりません。 read() で与えられ、write() に与えられた値を表現するために使用される JavaScript/Python データ型は、Odoo では必ずしも同じではないことを覚えておいてください。 例えば、many21eを読むと、 これは常にタプルで、最初の値はポイントされたレコードの id で、2番目の値は get です(例: (15, "Agrolait")). しかし、many211 を書くときは、タプルではなく、単一の整数でなければなりません。 AbstractField はこれらのメソッドのデフォルトの実装で、単純なデータ型でうまく機能し、value という名前のウィジェットプロパティを設定します。

フィールドの実装方法をよりよく理解するために、注意してください。 FieldInterface および AbstractField クラスの定義を Odoo Web クライアントのコードで直接見ることを強くお勧めします。

新しいタイプのフィールドの作成

このパートでは、新しいタイプのフィールドを作成する方法を説明します。 この例では、FieldChar クラスを再実装し、各パートについて徐々に説明します。

単純な読み取り専用フィールド

テキストのみを表示する最初の実装です。ユーザーはフィールドの内容を変更することはできません。

local.FieldChar2 = instance.web.form.AbstractField.extend({
    init: function() {
        this._super.apply(this, arguments);
        this.set("value", "");
    },
    render_value: function() {
        this.$el.text(this.get("value"));
    },
});

instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');

この例では、AbstractField から継承した FieldChar2 という名前のクラスを宣言します。また、レジストリの instance.web.formにもこのクラスを登録します。 ``char2 キーの下にある idgets`` 。 これにより、ビューのXML宣言に``<field/>``タグに``widget="char2"``を指定することで、任意のフォームビューでこの新しいフィールドを使用することができます。

この例では、単一のメソッドである render_value() を定義します。すべてウィジェットのプロパティ value を表示します。 これらは AbstractField クラスで定義された 2 つのツールです。 前に説明したように、フォームビューでは、表示する値を設定するためにフィールドの set_value() メソッドを呼び出します。 このメソッドは AbstractField に既定の実装があり、単純に value ウィジェットのプロパティを設定します。 AbstractFieldchange:value イベント自体を見て、render_value() が発生したときに呼び出します。 ですから、render_value() は、フィールドの値が変更されるたびに何らかの操作を実行するための便利なメソッドです。

init() メソッドでは、 フォームビューで何も指定されていない場合、フィールドのデフォルト値も定義します(ここでは、char フィールドのデフォルト値は空の文字列であると仮定します)。

読み書きフィールド

読み取り専用項目で、コンテンツのみを表示し、ユーザに変更を許可しない場合がありますが、Odoo のほとんどの項目でも編集ができます。 これにより、フィールドクラスはより複雑になります。主に、フィールドは編集可能なモードと編集なしモードの両方を扱うことになっています。 それらのモードはしばしば完全に異なります(デザインと使いやすさのために)。フィールドはいつでもモードを切り替えることができなければなりません。

現在のフィールドがどのモードになるかを知るために、AbstractField クラスは effective_readonly という名前のウィジェットプロパティを設定します。 フィールドはそのウィジェットプロパティの変更を監視し、それに応じて正しいモードを表示する必要があります。

local.FieldChar2 = instance.web.form.AbstractField.extend({
    init: function() {
        this._super.apply(this, arguments);
        this.set("value", "");
    },
    start: function() {
        this.on("change:effective_readonly", this, function() {
            this.display_field();
            this.render_value();
        });
        this.display_field();
        return this._super();
    },
    display_field: function() {
        var self = this;
        this.$el.html(QWeb.render("FieldChar2", {widget: this}));
        if (! this.get("effective_readonly")) {
            this.$("input").change(function() {
                self.internal_set_value(self.$("input").val());
            });
        }
    },
    render_value: function() {
        if (this.get("effective_readonly")) {
            this.$el.text(this.get("value"));
        } else {
            this.$("input").val(this.get("value"));
        }
    },
});

instance.web.form.widgets.add('char2', 'instance.oepetstore.FieldChar2');
<t t-name="FieldChar2">
    <div class="oe_field_char2">
        <t t-if="! widget.get('effective_readonly')">
            <input type="text"></input>
        </t>
    </div>
</t>

start() メソッド(ウィジェットが DOM に追加された直後に呼び出されます)では、change:effective_readonly イベントをバインドします。 これにより、ウィジェット プロパティ effective_readonly が変更されるたびにフィールドを再表示できます。 このイベントハンドラは display_field() を呼び出します。これは start() で直接呼び出されます。 この display_field() はこのフィールドのために特別に作成されました。AbstractField や他のクラスで定義されているメソッドではありません。 このメソッドを使用すると、現在のモードに応じてフィールドの内容を表示できます。

これからこのフィールドの概念は一般的ですが、effecte_readonly プロパティの状態を知るための検証がたくさんあります。

  • ウィジェットのコンテンツを表示するために使用される QWeb テンプレートで。 読み取り専用モードでは特に何もない場合は、<input type="text" /> を表示します。

  • In the display_field() method, we have to bind on the change event of the <input type="text" /> to know when the user has changed the value. When it happens, we call the internal_set_value() method with the new value of the field. This is a convenience method provided by the AbstractField class. That method will set a new value in the value property but will not trigger a call to render_value() (which is not necessary since the <input type="text" /> already contains the correct value).

  • render_value() では、読み取り専用または読み取り/書き込みモードに応じてフィールドの値を表示するために完全に異なるコードを使用します。

Exercise

カラーフィールドを作成

Create a FieldColor class. The value of this field should be a string containing a color code like those used in CSS (example: #FF0000 for red). In read-only mode, this color field should display a little block whose color corresponds to the value of the field. In read-write mode, you should display an <input type="color" />. That type of <input /> is an HTML5 component that doesn't work in all browsers but works well in Google Chrome. So it's OK to use as an exercise.

このウィジェットは color という名前のフィールドの message_of_the_day モデルのフォームビューで使用できます。 ボーナスとして このガイドの前の部分で作成された MessageOfTheDay ウィジェットを変更して、color フィールドに表示される背景色でその日のメッセージを表示できます。

フォームビューのカスタムウィジェット

フォームフィールドは、単一のフィールドを編集するために使用され、フィールドに本質的にリンクされます。 制限されているかもしれないからです また、特定のライフサイクルとのつながりが少ない*フォームウィジェット*を作成することもできます。

カスタム フォーム ウィジェットは widget タグを使用してフォームビューに追加できます。

<widget type="xxx" />

このタイプのウィジェットは、XML 定義に従った HTML の作成時にフォーム ビューによって作成されます。 これらはフィールドと共通のプロパティを持ちます (effecte_readonly プロパティなど)。 ですから、get_value()set_value() のようなメソッドは持っていません。FormWidget 抽象クラスを継承する必要があります。

フォームウィジェットは、変更をリッスンしたり、値を取得したり変更したりすることによって、フォームフィールドとやり取りすることができます。 field_manager 属性からフォームフィールドにアクセスできます:

local.WidgetMultiplication = instance.web.form.FormWidget.extend({
    start: function() {
        this._super();
        this.field_manager.on("field_changed:integer_a", this, this.display_result);
        this.field_manager.on("field_changed:integer_b", this, this.display_result);
        this.display_result();
    },
    display_result: function() {
        var result = this.field_manager.get_field_value("integer_a") *
                     this.field_manager.get_field_value("integer_b");
        this.$el.text("a*b = " + result);
    }
});

instance.web.form.custom_widgets.add('multiplication', 'instance.oepetstore.WidgetMultiplication');

FormWidget is generally the FormView() itself, but features used from it should be limited to those defined by FieldManagerMixin(), the most useful being:

  • get_field_value(field_name)() はフィールドの値を返します。

  • set_values(values)`は複数のフィールドの値を設定し、`{field_name: value_to_set}`() のマッピングを取ります。

  • イベント field_changed:field_name`は、``field_name` と呼ばれるフィールドの値が変更されるといつでもトリガーされます。

Exercise

Googleマップに座標を表示する

product に2つのフィールドを追加します。 緯度と経度を保存する roduct 次に、新しいフォームウィジェットを作成し、地図上に製品の原点の緯度と経度を表示します。

マップを表示するには、Google マップの埋め込みを使用します。

<iframe width="400" height="300" src="https://maps.google.com/?ie=UTF8&amp;ll=XXX,YYY&amp;output=embed">
</iframe>

XXX を緯度で置き換え、YYY を経度で置き換えます。

商品のフォームビューの新しいノートブックページで、2 つの位置フィールドと地図ウィジェットを使用して表示します。

Exercise

現在の座標を取得する

製品の座標をユーザーの場所にリセットするボタンを追加します。javascript geolocation API を使用してこれらの座標を取得できます。

現在のユーザーの位置に座標を自動的に設定するための追加ボタンを表示します。

ユーザーの座標を取得するために、簡単な方法は、位置情報の JavaScript API を使用することです。 `` の使い方については、オンラインドキュメントを参照してください。

また、フォームビューが読み取り専用モードの場合、ユーザーはそのボタンをクリックすることができないことに注意してください。 したがって、このカスタム ウィジェットは任意のフィールドと同様に effective_readonly プロパティを正しく処理する必要があります。 これを行う一つの方法は、 effecte_readonly が true の場合にボタンを消去することです。

1

別の概念として使われています 多くの言語では、(メタクラスの)完全なオブジェクトとそれ自身インスタンスがありますが、クラスとインスタンス間にはかなり分離された2つの階層が存在します。

2

ブラウザー間の違いに紙張りをするのと同様に時間の経過とともに必要性は低くなっています