第1章:フクロウコンポーネント¶
This chapter introduces the Owl framework, a tailor-made component system for Odoo. The main building blocks of OWL are components and templates.
フクロウ中 ユーザーインターフェイスのすべての部分はコンポーネントによって管理されています。ロジックを保持し、ユーザーインターフェイスをレンダリングするために使用されるテンプレートを定義します。 実際には、コンポーネントは Component
クラスをサブクラス化した小さなJavaScriptクラスによって表現されます。
始めるには、実行中の Odoo サーバーと開発環境のセットアップが必要です。 練習問題に入る前に、この :ref:`tutorial <tutorials/discover_js_framework/setup> `で説明されているすべてのステップに従っていることを確認してください。
ちなみに
Chromeをウェブブラウザとして使用している場合は、`Owl Devtools`拡張機能をインストールできます。 この拡張機能は Owl アプリケーションを理解し、プロファイルするのに役立つ多くの機能を提供します。
この章では、awesome_owl
addonを使います。これは、フクロウと他のいくつかのファイルしか含まれていない簡素化された環境を提供します。 目標は、Odoo Webクライアントコードに頼らずに、Owl自体を学ぶことです。
章の各演習の解決策は、 official Odoo tutorials repository にホストされています。 解を見ずに最初に解いてみることをお勧めします!
例: Counter
コンポーネント¶
まず、簡単な例を見てみましょう。 Counter
コンポーネントは内部番号の値を保持するコンポーネントです。 が表示され、ユーザーがボタンをクリックするたびに更新されます。
import { Component, useState } from "@odoo/owl";
export class Counter extends Component {
static template = "my_module.Counter";
setup() {
this.state = useState({ value: 0 });
}
increment() {
this.state.value++;
}
}
Counter
コンポーネントは、htmlを表すテンプレートの名前を指定します。これは、QWeb言語を使用してXMLで書かれます。
<templates xml:space="preserve">
<t t-name="my_module.Counter">
<p>Counter: <t t-esc="state.value"/></p>
<button class="btn btn-primary" t-on-click="increment">Increment</button>
</t>
</templates>
1. カウンターの表示¶

最初の課題として、 awesome_owl/static/src/`にある`Playground`コンポーネントを変更してカウンタにしましょう。 結果を確認するには、ブラウザで `/awesome_owl
route (ルート)に移動します。
Modify
playground.js
so that it acts as a counter like in the example above. KeepPlayground
for the class name. You will need to use the useState hook so that the component is re-rendered whenever any part of the state object that has been read by this component is modified.同じコンポーネントで
increment
メソッドを作成します。playground.xml
内のテンプレートを変更してカウンター変数を表示します。データを出力するには、t-esc を使用します。テンプレートにボタンを追加し、ボタンをクリックするたびに`increment`メソッドをトリガーするために、ボタンに`t-on-click <https://github.com/odoo/owl/blob/master/doc/reference/event_handling.md#event-handling>`_ 属性を指定します。
ちなみに
ブラウザーがダウンロードした Odoo JavaScript ファイルはミニファイされています。デバッグ目的では、ファイルがミニファイされていない場合の方が簡単です。 assets のデバッグモードに切り替えます。
この課題では、反応システム という Owl の重要な機能を示します。 useState
関数はプロキシに値をラップするため、Owl は state のどの部分が必要かを追跡できます。 値を変えるたびに更新することができます useState
関数を削除して、何が起こるか見てみましょう。
2. サブコンポーネントに Counter
を展開する¶
今のところ、 Playground
コンポーネントにカウンターのロジックがありますが、それは再利用できません。 サブコンポーネント を作成する方法を見てみましょう。
`Playground`コンポーネントからカウンターコードを新しい`Counter`コンポーネントに抽出します。
同じファイルで最初に実行できますが、一度実行されるとします。
Counter`を自分のフォルダとファイルに移動させるには、コードを更新してください。 `./counter/counter
から比較的インポートします。テンプレートが同じ名前のファイルにあることを確認します。`Playground`コンポーネントのテンプレートに`<Counter/>`を使用して、プレイグラウンドに2つのカウンターを追加します。

ちなみに
通常、ほとんどのコンポーネントコード、テンプレート、cssはコンポーネントと同じヘビケースの名前を持つ必要があります。 例えば、TodoList`コンポーネントがある場合、コードは`todo_list.js
、todo_list.xml
、必要に応じて`todo_list.scss`の中にある必要があります。
3. シンプルな「カード」コンポーネント¶
コンポーネントは、複雑なユーザーインターフェイスを複数の再利用可能なピースに分割する最も自然な方法です。 しかし、本当に役に立つようにするためには、それら間の情報を伝える必要があります。 親コンポーネントがどのように属性を使用してサブコンポーネントに情報を提供できるかを見てみましょう (最も一般的には props として知られています)。
この課題の目的は、 Card
コンポーネントを作成することです。これには title
と content
という 2 つのプロパティがあります。 例えば、以下のように使用できます。
<Card title="'my title'" content="'some content'"/>
上記の例では、次のようなブートストラップを使用して html を生成する必要があります。
<div class="card d-inline-block m-2" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">my title</h5>
<p class="card-text">
some content
</p>
</div>
</div>
`Card`コンポーネントを作成
Playground
にインポートして、テンプレートにいくつかのカードを表示します。

4. html を表示するために markup
を使用する¶
If you used t-esc
in the previous exercise, then you may have noticed that Owl automatically escapes
its content. For example, if you try to display some html like this: <Card title="'my title'" content="this.html"/>
with this.html = "<div>some content</div>""
,
the resulting output will simply display the html as a string.
この場合、Card`コンポーネントはあらゆる種類のコンテンツを表示するために使用されることがあります。 ユーザーにhtmlを表示させるのは理にかなっています これは `t-out ディレクティブ で行います。
ただし、任意のコンテンツを html として表示することは危険です。悪意のあるコードを挿入するために使用することができます。 `markup`関数で明示的に安全とマークされていない限り、フクロウは常に文字列をエスケープします。
`t-out`を使用するにはカードを更新してください
Playground
を更新してmarkup
をインポートし、html の値で使用しますマークアップされた文字列とは異なり、通常の文字列が常にエスケープされていることを確認してください。
注釈
t-esc
ディレクティブは Owl テンプレートでも使用できます。t-out
よりも若干速くなります。

5. プロパティの検証¶
The Card
component has an implicit API. It expects to receive two strings in its props: the title
and the content
. Let us make that API more
explicit. We can add a props definition that will let Owl perform a validation step in dev mode. You can activate the dev mode in the App
configuration (but it is activated by default
on the awesome_owl
playground).
すべてのコンポーネントの props の検証を行うことをお勧めします。
props バリデーション を
Card
コンポーネントに追加します。title
プロパティを別のプレイグラウンドテンプレートに変更します。 次に、ブラウザの開発ツールの Console タブでエラーが表示されます。
6. 2つの「カウンター」の合計¶
前回の課題では、props
は親コンポーネントから子コンポーネントに情報を提供するために使用できることがわかりました。 では、どのようにして逆方向に情報を伝えることができるのか見てみましょう。 2つの`Counter`コンポーネントを表示し、その下にその値の合計を表示します。 そのため、親コンポーネント(Playground
)は、いずれかの値が変更されたときに通知される必要があります。
これは callback prop: 呼び出される関数である props を使用することで行うことができます。 子コンポーネントは、任意の引数でその関数を呼び出すことを選択できます。 ここでは、オプションの onChange
プロパティを追加するだけで、Counter
コンポーネントがインクリメントされるたびに呼び出されます。
Counter
コンポーネントにプロパティ検証を追加します。オプションのonChange
関数プロパティを受け付ける必要があります。Counter
コンポーネントを更新して、インクリメントされるたびに`onChange` プロパティを呼び出すようにします。Playground`コンポーネントを変更してローカルの状態値(`sum
)を維持し、最初に2に設定し、テンプレートに表示します。Playground
にincrementSum
メソッドを実装このメソッドを props として 2 つ(またはそれ以上!)サブの
Counter
コンポーネントに渡します。

重要
callback propsを持つsubtletyがあります: 通常は`.bind`サフィックスで定義する必要があります。documentation を参照してください。
7. ToDoリスト¶
ToDoリストを作成することで、フクロウのさまざまな機能を発見しましょう。 2つのコンポーネントが必要です: TodoItem
コンポーネントのリストを表示する TodoList
コンポーネント。 todosのリストは、 TodoList
によって維持されるべき状態です。
このチュートリアルでは、todo`は、`id
(数値)、description
(文字列)、isCompleted
(真偽値)の3つの値を含むオブジェクトです。
{ id: 3, description: "buy milk", isCompleted: false }
TodoList
とTodoItem
コンポーネントを作成します。`TodoItem`コンポーネントは`todo`をプロパティとして受け取り、`div`に`id`と`description`を表示します。
今のところ、todosのリストをハードコードします:
// in TodoList this.todos = useState([{ id: 3, description: "buy milk", isCompleted: false }]);
t-foreach を使用すると、
TodoItem
に各タスクを表示できます。プレイグラウンドに
TodoList
を表示します。TodoItem
にプロパティ検証を追加します。

ちなみに
TodoList
と TodoItem
コンポーネントは非常に密接に結合されているため、同じフォルダに配置するのは理にかなっています。
注釈
t-foreach
ディレクティブは QWeb python 実装の Owl と同じではありません: t-key
固有の値が必要です。 フクロウがそれぞれの要素を適切に調整できるようにしました
8. 動的な属性を使用¶
今のところ、 TodoItem
コンポーネントは、 todo
が完了した場合、視覚的には表示されません。dynamic attributes を使用してみましょう。
`TodoItem`ルート要素に`text-muted`と`text-decoration-line-through `Bootstrapクラスを追加します。
ハードコードされた
this.todos
値を変更して、正しく表示されていることを確認します。
ディレクティブ名は t-att
(属性用) ですが、 これは class
の値を設定するために使うことができます (そして入力の value
のような html プロパティ)。

ちなみに
フクロウでは静的なクラス値と動的な値を組み合わせます。次の例は期待通りに動作します:
<div class="a" t-att-class="someExpression"/>
9. タスクを追加する¶
これまでのところ、私たちのリストのタスクはハードコードされています。 ユーザーがTodoをリストに追加できるようにすることで、より便利になります。
TodoList
コンポーネント内のハードコードされた値を削除します。this.todos = useState([]);
タスクリストの上にプレースホルダ付きの入力を追加します。 新しいタスクを入力。
addTodo
という名前のkeyup
イベントに event handler を追加します。入力が押されたかどうかを確認するために
addTodo
を実装します(ev. eyCode === 13
), そしてその場合は 入力の現在のコンテンツを説明として新しいTodoを作成し、すべてのコンテンツの入力をクリアします。todo に固有の id があることを確認してください。todo ごとに増分されるカウンターにすることができます。
ボーナスポイント: 入力が空の場合は何もしないでください。

関連項目
理論: コンポーネントのライフサイクルとフック¶
これまでにフック関数の例として`useState`があります。 hook は、コンポーネントの内部を*フックする*特別な関数です。 useState
の場合、現在のコンポーネントにリンクされているプロキシオブジェクトを生成します。 このため、フック関数は setup
メソッドで呼び出される必要があります。
フクロウコンポーネントは、インスタンス化、レンダリング、マウント、更新、取り外し、破棄など、多くの段階を経ています。 をクリックします。これは component lifycle です。 上の図は、コンポーネントの寿命における最も重要な出来事を示しています(フックは紫色で表示されています)。 大まかに言えば、コンポーネントが作成され、更新された後(潜在的に何度も)、破棄されます。
フクロウは様々な組み込みの`フック関数 <https://github.com/odoo/owl/blob/master/doc/reference/hooks.md>`_ を提供します。それらは全て`setup`関数で呼び出されなければなりません。 たとえば、コンポーネントがマウントされたときにコードを実行したい場合は、onMounted
フックを使用できます。
setup() {
onMounted(() => {
// do something here
});
}
ちなみに
フック関数はすべて`use`または`on`で始まります。例えば、`useState`または`onMounted`です。
10. 入力にフォーカスする¶
t-ref と useRef を使って DOM にアクセスする方法を見てみましょう。 主な考え方は、コンポーネントテンプレート内のターゲット要素を `t-ref`でマークする必要があることです。
<div t-ref="some_name">hello</div>
次に、useRefフック を使ってJSでアクセスできます。 しかし、考えてみると問題があります: コンポーネントの実際の html 要素は、コンポーネントの作成時には存在しません。 コンポーネントがマウントされている場合にのみ存在しますが、フックは setup
メソッドで呼び出す必要があります。 ですから、 useRef
はコンポーネントがマウントされたときにのみ定義される、 el
(要素用) キーを含むオブジェクトを返します。
setup() {
this.myRef = useRef('some_name');
onMounted(() => {
console.log(this.myRef.el);
});
}
前の課題の
input
に焦点を合わせます。 これはTodoList
コンポーネントから行う必要があります(入力の html 要素にfocus
メソッドがあります)。Bonus point: extract the code into a specialized hook
useAutofocus
in a newawesome_owl/utils.js
file.

ちなみに
refは、特別なオブジェクトであることを明らかにするために、 Ref
でサフィックスされます。
this.inputRef = useRef('input');
11. Toggling todos¶
では、新しい機能を追加しましょう。タスクを完了としてマークします。これは実際に考えられるよりも難しいです。 state の所有者はそれを表示するコンポーネントとは異なります。 そのため、TodoItem`コンポーネントは親に、todoの状態を切り替える必要があることを伝える必要があります。 これを行うには、`callback prop toggleState
を追加します。
タスクのIDの前に
type="checkbox"という属性を持つ入力を追加します。これは `isCompleted
の状態がtrueの場合にチェックする必要があります。ちなみに
Owl は
t-att
ディレクティブで値が false の場合に計算された属性を生成しません。`toggleState`コールバックプロパティを`TodoItem`に追加します。
TodoItem
コンポーネント内の入力にchange
イベントハンドラを追加し、todo id でtoggleState
関数を呼び出すようにします。うまくいくようにしよう!

12. タスクを削除しています¶
最後のタッチは、ユーザーがtodoを削除できるようにすることです。
`TodoItem`に新しいコールバックプロパティ`removeTodo`を追加します。
TodoItem
コンポーネントのテンプレートに<span class="fa fa-remove"/>
を挿入します。ユーザーがクリックするたびに、`removeTodo`メソッドを呼び出します。
うまくいくようにしよう!
ちなみに
配列を使用して todo リストを保存する場合は、JavaScript の
splice
関数を使用して todo を削除することができます。
// find the index of the element to delete
const index = list.findIndex((elem) => elem.id === elemId);
if (index >= 0) {
// remove the element at index from list
list.splice(index, 1);
}

13.スロット付きの一般的な`カード`¶
:ref:`前の練習問題 <tutorials/discover_js_framework/simple_card>`では、シンプルな`カード`コンポーネントを作りました。しかし、正直、それはかなり限られています。 サブコンポーネントなど、カード内に任意のコンテンツを表示したい場合はどうすればよいでしょうか? カードの内容は文字列で記述されているので、まあ、それは動作しません。 しかし、コンテンツをテンプレートとして表現することができれば非常に便利です。
これはまさに、Owl の slot システムのために設計されているものです。
スロットを使用するために、 Card
コンポーネントを修正しましょう。
content
プロパティを削除します。デフォルトのスロットを使用して本文を定義します。
Counter
コンポーネントのような任意のコンテンツを含むカードをいくつか挿入します。(ボーナス) プロパティ検証を追加します。

14. カードコンテンツを最小化する¶
最後に、`Card`コンポーネントに機能を追加しましょう。 より興味深いものにするには、ボタンでそのコンテンツを切り替える(表示または非表示)
`Card`コンポーネントに状態を追加して、開いているかどうかを追跡します (デフォルト)。
テンプレートに
t-if
を追加して、条件付きでコンテンツをレンダリングします。ヘッダーにボタンを追加し、ボタンがクリックされたときに状態を反転させるコードを変更します
