読者です 読者をやめる 読者になる 読者になる

the industrial

都内で働くITエンジニアの日記

Riot.js を利用してTODOアプリケーション構築のハンズオンを作成しました #riotjs

program

Riot.js Advent Calendar 2016 の2日目!

1日目はkuwahara_jsngさんの「今年1年真剣にRiot.jsと付き合ってきたので振り返ってみた」です。

さて、ついにv3がリリースされたRiot.jsですが、実は以前、社内向けに作ったハンズオンがあるので、Riot.js Advent Calendar 2016の場をお借りして紹介させていただきたいと思います。

ただ、バージョンは2.6時点のものですのでご了承下さい。

v3版も、いずれ近いうちに!!!

デモ

http://omiend.github.io/riotjs_todo/

はじめに

Riot.jsとは

関連するHTMLとJavaScriptを結合し、再利用可能なコンポーネントとしてまとめたカスタムタグを利用してアプリケーションを構築することができる、超軽量Webフレームワークです。

http://riotjs.com/ja/

カスタム・タグ

Viewと対になるScriptを、一つのタグに定義したものです。

HTML
└── Custom Tag
    ├── Custom Tag
    │   ├── HTML
    │   └── Script
    ├── HTML
    └── Script

コンパイラ

インブラウザ・コンパイルとプリコンパイルから選べます。 このハンズオンでは環境構築を簡素化するために、表示時にコンパイルされるインブラウザ・コンパイルを採用しています。

http://riotjs.com/ja/api/compiler/

私は普段、Webpackでプリコンパイル(&トランスパイル)させて、BundleされたJSを読み込むようにしています。

その際に利用する webpack.config.js 例です。

var path = require('path');
var webpack = require('webpack');

module.exports = {
  entry: {
    index: path.join(__dirname, 'src/app.js')
  },
  output: {
    path: path.join(__dirname, 'public/scripts'),
    filename: 'app.js',
    library: 'Router'
  },
  plugins: [
    new webpack.ProvidePlugin({
      riot: 'riot'
    })
  ],
  module: {
    loaders: [
      { 
        test: /\.tag$/, 
        exclude: /node_modules/, 
        loader: 'riotjs-loader'
      },
      { 
        test: /\.json$/, 
        exclude: /node_module]s/, 
        loader: 'json-loader'
      },
      { 
        test: /\.js$/,
        exclude: /node_modules/, 
        loader: 'babel-loader' 
      }
    ]
  }
};

ちなみに、 package.json はこんな感じ。

{
  "name": "riot_twitter",
  "version": "1.0.0",
  "description": "",
  "dependencies": {},
  "devDependencies": {
    "babel": "^6.5.2",
    "babel-core": "^6.13.2",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.13.2",
    "json-loader": "^0.5.4",
    "riot": "^2.5.0",
    "riot-router": "^0.8.0",
    "riotjs-loader": "^3.0.0",
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  },
  "scripts": {
    "watch": "webpack --progress --color --watch"
  },
  "author": "omiend"
}

ルーティング

ルーティングにはriot.routeを利用します。

下記アクションでURLが変更されると、与えられたcallback関数を実行します。

  • 1.新しいURLが、ブラウザのロケーションバーに入力されたとき
  • 2.ブラウザの戻る/進むボタンが押されたとき
  • 3.route(to)が呼び出されたとき
  • 4.アンカータグがクリックされたとき

当TODOアプリにおいては、URLの変更を検知→URLの状態を受け取り、callback関数にてカスタム・タグをマウントさせています。

// 例えば、‘todo/1000/edit’にアクセスすると
// collection=‘todo’; id=‘1000’; action=’edit’がバインドされる
riot.route(function(collection, id, action){
  riot.mount('route', collection || 'home', {id: id})
})

参考までに、私は普段riot-routerを利用しています。

riot-router

こちらも気になっています。

riot-routehandler

ハンズオン

注意点

独学なので、Riot.jsのお作法に則っていない可能性があります(お作法がアレば、の話ですが)

Global変数をそのまま触っており(良くない)、JavaScriptについては割りと乱暴にお手軽に実装しています。

※observableを利用して、fluxを取り入れるべきだなぁとは感じています。

また、ローカル環境においては、カスタムタグを外部ファイルとして読み込むため

<script type="riot/tag" src="tags/home.tag"></script>

Google Chromeで起動すると下記のエラーが発生し、正常に動作しない可能性があります。

その際は、Google Chrome起動時に--allow-file-access-from-filesを引数を指定して起動するか、Firefoxなどの別のブラウザでお試し下さい。

riot+compiler.min.js:2 XMLHttpRequest cannot load file://hoge-hoge/riotjs_todo/tags/home.tag. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.

Python(MacOSならば標準搭載)を利用し、SimpleServerを利用する手もあります。

知らなかったPythonのSimpleServer

ローカル環境で動かしましょう

git clone git@github.com:omiend/riotjs_todo.git でもどうぞ
  • index.htmlをブラウザで開く

構成

.
├── css
│   ├── main.css      // ページの整形用に
│   ├── normalize.css // https://necolas.github.io/normalize.css/
│   └── skeleton.css  // わたくしomiendが好きな、軽量CSSフレームワークです( http://getskeleton.com/)
├── images
│   └── riotjs.png    // riotをイメージした画像
├── index.html         // ブラウザが一番初めに読み込む単純なHTMLファイルです
├── scripts
│   └── app.js        // ルーティングと、予め登録されたデータを格納しています
└── tags               // カスタム・タグを配置しているディレクトリです
      ├── add.tag      // TODOデータの追加を行うカスタム・タグです
      ├── commands.tag // TODOデータに対して何らかの処理を行うコマンドを集めたカスタム・タグです
      ├── edit.tag     // TODOデータを更新するためのフォームが定義されたカスタム・タグです
      ├── home.tag     // ホーム画面を表示するための簡単なHTMLが定義されたカスタム・タグです
      ├── navi.tag     // ナビバーの単純なHTMLが定義されたカスタム・タグです
      └── todo.tag     // TODOデータを一覧したりなど、TODOアプリの基盤画面を定義したカスタム・タグです

index.html

ブラウザで一番初めに読み込むファイルです。

内容は単純なHTMLですが、下記の要素が定義されています。

<!-- カスタムタグがマウントされるセレクター 名称はrouteでなくても良いです -->
<route></route>
<!-- Riot.js本体と、コンパイラをCDNで読み込み -->
<script src="https://cdn.jsdelivr.net/riot/2.6/riot+compiler.min.js"></script>
<!-- ルーティングなどの読み込み -->
<script src="scripts/app.js" defer></script>

tag/navi.tag

こちらは本当に単純なHTMLです。

コメントアウトを解除し、画面にナビバーが表示されることを確認してください。 正しく表示されていれば、naviタグが正しくマウントされているということです。

tag/home.tag

ルーティング設定によって、「/」にアクセスされた時ににマウントされるタグです。

同タグ内のScriptにて‘Riot.js’という値を返す関数「echo()」が定義されています。

I Love {this.echo()} !
」にて当該関数を実行し、 ‘Riot.js’を受け取ってレンダリングしています。

単純な例ですが、カスタム・タグの基本がわかると思います。

tag/todo.tag

TODOアプリケーションの基盤的なカスタム・タグです。

まず、riotが提供するeachによってTODOデータをループし、表示しています。

<div class="row" each={todo in store.todos}>

一行ごとにcommandsタグをマウントさせています。

commandsタグはtodoという名前を引数として、ループ中のtodoを受け取っています。

<commands todo={todo}></commands>

tag/commands.tag

todoタグから受け取ったTODOデータは、optsというriotが提供する特別な変数に格納されます。

optsは、Reactで言う所のpropsのイメージです。

<div if={opts.todo.done}><!-- doneがtrueだったらレンダリング -->

commandsタグは、TODOデータに対してアクションを行うボタンを提供するために実装したカスタム・タグです。

editTagをにマウントさせるために呼び出される関数。 フラグを更新する関数。 TODOデータを削除する関数を定義しています。

一つのカスタム・タグが、各行で各TODOデータのみを利用する動きとなっており、コンポーネントの再利用を意識した作りとしています。

tag/add.tag

addタグがマウントされるはtodoタグ内に定義されているので、addタグは子タグと呼べます。

子タグは親タグ(todoタグ)がマウントされた後に初期化されます。

TODO内容を入力し、ADDボタンがクリックされると、addタグに定義されたadd関数が実行され、storeに入力内容を保存します。

実はその後の「 store.trigger('refresh') 」が大切な処理です。

sroreはapp.jsにてobservableのオブジェクトとしており、add実行時にtriggerを実行することによって refresh イベントを発火させています。

riot.observable(store)

storeに対する refresh イベントが発火されると、todoタグに定義された下記のcallback関数が実行され、todoタグが自身を更新し、TODO一覧に追加したTODOデータが表示されるという仕組みになっています。

var self = this // callbackの中でtodoタグを参照するため
store.on('refresh', function(){
  self.update() // todoタグを更新
})

オブザーバブル(Observable)について http://riotjs.com/ja/api/observable/

tag/edit.tag

これまでの説明以上の目新しいことはしていません。

コメントアウトを解除して、更新画面が表示されることを確認してください。

commandsタグからリクエストパラメータでeditタグを呼び出し・・・

edit() {
  riot.route(`edit/${opts.todo.id}`)
}
riot.route(function(collection, id, action){
  // collection = edit
  // id = commandsタグの${opts.todo.id}で取得されたTODOデータのID
  riot.mount('route', collection || 'home', {id: id})
})

TODOデータのIDを opts を利用して受け取っています。

opts.form = store.todos.filter(function(s){return s.id == opts.id})[0]

参考にさせていただいたモノ

基礎

Router周り

KINGSGLAIVE FINAL FANTASY XV

movies game

KINGSGLAIVE FINAL FANTASY XV [Blu-ray]

めっちゃ面白かったー!

もうほぼ実写って程CGが凄く、主人公はカッコよく、そして随所にFFっぽさがあってね

明日発売のFF15前に観れてよかった

しかもゲームに全然興味ない嫁ちゃんを巻き込んでの鑑賞なのだけど

嫁ちゃんも面白いって言ってくれて嬉しや

FF15は10年待ってる事になるんやねー

丁度発送連絡も来たし、楽しみすぎるぜよ

 

BLACK BOX 〜記憶の罠〜

movies

ブラック・ボックス ~記憶の罠~ DTSスペシャル・エディション [DVD]

結局昨晩は映画3本立て続けに観てしまい、今日はちょっと眠かった(割とちゃんと仕事はした)

フランスのサスペンス映画

交通事故後に記憶があやふやになり、失踪した兄を追うというもの

しかし、ラストは驚愕の真実…なんだけど、ビックリ度はそこまででもない

しかし、全編通して青みがかった絵と、ヨーロッパ映画独特の雰囲気も味わえて、観ていて気持ちよかった

お酒飲んでたらもっと気持ち良くなれたかも

 

ヴィジット(ネタバレあり)

movies

ヴィジット (字幕版)

大好きなMNシャマランさんの映画

一番新しい映画かな?

母親とは疎遠な祖父母の元に一週間遊びに行く娘と息子

始めは好印象だったのだが、夜な夜な徘徊する祖母や、言動がオカシナ祖父

その正体は…

とまあ、ねたばれっちゃうと、この祖父母は近くの精神病院から逃げ出し、本当の祖父母を殺害してなりすましたニセモノ

精神病患者なので理屈が通じないという怖さを題材とした映画だったのだけど、精神病患者をこんな風に使ったらダメだろう…

怖さでいうと、シャマランさんの映画では一番怖かった

 

ハウンター

movies

ハウンター(字幕版)

16歳の誕生日前日をループする少女が主人公

何度脱出を試みても無駄で諦め掛けていたのだが、ふとした変化から家に何者かの気配を感じ…

アメリカのホラー映画にしては視覚的な怖さ(僕はこれをびっくり箱ホラーと呼ぶ)が無くて好印象

後半の雰囲気はサイレント・ヒルに続くものがあり、映画全体を通して上手くできてるなーと

ホラーとしてはそんなに怖くなかったけど、面白かった

 

バンクジョブ

movies

バンク・ジョブ (字幕版)

観たことある様な、ない様な映画

 

映画の観すぎで、ままある笑

 

でも面白かった

 

主演はステイサムなのだけど、人っこ一人あやめないし、銃すら撃たない新鮮なステイサム

 

革命家を逮捕したいのだけど、王室のスキャンダルを盾になかなか手が出せずにいた政府が画策し、ステイサム率いるチンピラにそのスキャンダル写真を盗ませようとする話

 

ステイサム側もそのヤバさに気づき〜ってな具合なのだけど、ビックリするのがコレが実話を基にした映画だってこと

インシディアス

movies

インシディアス (字幕版)

SAWのジェームズ・ワン監督の映画

 

かなり丁寧にポルターガイスト的な映画として作ってて、ゴア表現は無し

 

ジェームズ・ワンさんの映画なのに、それがなんだか新鮮

 

内容も面白く、というかかなり怖くて、満足満足