シンプルなアプリケーション
- チュートリアル
- リソース
- 重要なコンセプト
- ソーシャル
- その他
シングルページアプリケーションを構成する主要な要素をカバーするシンプルなアプリケーションを開発してみましょう。
まずはアプリケーションのエントリーポイントを作成してみましょう。index.html
を作成します。
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mithrilのアプリケーション</title>
</head>
<body>
<script src="bin/app.js"></script>
</body>
</html>
<!doctype html>
行はHTML5のドキュメントであることを示しています。最初のcharset
メタタグはドキュメントのエンコーディングを指定します。viewport
メタタグはモバイルブラウザがどのようにページを拡大して表示するかを指定します。title
タグはブラウザのタブに表示されるアプリケーション名のテキストを含みます。script
タグはアプリケーションを動かすJavaScriptファイルのパスを指定します。
1つのJavaScriptファイルアプリケーション全体を作成することもできますが、将来、コードベースの中を探索するのが難しくなるでしょう。コードを複数のモジュールに分割し、それらのモジュールからbin/app.js
という名前のバンドル を作成するようにしましょう。
バンドラーツールのセットアップ方法はたくさんありますが、一番使われている方法はnpmでしょう。Mithrilも含めて、現代的なJavaScriptのライブラリとツールはnpm経由で配布されています。npmはNode.jsはNode.js Package Managerの省略形です。npmをインストールする場合は、 Node.jsをインストールしましょう。npmはNode.jsと一緒にインストールされます。Node.jsとnpmがインストールされたら、コマンドラインを開き、次のコマンドを入力します:
npm init -y
npmが正しくインストールされていれば、package.json
が作成されます。このファイルは、プロジェクトのメタ説明の雛形です。このファイルを編集して、プロジェクト情報と作者名を自由に変更してください。
Mithrilをインストールするには、インストールページの解説に従ってください。Mithrilがインストールされたプロジェクトのスケルトンができると、アプリケーションを作成する準備が整いました。
状態を保存するモジュールを作ってみましょう。src/models/User.js
という名前のファイルを作ってみましょう。
// src/models/User.js
var User = {
list: []
}
module.exports = User
サーバーから何らかのデータをロードしてくるコードを追加しましょう。サーバーとコミュニケーションするには、MithrilのXHRユーティリティである m.request
が使用できます。まず、Mithrilをインクルードして取り込みましょう。
// src/models/User.js
var m = require("mithril")
var User = {
list: []
}
module.exports = User
次に、XHR呼び出しを行う関数を作ります。loadList
という名前にしましょう。
// src/models/User.js
var m = require("mithril")
var User = {
list: [],
loadList: function() {
// TODO: XHR呼び出しをする
}
}
module.exports = User
それではXHRリクエストを行うm.request
呼び出しを行いましょう。このチュートリアルでは、REM APIへの呼び出しを行います。これは高速なプロトタイピングのためにデザインされたのモックのREST APIです。このAPIを使ってGET https://rem-rest-api.herokuapp.com/api/users
エンドポイントにアクセスすると、ユーザーのリストが返ってきます。m.request
を使ってXHRのリクエストを行い、そのエンドポイントのレスポンスを取得しましょう。
// src/models/User.js
var m = require("mithril")
var User = {
list: [],
loadList: function() {
return m.request({
method: "GET",
url: "https://rem-rest-api.herokuapp.com/api/users",
withCredentials: true,
})
.then(function(result) {
User.list = result.data
})
},
}
module.exports = User
method
オプションにはHTTPメソッドを指定します。サーバーに副作用をあたえずにデータを取得するので、GET
メソッドを使う必要があります。url
はAPIのエンドポイントのアドレスです。withCredentials: true
はREM APIで必要となる、クッキーの送受信を有効化する設定です。
m.request
を呼び出すとPromiseが返されます。このPromiseが解決すると、エンドポイントから返ってきたデータが渡されます。デフォルトではMithrilはHTTPレスポンスボディがJSONフォーマットであるとみなして、自動でJavaScriptのオブジェクトか配列に変換しようとします。.then
コールバックは、XHRリクエストが完了すると呼び出されます。この場合、このコールバックはresult.data
の配列をUser.list
に代入します。
loadList
の中に、return
構文があることに気づかれたでしょうか?これはPromiseを使うときによく使われる良い習慣です。これにより、XHRのリクエストが完了した後に呼ばれるコールバックを追加で登録できるようになります。
このシンプルなモデルは2つのメンバーを公開しています。User.list
(ユーザーオブジェクトの配列)と、User.loadList
(サーバーのデータを使ってUser.list
を初期化する)です。
それでは今作成したユーザーモデルモジュールのデータを表示できるように、ビューモジュールを作成しましょう。
src/views/UserList.js
というファイルを作成しましょう。まず、これからすぐに必要になるMithrilとモデルをインクルードします。
// src/views/UserList.js
var m = require("mithril")
var User = require("../models/User")
次にMithrilコンポーネントを作りましょう。今回作成するコンポーネントはview
関数を持つシンプルなオブジェクトです:
// src/views/UserList.js
var m = require("mithril")
var User = require("../models/User")
module.exports = {
view: function() {
// TODO: ここにコードを追加する
}
}
デフォルトでは、Mithrilのビューはhyperscriptを使って定義されています。Hyperscriptは、複雑なタグのHTMLよりも自然に字下げできる簡潔な構文を提供します。また、その構文は単純にJavascriptなので、Javascriptツールのエコシステムを多く活用することができます。例えばBabel、JSX(インラインHTML構文拡張 )、eslint(linting)、uglifyjs(minification)、istanbul(コードカバレッジ)、flow(静的型分析)など。 Hyperscriptを使うと、複雑なタグを持つHTMLよりも自然にインデントできる、完結な構文でテンプレートが記述できます。それに加えて、Babel, JSX (インラインのHTML文法拡張), eslint (構文チェック), uglifyjs (コードサイズ縮小化), istanbul (コードカバレッジ), flow (静的な型解析) などのさまざまなJavaScriptのツールを活用することができます。
それでは、MithrilのHyperscriptを使って要素のリストを作成してみましょう。HyperscriptはMithrilのビューを作成するもっとも一般的な手法ですが、基本的な書き方に慣れてきた後に使う代替手法として人気のあるJSXにも挑戦してみてください。
var m = require("mithril")
var User = require("../models/User")
module.exports = {
view: function() {
return m(".user-list")
}
}
".user-list"
文字列は、見た目で期待される通り、CSSセレクターです。.user-list
はクラスを表しています。タグが指定されないとdiv
がデフォルトで使用されます。このビューは<div class="user-list"></div>
と等価です。
それでは、以前作成したモデル (User.list
) のユーザーのリストを参照し、各要素に対してループを回してみましょう:
// src/views/UserList.js
var m = require("mithril")
var User = require("../models/User")
module.exports = {
view: function() {
return m(".user-list", User.list.map(function(user) {
return m(".user-list-item", user.firstName + " " + user.lastName)
}))
}
}
User.list
はJavaScriptの配列ですし、hyperscriptのビューも単なるJavaScriptです。そのため、.map
メソッドを使って配列をループすることができます。このコードはユーザー名を含むdiv
のリストを表す、vnodeの配列を作成します。
このコードには問題があります。それはUser.loadList
関数を呼んでいないことです。そのため、User.list
は空の配列のままです。ビューをレンダリングしても何も表示されません。コンポーネントを描画するためにUser.loadList
を呼ぶ必要がありますが、それにはコンポーネントのライフサイクルメソッドの力を借ります:
// src/views/UserList.js
var m = require("mithril")
var User = require("../models/User")
module.exports = {
oninit: User.loadList,
view: function() {
return m(".user-list", User.list.map(function(user) {
return m(".user-list-item", user.firstName + " " + user.lastName)
}))
}
}
コンポーネントにoninit
を追加しました。これにはUser.loadList
の参照を持たせています。このコードは、コンポーネントの初期化時に、XHRのリクエストを行うUser.loadListが呼ばれることを意味しています。サーバーがレスポンスを返すと、User.list
に値が設定されます。
このコードではoninit: User.loadList()
(末尾にかっこ) とはしませんでした。これらの違いは、oninit: User.loadList()
の場合はその場で実行されますが、oninit: User.loadList
コンポーネントのレンダリング時にのみ呼び出されます。これは重大な違いであり、JavaScript初心者が陥りやすい落とし穴です。その場で関数を呼び出すと、コンポーネントがレンダリングされるかどうかに関わらず、ソースコードの評価後にすぐにXHRのリクエストが行われてしまいます。この場合、アプリケーション上で前後に移動した場合などにコンポーネントが再作成されたときには、期待に反して呼び出しが行われません。
それでは最初に作成したエントリーポイントのファイルsrc/index.js
からビューのレンダリングを行いましょう:
// src/index.js
var m = require("mithril")
var UserList = require("./views/UserList")
m.mount(document.body, UserList)
m.mount
を呼び出すと、指定されたコンポーネント (UserList
) をDOM要素 (document.body
) 内にレンダリングされます。DOMに今まであった要素は削除されます。HTMLファイルをブラウザで開くと、人名のリストが表示されるはずです。
この状態だと何もスタイルを設定されていません。
現在は、アプリケーションのスタイルを整えるのに利用できる規約やライブラリがたくさんあります。Bootstrapのように、HTMLの構造と、意味のあるクラス名の両方を指定する仕組みもあります。これはクラス名と意味が近いという利点がありますが、カスタマイズが難しいという欠点があります。一方で、Tachyonsのように、たくさんの自己記述型のアトミックではあるものの、意味を持たないクラス名にするというコストを支払っています。"CSS-in-JS"は最近人気を伸ばしている他のCSSシステムです。基本的にトランスパイルによってスコープを実現します。CSS-in-JSライブラリを使うと、問題となる領域を狭くすることができるため、メンテナンス性が向上しますが、複雑性のコストを払う必要があります。
どのようなCSSの表記法やライブラリを使ったとしても、CSSのカスケーディング機能を避けることが大切です。チュートリアルでは単純に済ませるために、大げさなぐらい明示的なクラス名を使っています。クラス名自身がTachyonsが提供するようなアトミック性を持っているため、クラス名の衝突は起きないでしょう。プレーンなCSSでも複雑さの低いプロジェクト(例えば最初の実装工数が3-6人月でフェーズが少ない)では十分です。
スタイルを追加したら、styles.css
を作成し、index.html
にリンクを追加しましょう。
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mithrilのアプリケーション</title>
<link href="styles.css" rel="stylesheet" />
</head>
<body>
<script src="bin/app.js"></script>
</body>
</html>
これでUserList
コンポーネントにスタイルを追加することができるようになりました:
.user-list {list-style:none;margin:0 0 10px;padding:0;}
.user-list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;}
.user-list-item:hover {text-decoration:underline;}
上記のCSSは、ルールのすべてのスタイルをアルファベット順に1行にまとめるという規約を使って書かれています。この表記法は、スクリーンサイズの横幅を最大限に活かすルールになっています。CSSセレクターを探す時は、論理的なグループで整列されて並んでいますし、予測可能で均一化されているため、簡単に要素を探すことができます。
もちろん、スペースとインデントを使うルールが好みであればそれを使うこともできます。このサンプルが紹介しているルールはあまり広まっていはいませんが、強い理由付けがあるルールになっています。より広く使うルールもあります。
ブラウザウィンドウをリロードすると、スタイルが設定された要素が表示されます。
ラウティングをアプリケーションに追加しましょう。
ラウティングはユニークなURLと画面の組を設定します。これにより、複数の「ページ」の間の移動ができるようになります。Mithrilはシングルページアプリケーションを念頭に設計されています。この「ページ」は古い価値観ではそれぞれ個別のHTMLファイルですが、シングルページアプリケーションではページごとにファイルを分ける必要はありません。シングルページアプリケーションのラウティングを使うと、同じHTMLファイルがブラウザを閉じられるまで継続して使われますが、JavaScriptを使ってアプリケーションの状態が変更されます。クライアント側でラウティングを行うと、ページ遷移中に画面がクリアされてブランクなページが見えてしまうのを避けることができます。また、ウェブサービス指向アーキテクチャ(サーバー側で生成されたHTMLを返すのではなく、JSONでデータをダウンロードさせるウェブアプリケーション)と組み合わせると、サーバーとの通信量を削減できます。
m.mount
呼び出しをm.route
に変更すると、ラウティングを使うことができます:
// src/index.js
var m = require("mithril")
var UserList = require("./views/UserList")
m.route(document.body, "/list", {
"/list": UserList
})
m.route
を呼ぶとアプリケーションがdocument.body
の中にレンダリングされます。"/list"
引数はデフォルトのラウトになります。ユーザーがラウト一覧にないURLを開こうとすると、このURLにリダイレクトされます。{"/list": UserList}
オブジェクトは存在するラウトと、それぞれのラウトに対してどのコンポーネントが利用されるのかのマップを宣言します。
ブラウザのページをリフレッシュするとURLに#!/list
が付与されて、ラウティング機構が稼働していることが分かります。ラウトは同じUserListのレンダリングを行うため、前と同じユーザーのリストが見えます。
#!
スニペットはハッシュバングと呼ばれています。これはクライアントサイドのラウティング実装で一般的に使われる文字列です。この文字列はm.route.prefix
を使って設定できます。設定に寄ってはサーバー側の変更が必要となるため、このチュートリアル内ではこのまま進めます。
それではユーザーを編集するための別のラウトをアプリケーションに追加しましょう。まず、views/UserForm.js
というモジュールを作成します。
// src/views/UserForm.js
module.exports = {
view: function() {
// TODO: ビューを実装する
}
}
モジュールがでkちあらrequire
を使ってsrc/index.js
にこの新しいモジュールを追加します。
// src/index.js
var m = require("mithril")
var UserList = require("./views/UserList")
var UserForm = require("./views/UserForm")
m.route(document.body, "/list", {
"/list": UserList
})
次に、このモジュールを参照するラウトを追加しましょう:
// src/index.js
var m = require("mithril")
var UserList = require("./views/UserList")
var UserForm = require("./views/UserForm")
m.route(document.body, "/list", {
"/list": UserList,
"/edit/:id": UserForm,
})
新しいラウトの中に:id
という文字列があります。これはラウトパラメータです。これは一種のワイルドカードでと考えることができます。/edit/1
というURLがあると、id
に"1"
が代入され、UserForm
が解決されます。/edit/2
もUserForm
を解決しますが、id
は"2"
となります。他にもあります。
それではこれらのラウとパラメータを受け取れるようにUserForm
コンポーネントを実装しましょう。
// src/views/UserForm.js
var m = require("mithril")
module.exports = {
view: function() {
return m("form", [
m("label.label", "名前"),
m("input.input[type=text][placeholder=名前]"),
m("label.label", "名字"),
m("input.input[placeholder=名字]"),
m("button.button[type=button]", "保存"),
])
}
}
新しいコンポーネントで使うスタイルもstyles.css
に追加しましょう:
/* styles.css */
body,.input,.button {font:normal 16px Verdana;margin:0;}
.user-list {list-style:none;margin:0 0 10px;padding:0;}
.user-list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;}
.user-list-item:hover {text-decoration:underline;}
.label {display:block;margin:0 0 5px;}
.input {border:1px solid #ddd;border-radius:3px;box-sizing:border-box;display:block;margin:0 0 10px;padding:10px 15px;width:100%;}
.button {background:#eee;border:1px solid #ddd;border-radius:3px;color:#333;display:inline-block;margin:0 0 10px;padding:10px 15px;text-decoration:none;}
.button:hover {background:#e8e8e8;}
この状態では表示はできますが、ユーザーイベントには反応するようになっていません。src/models/User.js
に実装されているUser
にいくつかコードを追加しましょう。これが変更前のコードです。
// src/models/User.js
var m = require("mithril")
var User = {
list: [],
loadList: function() {
return m.request({
method: "GET",
url: "https://rem-rest-api.herokuapp.com/api/users",
withCredentials: true,
})
.then(function(result) {
User.list = result.data
})
},
}
module.exports = User
1人のユーザをロードできるようにコードを追加します。
// src/models/User.js
var m = require("mithril")
var User = {
list: [],
loadList: function() {
return m.request({
method: "GET",
url: "https://rem-rest-api.herokuapp.com/api/users",
withCredentials: true,
})
.then(function(result) {
User.list = result.data
})
},
current: {},
load: function(id) {
return m.request({
method: "GET",
url: "https://rem-rest-api.herokuapp.com/api/users/:id",
data: {id: id},
withCredentials: true,
})
.then(function(result) {
User.current = result
})
}
}
module.exports = User
User.current
プロパティが追加され、User.load(id)
メソッドを呼び出すとこのプロパティに格納されます。UserForm
ビューでこのメソッドを使うようにしましょう。
// src/views/UserForm.js
var m = require("mithril")
var User = require("../models/User")
module.exports = {
oninit: function(vnode) {User.load(vnode.attrs.id)},
view: function() {
return m("form", [
m("label.label", "名前"),
m("input.input[type=text][placeholder=名前]", {value: User.current.firstName}),
m("label.label", "名字"),
m("input.input[placeholder=名字]", {value: User.current.lastName}),
m("button.button[type=button]", "保存"),
])
}
}
UserList
コンポーネントと同様に、oninit
からUser.load()
を呼び出します。"/edit/:id": UserForm
というラウト定義には:id
というパラメータがあったのを覚えているでしょうか?ラウとパラメータはUserForm
コンポーネントのvnodeの属性となります。/edit/1
というURLでアクセスされると、vnode.attrs.id
に"1"
が代入されます。
UserList
ビューを修正し、UserForm
のページに遷移できるようにしましょう:
// src/views/UserList.js
var m = require("mithril")
var User = require("../models/User")
module.exports = {
oninit: User.loadList,
view: function() {
return m(".user-list", User.list.map(function(user) {
return m("a.user-list-item", {href: "/edit/" + user.id, oncreate: m.route.link}, user.firstName + " " + user.lastName)
}))
}
}
.user-list-item
をa.user-list-item
に修正しました。クリック時の移行先の参照をhref
に追加します。そしてSPAとして遷移を行うために、oncreate: m.route.link
も追加します。これにより、このリンクは通常のリンクのように動作するのではなく、ラウトの切り替えのリンクとして動作するようになります。このリンクをクリックすると、現在のページをアンロードすることなく、ハッシュバング#!
以降のURLだけが書き換わるようになります。
ページをリフレッシュすると、人の行をクリックしてフォームに遷移できるようになります。ブラウザの戻るボタンを押すと人名のリストに戻ります。
このフォーム自体はまだ未完成なので「保存」を押しても保存しません。この機能を完成させましょう:
// src/views/UserForm.js
var m = require("mithril")
var User = require("../models/User")
module.exports = {
oninit: function(vnode) {User.load(vnode.attrs.id)},
view: function() {
return m("form", {
onsubmit: function(e) {
e.preventDefault()
User.save()
}
}, [
m("label.label", "名前"),
m("input.input[type=text][placeholder=名前]", {
oninput: m.withAttr("value", function(value) {User.current.firstName = value}),
value: User.current.firstName
}),
m("label.label", "名字"),
m("input.input[placeholder=名字]", {
oninput: m.withAttr("value", function(value) {User.current.lastName = value}),
value: User.current.lastName
}),
m("button.button[type=button]", "保存"),
])
}
}
両方のinputタグに、ユーザーが入力をするとUser.current.firstName
とUser.current.lastName
プロパティを変更するoninput
イベントを追加しました
これに加えて、「保存」ボタンが押されたときに呼ばれるUser.save
メソッドを定義する必要があります。このメソッドを実装してみましょう:
// src/models/User.js
var m = require("mithril")
var User = {
list: [],
loadList: function() {
return m.request({
method: "GET",
url: "https://rem-rest-api.herokuapp.com/api/users",
withCredentials: true,
})
.then(function(result) {
User.list = result.data
})
},
current: {},
load: function(id) {
return m.request({
method: "GET",
url: "https://rem-rest-api.herokuapp.com/api/users/:id",
data: {id: id},
withCredentials: true,
})
.then(function(result) {
User.current = result
})
},
save: function() {
return m.request({
method: "PUT",
url: "https://rem-rest-api.herokuapp.com/api/users/:id",
data: User.current,
withCredentials: true,
})
}
}
module.exports = User
このソースの一番下にあるsave
メソッドの中では、PUT
HTTPメソッドを指定して、サーバーにデータの変更を行います。
それでは、できあがったアプリケーションを使ってユーザーの名前を編集してみましょう。変更を保存すると、変更がユーザーリストに反映されるのを見ることができます。
現在は、ブラウザの戻るボタンしか、元のリストに戻る手段がありません。理想的には、何らかのメニュー、あるいはさらに汎用化させて、グローバルなUI要素が配置されたレイアウトがある方が望ましいでしょう。
src/views/Layout.js
というファイルを作成しましょう:
var m = require("mithril")
module.exports = {
view: function(vnode) {
return m("main.layout", [
m("nav.menu", [
m("a[href='/list']", {oncreate: m.route.link}, "ユーザー一覧")
]),
m("section", vnode.children)
])
}
}
このコンポーネントはきわめてシンプルです。ユーザー一覧へのリンクを持つ<nav>
要素を持ちます。/edit
リンクを作成したのと同じように、このリンクもラウティングの変更が行えるようにm.route.link
を使っています。
<section>
エレメントには、子供の要素としてvnode.children
を渡しています。vnode
Layoutコンポーネントのインスンタンスを表現しているvnodeの参照です。vnodeはm(Layout)
という関数呼び出しで返されます。vnode.children
はvnodeの子供を参照します。
スタイルを追加しましょう:
/* styles.css */
body,.input,.button {font:normal 16px Verdana;margin:0;}
.layout {margin:10px auto;max-width:1000px;}
.menu {margin:0 0 30px;}
.user-list {list-style:none;margin:0 0 10px;padding:0;}
.user-list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;}
.user-list-item:hover {text-decoration:underline;}
.label {display:block;margin:0 0 5px;}
.input {border:1px solid #ddd;border-radius:3px;box-sizing:border-box;display:block;margin:0 0 10px;padding:10px 15px;width:100%;}
.button {background:#eee;border:1px solid #ddd;border-radius:3px;color:#333;display:inline-block;margin:0 0 10px;padding:10px 15px;text-decoration:none;}
.button:hover {background:#e8e8e8;}
src/index.js
のラウトを変更して、このレイアウトを追加してみましょう:
// src/index.js
var m = require("mithril")
var UserList = require("./views/UserList")
var UserForm = require("./views/UserForm")
var Layout = require("./views/Layout")
m.route(document.body, "/list", {
"/list": {
render: function() {
return m(Layout, m(UserList))
}
},
"/edit/:id": {
render: function(vnode) {
return m(Layout, m(UserForm, vnode.attrs))
}
},
})
それぞれのコンポーネントはラウトリゾルバに置き換えられました。ラウトリゾルバはrender
メソッドを持つオブジェクトです。render
メソッドは通常のコンポーネントのビュート同じように、ネストされたm()
関数呼び出しを使って書くことができます。
m()
関数呼び出しの中で、セレクター文字列の代わりにコンポーネントが使われている点が今までと違っていますよね?/list
ラウトの中では、m(Layout, m(UserList))
というコードが呼ばれています。このコードは、vnodeのルートはLayout
のインスタンスとなり、子供としてUserList
vnodeを持つという意味になります。
/edit/:id
ラウトでは、vnode
引数を使ってラウトパラメータをUserForm
コンポーネントに渡しています。URLがもし/edit/1
であれば、vnode.attrs
は{id: 1}
となり、m(UserForm, vnode.attrs)
はm(UserForm, {id: 1})
と等価になります。等価なJSXコードは<UserForm id={vnode.attrs.id} />
となります。
このページをリフレッシュすると、アプリケーション内のすべてのページにグローバルナビゲーションが表示されます。
チュートリアルはここで終了です。
このチュートリアルでは、サーバーからユーザーをリストして個別に編集できるシンプルなアプリケーションを順を追って作成してきました。ぜひ、追加のエクササイズとして、ユーザの新規作成と削除の実装にも挑戦してみてください。
もしMithrilコードのサンプルをもっと見たいのであればサンプルページを参照してください。もし何か質問があればMithrilのチャット(英語)で自由に聞いてください。
License: MIT. © Leo Horie.