えんじにあ ときどき くらいまー

個人的な技術メモとかボルダリングのこととかなんか色々書けたらいいなぁ

Riot.jsを使ってSPAサイトを作ってみる 基礎編 Section2 ~ページ遷移してみる~

はじめに

こんにちは。今回はいいペースで書けてます。

前回に引き続き、Riot.jsを使用したSPAサイトの作り方を説明していきたいと思います。
(言うほど自分も分かってませんが)

今回は、前回作成したものに以下の処理を付けたいと思います。

  • 実行時のHOME画面ではメニューのみを表示する。
  • メニューをクリックしたら、タイトルが切り替わる。
  • メニューの『さいしょのぺーじ』をクリックしたら、メニューの下にログインフォームを表示する。
  • メニューの『さいしょのぺーじ』以外のメニューをクリックしたら、ログインフォームが非表示になる。

今回も前回と同じく順を追って説明していきたいと思います。

シリーズ

目次

クリック処理を入れよう

前回作成したriot_spa.htmlファイルにクリック処理を追記します。
以下の処理を mainタグ 内に追加してください。

  • riot_spa.html
<main>
...<略>...

  this.click = (event) => {
    this.title = event.item.title;
    return true;
  };
</main>

処理を追加したら実行してみましょう。
メニューをクリックすると、画面のタイトルが切り替わるのが確認できるかと思います。

onclick, onsubmitなどの onで始まる属性 には、イベントが起きた際に呼ばれる関数を設定できます(イベントハンドラ)。
これらのイベントハンドラが呼ばれたとき、 this.update() が自動的に呼ばれ、変更がUIに反映されるようになっています。

イベントハンドラ内で return true の処理を入れていますが、これはイベントハンドラのデフォルトの挙動が キャンセル になっているためです。
(チェックボックスラジオボタンを除く)

また、イベントハンドラは第一引数にイベントオブジェクトを受け取ります。
event.item はループ中のみ有効ですが、現在の要素を取得することができるイベントオブジェクトになります。

イベントオブジェクト

イベントオブジェクトについて軽く触れましたがので、他も紹介します。(eはイベントハンドラの引数)

  • e.currentTarget … イベントハンドラが指定された要素を指します。
  • e.target … イベントの送信元エレメントです。これは必ずしも必要ではなく、currentTargetと同じです。
  • e.which … キーボードイベント(keypress、keyupなど)のキーコートです。
  • e.item … ループの中でのみ有効で、現在の要素を指します。

カスタムタグを作成する ~その2~

メインのカスタムタグは既に作成しましたので、メニューの『さいしょのぺーじ』をクリックしたときに表示されるページをカスタムタグで作成しましょう。

カスタムタグの mainタグ の下に以下を追記してください。

  • riot_spa.html
<first>
  <form onsubmit='{submit}'>
    <input type='text' name='username'/>
    <input type='password' name='password'/>
    <button type='submit' name='submit'>Login</button>
  </form>

  const defaultUserName = 'input user name';
  const defaultPassword = 'input password';

  this.on('mount', () => {
    this.username.value = defaultUserName;
    this.password.value = defaultPassword;
  });

  this.submit = () => {
    let canSubmit = true;

    if(!this.username.value || this.username.value === defaultUserName){
      console.log('ちゃんとユーザ入力しようよ');
      canSubmit = false;
    }

    if(!this.password.value || this.password.value === defaultPassword){
      console.log('ちゃんとパスワード入力しようよ');
      canSubmit = false;
    }

    if(!canSubmit){
      return false;
    }

    console.log('username: ', this.username.value);
    console.log('password: ', this.password.value);
  };
</first>

やっている内容としては、ユーザ名・パスワードの入力項目とログインボタンのフォームを設置しているのと、
ログインボタンが押されたときに、ユーザ名・パスワードが正常に入力されているかの確認をしているだけという簡単なことですね。
※注:入力項目のバリデーションはすっごい適当にやってます。

そんな適当な処理は置いておいて、1点重要な処理があります。
それが以下の処理です。

this.on('mount', () => {
  this.username.value = defaultUserName;
  this.password.value = defaultPassword;
});

これは何をしているかというと、ページにタグがマウントされたという イベント をハンドリングして、
ユーザ・パスワードに初期値を入れるという処理をしています。

タグのイベント

タグのイベントには以下があります。

  • update … タグの更新直前。UIが更新される前にコンテキストデータを再計算できる。
  • updated … タグの更新完了の直後。更新されたDOMに対して処理ができる。
  • mount … ページにタグがマウントされた直後。
  • unmount … ページからタグのマウントが解除された直後。

カスタムタグの複数マウント

それでは先ほど作成したカスタムタグをマウントしましょう。

mainタグをマウントしていた箇所を以下のように書き換えてください。

  • riot_spa.html
<script>
  // mainとfirstタグのマウント
  riot.mount('main, first');
</script>

これでmainタグとfirstタグがマウントされました。
カンマで区切ることで複数マウントすることができます。

また、別の方法もあります。

<script>
  // 全てのカスタムタグのマウント
  riot.mount('*');
</script>

このように記述することで、ページ上の全てのカスタムタグをマウントすることができます。

では、マウントした firstタグ を表示してみましょう。
bodyを以下のように修正してください。

  • riot_spa.html
<body>

  <main></main>
  <first></first>

  ...

</body>

それでは実行してみましょう。

メニューとログインフォームが表示されたのがわかるかと思います。
しかしながら、メニューをクリックしてもタイトルが切り替わるだけで画面が切り替わりません。

実現したいことは、

  • 実行時のHOME画面ではメニューのみを表示する。
  • メニューをクリックしたら、タイトルが切り替わる。
  • メニューの『さいしょのぺーじ』をクリックしたら、メニューの下にログインフォームを表示する。
  • メニューの『さいしょのぺーじ』以外のメニューをクリックしたら、ログインフォームが非表示になる。

ですので、さらに処理を修正していきます。

ルーティング設定

まずは、実行時のHOME画面ではメニューのみを表示するように、さきほど body に追記した firstタグ を削除しましょう。
(消すつもりなら、はじめから書くなというのは無しで)

つぎに、 firstタグ のマウントも解除しましょう。
(消すつもりなら、はじめかr...)

ここからが本題です。

mainタグに以下のように、 contents タグを追記しましょう。

  • riot_spa.html
<main>
  ...
    </li>
  </ul>

  <contents></contents>

  ...
</main>

また、以下の処理も同様に mainタグ に追記してください。

  • riot_spa.html
<main>
  ...
  
  riot.route((tagName) => {
    if(!tagName){
      return;
    }
    riot.mount('contents', tagName);
  });

  riot.route.stop();
  riot.route.start();
</main>

処理の内容を説明します。

  • riot.route(callback)はURLが変更されると、callback関数にベースパスから変更されたURLが引数として渡ります。
    • デフォルトのベースパスは # です。そのため、tagNameには#以下が渡されています。
    • デフォルトのベースパスは riot.route.base(ベース) で変更することができます。
  • riout.mount('contents', tagName) でcontentsタグに、tagNameで指定されたカスタムタグをマウントしています。
    • タグマウント時にオブジェクトを渡すこともできます。
  • riot.route.stop()でURLの変更検知を停止し、全てのコールバックをクリアします。また、 riot.route.start() でURL変更の検知を開始します。
    • URL変更検知を停止し、全てのコールバックをクリアしてから、URL変更検知を開始するのが安全だと思います。

それでは実行してみてください。実現したい機能ができていると思います。
一応これで超簡易的なSPAサイトができました。
(サイトといえるシロモノではないですが、基本は同じです)

ソース

色々修正入れましたので、現在のソース全文載せときます。

<!doctype html>

<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="description" content="Riot.jsでSPAサイトを構築する方法" />

    <title>Riot.jsを使ってSPAサイトを作ってみる</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/riot/2.6.7/riot+compiler.js'></script>

  </head>
  <body>

    <main></main>

    <script type='riot/tag'>
      <main>
        <h1>{title}</h1>

        <ul>
          <li each='{list}'>
            <a href='#{tagName || 'no_tag'}' onclick='{click}'>{title}</a>
          </li>
        </ul>

        <contents></contents>

        // thisは常に常に現在のタグのインスタンスを指す
        this.list = [
          {
            tagName: 'first',
            title: 'さいしょのぺーじ'
          },
          {
            tagName: 'second',
            title: '2、2番目だと'
          },
          {
            tagName: 'third',
            title: '銅メダル!'
          },
          {
            title: 'ぶーびー'
          },
          {
            tagName: 'last',
            title: '最終ページや'
          }
        ]

        this.title = opts.title || 'HOME';

        this.click = (event) => {
          this.title = event.item.title;
          return true;
        };

        riot.route((tagName) => {
          if(!tagName){
            return;
          }
          riot.mount('contents', tagName);
        });

        riot.route.stop();
        riot.route.start();
      </main>
      <first>
        <form onsubmit='{submit}'>
          <input type='text' name='username'/>
          <input type='password' name='password'/>
          <button type='submit' name='submit'>Login</button>
        </form>

        const defaultUserName = 'input user name';
        const defaultPassword = 'input password';

        this.on('mount', () => {
          this.username.value = defaultUserName;
          this.password.value = defaultPassword;
        });

        this.submit = () => {
          let canSubmit = true;
          if(!this.username.value || this.username.value === defaultUserName){
            console.log('ちゃんとユーザ入力しようよ');
            canSubmit = false;
          }
          if(!this.password.value || this.password.value === defaultPassword){
            console.log('ちゃんとパスワード入力しようよ');
            canSubmit = false;
          }

          if(!canSubmit){
            return;
          }

          console.log('username: ', this.username.value);
          console.log('password: ', this.password.value);
        };
      </first>
    </script>

    <script>
      // マウント
      riot.mount('main');
    </script>

  </body>
</html>

まとめ

今回の重要な点は以下になります。

  • イベントハンドラが呼ばれたとき、 this.update() が自動的に呼ばれ変更がUIに反映される。
  • this.on('イベント', callback) でカスタムタグのイベントをハンドリングして処理を差し込むことができる。
    • ハンドリングできるイベントは次の4つ。 update/updated/mount/unmount
  • riot.mount('カスタムタグ名') で複数のカスタムタグをマウントすることができる。
  • riot.route(callback)でURLの変更検知ををし、処理を差し込める。
  • riot.route.stop(), riot.route.start() でURLの変更検知の停止・開始ができる。
    • URL変更検知の停止 => URL変更検知の開始をセットで行うのが安全。

次回はフォームのデザインが微妙なのでそのあたりを修正していきたいと思います。

また、他にも重要な機能がありますので、そちらもあわせて紹介します。

(2日連続書けた。。。明日は無理かな。。。)