今回はこちらのページに沿って進めていきます。
###イントロ
前回の記事では、静的なテキストの表示など基礎的なところから、入力フィールドを追加して、変更を伝達させるなどを行いました。今回はいくつかのハイキング情報を作成、選択、編集できるようにしてみたいと思います。
このチュートリアルでは、管理が楽になるよう、MainView.uxに全ての情報を保存します。ビューの上部にセレクターを追加して、編集したいハイキングを選択し、前回のビューにこのデータを入力しましょう。
※この段階ではまだ変更は保持されません。モデルのデータをビューに適用するだけですので、起動時にリセットされます。しかし、後の章でそれを解決する予定です。
ハイキングのリストのデータを作る
ハイキングのリストを表示するには、そのデータが必要なので、アプリのモデルのようなものを構築します。まずは単純な配列でつくってみます。JSに次のデータを追加します。
var _hikes = [
{
id: 0,
name: "Tricky Trails",
location: "Lakebed, Utah",
distance: 10.4,
rating: 4,
comments: "This hike was nice and hike-like. Glad I didn't bring a bike."
},
{
id: 1,
name: "Mondo Mountains",
location: "Black Hills, South Dakota",
distance: 20.86,
rating: 3,
comments: "Not the best, but would probably do again. Note to self: don't forget the sandwiches next time."
},
{
id: 2,
name: "Pesky Peaks",
location: "Bergenhagen, Norway",
distance: 8.2,
rating: 5,
comments: "Short but SO sweet!!"
},
{
id: 3,
name: "Rad Rivers",
location: "Moriyama, Japan",
distance: 12.3,
rating: 4,
comments: "Took my time with this one. Great view!"
},
{
id: 4,
name: "Dangerous Dirt",
location: "Cactus, Arizona",
distance: 19.34,
rating: 2,
comments: "Too long, too hot. Also that snakebite wasn't very fun."
}
];
各アイテムが同じフィールドを持っています。IDは一意の識別子です。
※各変数のスコープですが、基本的には同一の<JavaScript>
でないとデータにはアクセスできないようです。なので、必要なデータは同じ<JavaScript>
内に含めるように配置してください。
ハイキングリストを表示しよう
データはできたので、それを表示するビューを作りましょう。まずは作ったデータをmodule.exports
に含めるところからです。
module.exports = {
hikes: _hikes,
name: _name,
location: _location,
distance: _distance,
rating: _rating,
comments: _comments
}
ハイキングを選択できるようにするため、それぞれのボタンを表示します。押されたらそのハイキングの編集ビューが表示されるようにします。データの数に応じてボタンが作られるようにしたいので、下記のようにします。
<Each Items="{hikes}">
</Each>
<Each>
はとても強力な機能です。<Each>
はitems
プロパティで指定されたコレクションを取得し、各アイテムを各タグの中のサブツリーのコピーに渡します。<Each>
の中に、各アイテムをコピペするような感じです。
ここでは、ハイキングの名前がセットされたボタンを<Each>
を使って作成します。
<Each Items="{hikes}">
<Button Text="{name}" />
</Each>
これを<StackPanel>
の一番上にセットすれば、各アイテムのname
がテキストとしてセットされたボタンが完成します。当然、<Each>
を複数配置すればその分ボタンは生成されます。<JavaScript>
側では、データを丸ごと公開だけである(nameという属性は公開していない)ことに注目してください。export
された要素の中にname
というプロパティが既にありますが、<Each>
は、hikes
に自動的にコンテキストを絞り込んでくれます。なので、意図したname
をバインドしてくれます。
###ハイキングを選択する
さて、ボタンを作ることができたので、クリックして選択しましょう。現状のビューは編集可能な複数のObservableで構成されています。そこに、現在編集しているハイキングを表すObservableを追加します。デフォルトの値は空にしておきます。
var hike = Observable();
今度は、今作ったObservableから、他のObservableにデータを渡す必要があります。片方は、ハイキングが行われるたびにObservableなフィールドを追加しないといけないので、もうちょっと綺麗に書きたいところです。
そこで、map
メソッドを使ってObservableを複製します。
var name = hike.map(function(x) { return x.name; });
リアクティブプログラミングに慣れていないときは、少し気になるかもしれませんが、心配しないでください。深く考えず、直感的に見てもらえば大丈夫です。
このコードを分解してみると、まず、hike
にmap
を実行しています。map
は、新しいObservableを返します。そのObservableの中に入る各値は、hike
の中の各値が伝搬されています。つまり、新しく作られたObservable
は、hike
の中にある値からマップされ、hike
の値に基づいています。
また、渡している関数はマッピング関数とよばれ、引数を一つとり、その引数に基づいた値を返します。場合によっては、他のコードに影響を与えてしまう場合もありますが、基本的にはpure関数なので、値が変わることはありません。
簡単にいえば、ハイキングに新しい値が追加されても、逐一Observableを加えなくていいということです!Modelに追加しておけば、自動的に引数として渡されます。map関数は、その渡された引数に基づいて新しい値を返します。この値がObservableの値となります。
Observableについては、Observable Crash Courseとリファレンスで詳細を調べることができます。
さて、それでは、name
と同じように、ほかのものもリファクタリングしましょう。
var name = hike.map(function(x) { return x.name; });
var location = hike.map(function(x) { return x.location; });
var distance = hike.map(function(x) { return x.distance; });
var rating = hike.map(function(x) { return x.rating; });
var comments = hike.map(function(x) { return x.comments; });
最後に、各ボタンと、クリックしたときに、実行する関数を繋げる必要があります。まず、空の関数を作り、export
に追加します。
function chooseHike() {
}
module.exports = {
hikes: _hikes,
name: _name,
location: _location,
distance: _distance,
rating: _rating,
comments: _comments,
chooseHike: chooseHike
};
ボタンとこの関数を紐付けます。
<Button Text="{name}" Clicked="{chooseHike}" />
これであとは関数の中身を作っていくだけです。Observableの値を指定したいのですが、どうすればよいでしょう?ButtonのClickedに関数をバインドしたので、引数として、<Each>
でまわしているデータのコンテキストを表すデータフィールドが渡されます。そのデータをとって、渡しましょう。
function chooseHike(_item) {
hike.value = _item.data;
}
保存すれば、期待通り動作していると思います。最初は空だった入力フィールドが、ハイキングを選択するとそれぞれのデータで埋められます。
今日の成果はこんな感じです!
コード全体はこんな感じ
<App>
<ClientPanel>
<JavaScript>
var _hikes = [
{
id: 0,
name: "Tricky Trails",
location: "Lakebed, Utah",
distance: 10.4,
rating: 4,
comments: "This hike was nice and hike-like. Glad I didn't bring a bike."
},
{
id: 1,
name: "Mondo Mountains",
location: "Black Hills, South Dakota",
distance: 20.86,
rating: 3,
comments: "Not the best, but would probably do again. Note to self: don't forget the sandwiches next time."
},
{
id: 2,
name: "Pesky Peaks",
location: "Bergenhagen, Norway",
distance: 8.2,
rating: 5,
comments: "Short but SO sweet!!"
},
{
id: 3,
name: "Rad Rivers",
location: "Moriyama, Japan",
distance: 12.3,
rating: 4,
comments: "Took my time with this one. Great view!"
},
{
id: 4,
name: "Dangerous Dirt",
location: "Cactus, Arizona",
distance: 19.34,
rating: 2,
comments: "Too long, too hot. Also that snakebite wasn't very fun."
}
];
var Observable = require("FuseJS/Observable")
var hike = Observable();
var _name = hike.map(function(x) { return x.name; });
var _location = hike.map(function(x) { return x.location; });
var _distance = hike.map(function(x) { return x.distance; });
var _rating = hike.map(function(x) { return x.rating; });
var _comments = hike.map(function(x) { return x.comments; });
function chooseHike(_item) {
hike.value = _item.data;
}
module.exports = {
hikes: _hikes,
name: _name,
location: _location,
distance: _distance,
rating: _rating,
comments: _comments,
chooseHike: chooseHike
};
</JavaScript>
<ScrollView>
<StackPanel>
<Each Items="{hikes}">
<Button Text="{name}" Clicked="{chooseHike}" />
</Each>
<Text>Name:</Text>
<TextBox Value="{name}" />
<Text>Location:</Text>
<TextBox Value="{location}" />
<Text>Distance (km):</Text>
<TextBox Value="{distance}" InputHint="Decimal" />
<Text>Rating:</Text>
<TextBox Value="{rating}" InputHint="Integer" />
<Text>Comments:</Text>
<TextView Value="{comments}" TextWrapping="Wrap" />
</StackPanel>
</ScrollView>
</ClientPanel>
</App>
###今回登場したFuseの機能たち
###明日は
各ビューを完全に違う画面に分離して、コンポーネントっぽくしたいと思います!