【Fuse】NavigatorとRouterで画面遷移する

今回は[こちら](https://www.fusetools.com/docs/tutorial/navigation-and-routing)のページに沿って進めていきます。訳すのが難しい箇所があったので、自分の言葉で書いている箇所がいくつかあります。英語が読める方は原文を参照してください。。

###イントロ

前回はビューをコンポーネントとして分離させました。これはスケーラブルでナイスな構造へ向けた大きな一歩です。また、`PageControl`を使って、ページ間の簡単な移動も行いました。

しかし、`PageControl`は横並びの2つのビューをスワイプで移動できる点は有効ですが、完璧ではありません。たとえば、ハイキング編集ビューは編集するハイキングがない場合、スワイプの意味がありません。`PageControl`は基本的に最初に全てのビューをインスタンス化し、（画面に表示されていなくても）そのビューを削除することなくずっと裏で保持し続けます。アプリ全体で、メモリをそんなに消費しない場合や、必要なときだけインスタンス化したいということがない場合には有効です。

とにかく、Fuseは他の簡単な概念と一緒に`Navigator`と`Router`を扱うツールを提供しています。今回はそれらの取り込み方について説明していきます。

###`Navigator`に移行しよう

`PageControl`同様、`Navigator`もナビゲーション用のコンテナです。ナビゲートできるコンポーネントを含むことができるコントロールであると言えます。ただし、`PageControl`とは異なり、`Navigator`は要求に応じてテンプレートを使用して、都度子要素をインスタンス化します。つまり、各ページをインスタンス化し、リサイクルすることで、メモリリソースを節約することができます。`PageControl`のように、デフォルトでスワイプ操作を持ってはいませんが、逆に多様な表現ができると言ってよいでしょう。

`Navigator`を使うには、どのコンポーネントをナビゲートするかを指示しなくてはなりません。ここで`Router`が登場します。すこしややこしいので、実際にコードを変更しながら見ていきたいと思います。早速`Navigator`を追加しましょう。

前回追加した`PageControl`を`Navigator`に変更しましょう。

```MainView.ux
<App>
    <ClientPanel>
        <Navigator>
            <HomePage />
            <EditHikePage />
        </Navigator>
    </ClientPanel>
</App>
```

`<Navigator>`に含まれる子要素は基本的にテンプレートです。インスタンス化されません。子要素をテンプレートとして扱うため、子要素に属性を追加します。

```MainView.ux
<App>
    <ClientPanel>
        <Navigator>
            <HomePage ux:Template="home" />
            <EditHikePage ux:Template="editHike" />
        </Navigator>
    </ClientPanel>
</App>
```

ここで設定した属性`ux:Template`に、与えられた値（ここではhomeとeditHike）をもとに、テンプレートからインスタンス化します。例えば`Navigator`が`home`へナビゲーションするよう指示すると、HomePageクラスのインスタンスが（まだ生成されていない場合は）生成され、そこにナビゲートされます。editHikeの場合も同様です。`Template`属性に与えられた値が重複していない限り、テンプレートを追加することができます。（重複した場合は、どれも認識してくれません。）

さて、この状態で保存すると、表示は消えてしまいますね。先ほども説明した通り、`<Navigator>`の子要素はインスタンスではなく、テンプレートなので、まだビューが生成されていません。通常は、特定のルートに遷移することでインスタンス化を行いますが、デフォルトのパスを指定することもできます。デフォルトを設定しておくと、まだその子要素にナビゲートしていない場合は自動でナビゲートされます。今回のアプリではデフォルトを設定しておくのがよいでしょう。デフォルトにホーム画面を設定し、ハイキングを編集すると、編集ビューに移動するように作ってみましょう。

デフォルトのパスを設定したい`<Navigator>`に`DefaultPath`属性を設定します。

```MainView.ux
<App>
    <ClientPanel>
        <Navigator DefaultPath="home">
            <HomePage ux:Template="home" />
            <EditHikePage ux:Template="editHike" />
        </Navigator>
    </ClientPanel>
</App>
```

これで、起動時にホーム画面が表示されるはずです。当然、`editHike`を指定すれば、編集ビューが最初に表示されるようにすることもできます。

###`Router`でルーティング

`Navigator`とテンプレートの準備が終わったので、今度は`Router`を追加します。`Router`はアプリ内のどの画面に移動するかということと、実際にそこに移動することを管理します。具体的には、`Router`は与えられたルートから、移動先の「ターゲット」を決定し、場合によってはデータを追加するなどして、ターゲットまで移動します。ルーターの出口を見つけるために、ツリー構造をさがして、実際にナビゲーションします。`PageControl`と`Navigator`は両方とも`Router`の出口となりえます。`Router`は今までのルートの履歴を持っていて、必要であれば戻ることも可能です。

いろいろと説明しましたが、その分`Router`にはいろいろな機能があり、強力であることを示しています。使い方はわりと簡単なので、実際にアプリに追加して、2つのページ間の移動を実装してみましょう。

まずは`Router`のインスタンスを作るところからです。アプリ全体でひとつの`Router`を参照していく形でつかうので、`<App>`の最上位に配置します。また、他のビューから参照するための`Name`も設定しておきましょう。

```MainView.ux
<App>
	<Router ux:Name="router" />
    <ClientPanel>
        <Navigator DefaultPath="home">
            <HomePage ux:Template="home" />
            <EditHikePage ux:Template="editHike" />
        </Navigator>
    </ClientPanel>
</App>
```
これでインスタンス化できます。次に、`HomePage`から`EditHikePage`に移動するための準備をします。これを行うにはいくつかの方法がありますが、ベストプラクティスは`Dependecy`をうまく使うことです。コンポーネントが適切に機能するために必要な情報を追加することができます。コンポーネントの一つに`Dependecy `を追加すると、Fuseはその関係を満たすためにいろいろな設定を行います。うまくいかない場合はコンパイルエラーとなります。これにより、依存関係が常に保たれ、正しいコードを保証することができます。

ではその`Dependecy`を追加してみましょう。まずは`Dependency`属性を追加して、依存関係にあるコンテキストの名前を指定します。

```HomePage.ux
<Page ux:Class="HomePage">
    <Router ux:Dependency="router" />
```
`Router`インスタンスを追加するのによく似ていますが、これは依存関係を定義しているだけです。あとは、`MainView.ux`の方にこれを公開すれば、依存関係ができあがります。

```MainView.ux
<App>
	<Router ux:Name="router" />
    <ClientPanel>
        <Navigator DefaultPath="home">
            <HomePage ux:Template="home" router="router" />
            <EditHikePage ux:Template="editHike" />
        </Navigator>
    </ClientPanel>
</App>
```

小文字で指定する傾向があること以外は、他の属性値と同じように指定できます。この`router`を指定しないと、コンポーネントの依存関係が満たされず、コンパイルエラーとなります。

それでは、今実装した`Router`をつかって、`EditHikePage`に遷移してみましょう。宣言しておいた`chooseHike`メソッドを書き換えていきましょう。

まずはメソッドの名前を`goToHike`に変えます。このとき、`module.export`で公開するときの名前や、`HomePage.ux`から参照する場合の名前も変えておいてください。そして引数からハイキングオブジェクトを取得します。

```HomePage.js
function goToHike(args) {
	var hike = args.data;
}
```

ハイキングを選択したときの遷移先も設定する必要があるので、`Router`の`dependancy`を取得します。幸い、Fuseでは、`ux:Dependency`の設定した`Router`をJSから参照することができます。ここで参照する`Router`は、実際には`MainView.ux`から`HomePage.ux`の`Dependency`に渡された`Router`と同じものになります。

参照した`Router`に、それぞれの`EditHikePage`に移動するよう指示します。これを行うには、`push`関数を実行します。

```HomePage.js
function goToHike(args) {
	var hike = args.data;
	router.push("editHike");
}
```
`push`は指定されたルートに実際にナビゲートするメソッドです。指定する値は`ux:Template`に指定した値をセットします。今回は少し前に設定した「editHike」と指定しました。プレビューを確認すると期待通りの動作をしていることがわかります。もちろん、遷移時にデータを送っていないので、中身は空のままです。ここで初めて`Navigator`により`editHikePage`のインスタンスが作成されます。

さて、ハイキングを選択して、編集ビューに行くことはできましたが、そこから戻る機能を実装しなくてはいけません。戻るボタンを追加します。

まずは`HomePage`と同じように、`EditHikePage`に`Dependency`を追加する必要があります。

```EditHikePage.ux
<Page ux:Class="EditHikePage">
    <Router ux:Dependency="router" />
```

そして、`MainView.ux`の方も設定します。

```MainView.ux
<Navigator DefaultPath="home">
	<HomePage ux:Template="home" router="router" />
	<EditHikePage ux:Template="editHike" router="router" />
```

これで、ルーティングはできたので、あとは戻るボタンを追加して、Clickハンドラーを設定します。

```EditHikePage.ux
				<TextView Value="{comments}" TextWrapping="Wrap" />
				<Button Text="Back" Clicked="{goBack}" />
        </StackPanel>
    </ScrollView>
</Page>
```

JSファイルに実際にページ移動を実行するメソッドを定義します。

```EditHikePage.js
function goBack() {
	router.goBack();
}

module.exports = {
    ...

    goBack: goBack
};
```
`router.goBack`は直前のルートへ戻るように指示します。Fuseでは、`router.push`で移動した場合は履歴をためることができるので、前の画面に戻ることができます。しかし、`router.goto`を使うと、移動はできますが、その履歴は蓄積されないことに注意してください。

`goBack`は自動的に、端末のハードウェア戻るボタン（があれば）と連動します。PCでプレビューしている場合は、Cmd + B(OS X)または、Ctrl + B(Windows)でシミュレートすることができます。

ここまでで、一通りの画面遷移が完成しました！

### ページ間でデータをやりとりする

最後に、画面遷移時に選択したハイキングの情報を編集ビューに渡すことをやりたいと思います。これを実現するには、「ホーム画面から編集ビューにデータを送ること」と「編集ビューでデータを受け取ること」の二つの実装が必要になります。

これは非常に簡単です。`router.push`を実行する際に、ハイキングデータオブジェクトを追加してあげれば、`Router`はデータを一緒に送信してくれます。今回のデータは全てシンプルな文字列データのみ含まれているので、他の機能を実装する必要がありません。

ハイキングデータオブジェクトを追加するには、下記のように修正します。

```HomePage.js
function goToHike(arg) {
    var hike = arg.data;
    router.push("editHike", hike);
}
```

これで、`editHike`に遷移するとともに、データをわたすことができます。（この場合、`EditHikePage`のインスタンスにデータが渡されます。）

このチュートリアルでは単純なナビゲーションしか使用しませんが、マルチレベルのナビゲーション()も可能です。詳細については[こちら](https://www.fusetools.com/docs/navigation/navigation#multi-level-navigation)を参照してください。

今度は受け取る側の実装です。Fuseはこのために`Parameter`と呼ばれる特別な`Observable`を用意しています。この`Parameter`は`Router`経由でコンポーネントに渡される全ての値を示しています。前に`EditHikePage.js`に設定した`Observable`を確認してみましょう。

```EditHikePage.js
var Observable = require("FuseJS/Observable");

var hike = Observable();

...
```
`Parameter`として渡ってきた値を`hike`に設定できれば理想的です。これを実現するには、つぎのようにします。

```EditHikePage.js
var hike = this.Parameter;
```

`Parameter`は`Observable`の一つなので新しい`Observable`を作る必要はありません。ハイキングデータオブジェクトをもっている`Parameter`を使うだけでOKです。なので、`Observable`のインポートが削除できる点にも注目です。

先に示したように、`Parameter`は`this.Parameter`として使います。これは`Parameter`がモジュールの１部分となるからです。`EditHikePage`の特定のインスタンスを参照するために、「このインスタンスの」という指定を行う必要があります。これはJSのどの部分で行われたかにより、コンテキストが変わる可能性があるので、モジュールのルートの正しいインスタンスを使うことが大事です。

これで、選択したハイキングのページに遷移しつつ、それぞれのデータが表示されていることがわかると思います！

###今回の成果

今回は、ページ間の遷移とデータの受け渡しを行いました！

![タイトルなし.gif](https://qiita-image-store.s3.amazonaws.com/0/100937/153693f1-5840-ecc8-cbe1-f93d53ecffab.gif)

######公式サイトの動画拝借しちゃいました…

今回修正したファイルは下記です。

```MainView.ux
<App>
	<Router ux:Name="router" />
	<ClientPanel>
        <Navigator DefaultPath="home">
            <HomePage ux:Template="home" router="router" />
            <EditHikePage ux:Template="editHike" router="router" />
        </Navigator>
    </ClientPanel>
</App>
```

```HomePage.ux
<Page ux:Class="HomePage">
	<Router ux:Dependency="router" />
	<JavaScript File="HomePage.js" />
	<ScrollView>
		<StackPanel>
			<Each Items="{hikes}">
			    <Button Text="{name}" Clicked="{goToHike}" />
			</Each>
		</StackPanel>
	</ScrollView>
</Page>
```

```HomePage.js
var hikes = require("hikes");

function goToHike(args) {
	var hike = args.data;
	router.push("editHike", hike);
}

module.exports = {
	hikes: hikes,
	goToHike: goToHike
}
```

```EditHikePage.ux
<Page ux:Class="EditHikePage">
	<Router ux:Dependency="router" />
	<JavaScript File="EditHikePage.js" />
	<ScrollView>
		<StackPanel>
			<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" />

			<Button Text="back" Clicked="{goBack}" />
		</StackPanel>
	</ScrollView>
</Page>
```

```EditHikePage.js
var hike = this.Parameter;
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 goBack() {
	router.goBack();
}

module.exports = {
	name: _name,
	location: _location,
	distance: _distance,
	rating: _rating,
	comments: _comments,
	goBack: goBack
};
```

###今回登場したFuseの機能たち
- [Navigator](https://www.fusetools.com/docs/fuse/controls/navigator)
- [Router](https://www.fusetools.com/docs/fuse/navigation/router)

その他、`DefaultPath`属性、`Dependecy`属性、`Parameter`の使い方などがありました。だいぶアプリっぽくなってきましたね。

###明日は
いい感じに動いてはいますが、実際にModelのデータを変更することができていません。なので、バックエンドのモックアップを作る作業をします。いつか実際のバックエンドを追加するときを想定して、アプリの構造を最適化していきましょう。