ウェブクライアントのカスタマイズ¶
危険
このチュートリアルは時代遅れです。
このガイドでは、Odoo の Web クライアント用のモジュールを作成します。
Odoo でウェブサイトを作成するには、 ウェブサイトのテーマを作成 を参照してください。ビジネス機能を追加したり、Odoo の既存のビジネスシステムを拡張したりするには、 モジュールの構築 を参照してください。
シンプルなモジュール¶
基本的な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/css
と src/js
の内容は petstore.xml
で定義されています。
警告
すべてのJavaScriptファイルは連結され、 minified はアプリケーションの読み込み時間を向上させます。
個々のファイルが消失し、コードが読めなくなるにつれて、デバッグが難しくなることが欠点の1つです。 「開発者モード」を有効にすることで、このプロセスを無効にすることができます。Odoo インスタンス(デフォルトではユーザー admin パスワード admin )にログインし、ユーザーメニュー(Odoo 画面の右上隅)を開き、 About Odo と 開発者モードを有効にする を選択します。


これにより、ウェブクライアントは再読み込みされ、最適化は無効になり、開発とデバッグは大幅に快適になります。
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ツールキット(Qt、Cocoa、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');
基本的なウィジェットをクライアントアクションとして登録します。 クライアントアクションは後で説明されます 今のところこれは、
メニューを選択したときにウィジェットを呼び出して表示することを可能にするものです。警告
ウィジェットは私たちのモジュールの外から呼び出されるため、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>");
},
});
このメッセージは、
を開いたときに表示されます。注釈
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>");
},
});
GreetingsWidget
の appendTo()
メソッドを使用して、HomePage
に GreetingsWidget
を追加することができます:
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 ルートに独自のコンテンツを追加しますHomePage
はGreetingsWidget
をインスタンス化しますFinally it tells
GreetingsWidget
where to insert itself, delegating part of its$el
to theGreetingsWidget
.
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 は Genshi や Thymeleaf, や 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 useWidget()
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-foreach
と t-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-name
と t-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_names
と color
。
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() {
..
},
});
これは動作しますが、いくつかの問題があります:
どちらかと言うと
バインディングは
start()
が実行されたとき(ウィジェットの初期化中)にのみ実行されるため、ウィジェットのルート要素の置き換えはサポートされません。``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 triggerschange:propname
(where propname is the property name passed as first parameter toset()
) andchange
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 aodoo.web.Query()
which can be further customized before being executedQuery()
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. SeeQuery()
for its customization options.
クエリが必要に応じて設定された場合は、単純に Queryを呼び出します。 ll()
を実行し、結果に遅延を返します。結果は read()
'sは、各辞書が要求されたレコードで、要求されたフィールドごとに辞書キーを持つ辞書の配列です。
演習¶
Exercise
今日のメッセージ
oepetstore.message_of_the_day
モデルの最後のレコードを表示する MessageOfTheDay
ウィジェットを作成します。ウィジェットは表示されるとすぐにレコードを取得します。
ウィジェットをペットストアのホームページに表示します。
Exercise
ペットのおもちゃリスト
5つのおもちゃを表示する PetToysList
ウィジェットを作成します (名前と画像を使用)。
ペットのおもちゃは新しいモデルには保存されず、特別なカテゴリ ペット玩具 を使用して product.product
に保存されます。 に移動すると、あらかじめ生成されたおもちゃを見ることができます。 ペットのおもちゃだけを選択するために適切なドメインを作成するには 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: {},
});
},
});
最も一般的なアクション type
は ir.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つまたは複数のビューを設定します。

ビュー¶
ほとんどの :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
: 保存する前に値を持たなければなりません。required
がtrue
で、フィールドに値がない場合、フィールドの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
ウィジェットのプロパティを設定します。 AbstractField
は change: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 thechange
event of the<input type="text" />
to know when the user has changed the value. When it happens, we call theinternal_set_value()
method with the new value of the field. This is a convenience method provided by theAbstractField
class. That method will set a new value in thevalue
property but will not trigger a call torender_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&ll=XXX,YYY&output=embed">
</iframe>
XXX
を緯度で置き換え、YYY
を経度で置き換えます。
商品のフォームビューの新しいノートブックページで、2 つの位置フィールドと地図ウィジェットを使用して表示します。
Exercise
現在の座標を取得する
製品の座標をユーザーの場所にリセットするボタンを追加します。javascript geolocation API を使用してこれらの座標を取得できます。
現在のユーザーの位置に座標を自動的に設定するための追加ボタンを表示します。
ユーザーの座標を取得するために、簡単な方法は、位置情報の JavaScript API を使用することです。 `` の使い方については、オンラインドキュメントを参照してください。
また、フォームビューが読み取り専用モードの場合、ユーザーはそのボタンをクリックすることができないことに注意してください。 したがって、このカスタム ウィジェットは任意のフィールドと同様に effective_readonly
プロパティを正しく処理する必要があります。 これを行う一つの方法は、 effecte_readonly
が true の場合にボタンを消去することです。