Mithril.js 試してみた(1) todo アプリを作り始める所まで

# Mithril.js を試してみた

+ **jQuery** はなんだか最初は簡単だなぁ、と思って使ってみたが、やっている内に、泥臭いし、たくさんコードを書かないといけないし、DOMに追加したり、削除したり、順番を変えたり、保持しているデータとDOMを同期させる仕事をやるだけで疲れてきて、嫌いになった。

+ **Angular.js** (Google) も試してみて、最初は感動したけど、なんかいろいろと難しくて挫折した。

+ **React.js** (Facebook) は、絶対これが本命だと思って、すごく期待して、本まで買って試していたが、ちょっと覚える事が多くて、どうしようかと迷っていた。

+ **Riot.js** もちょっと試そうかと思ったけど、やっぱり覚える事が多くて、大変そうに思えた。

+ **Mithril.js** は、なんか思った通りに書ける気がする。覚える事が少ない。軽い。速い。
JavaScriptオンリーでHTMLもCSSも書ける感じだ。

現時点で Virtual DOM を使うフレームワークは正解だと思う。
jQuery は初心者の勉強のためにはいいけど、僕の中では使っちゃダメなものとして位置付けられている。

>追加記事を書いた。
Mithril.js 試してみた(1) todo アプリを作り始める所まで - Qiita ⇒ この記事
[Mithril.js 試してみた(2) サーバーからデータを取得する m.request() - Qiita]
(http://qiita.com/LightSpeedC/items/f533f048e01ac19a06ec)
[Mithril.js 試してみた(3) console.logの様に画面に表示してみる - Qiita]
(http://qiita.com/LightSpeedC/items/23dcecfa22b89dc7c165)
[Mithril.js 試してみた(4) todo アプリのフロント側まで - Qiita]
(http://qiita.com/LightSpeedC/items/c3026847dc5809d2a7a9)
[Mithril.js 試してみた(5) Excelの様な表計算アプリを3時間で作る m.component() - Qiita]
(http://qiita.com/LightSpeedC/items/c19677822f896adc43d9)

## いつの間にか日本語のサイトが...

知らない間に http://mithril.js.org/ の日本語訳サイトが作られていた。
スゴイ。すばらしい。ありがたい。
↓
http://mithril-ja.js.org/ (http://shibukawa.github.io/mithril-ja/)

本も出てるじゃん
Mithril――最速クライアントサイドMVC
http://www.oreilly.co.jp/books/9784873117447/
サンプル https://github.com/oreilly-japan/mithril-book-sample

## とりあえず最小限のAPIだけでViewだけのサンプルアプリを作ってみた

ダウンロードした `mithril.min.js` を使用する。
以下のサンプルは`m.mount()`と`m.draw()`しか使用していない。

```html:counter-min.html
<!DOCTYPE html>
<meta charset="UTF-8">
<title>counter minimum - Mithril.js</title>

<script src="mithril.min.js"></script>
<!--[if IE]><script src="es5-shim.min.js"></script><![endif]-->

<body></body>

<script>

//カウンター
var counter = 0;

//タイマーでカウントアップ
setInterval(function () {
	counter++;
	m.redraw(true);
}, 1000);

//ビュー
function view() {
	return 'count: ' + counter;
}

//HTML要素にコンポーネントをマウント
m.mount(document.body, {view: view});

</script>
```

ルール無視のサンプルだ。真似してはいけない。
Viewから外の変数を見ているし、外から再描画の指示があるし、
例としてはあまり良くないかな。

IE8でも動く様に `es5-shim.min.js` を以下から入手した。
[es5-shim - github](https://github.com/es-shims/es5-shim#readme)

## ControllerとViewだけのアプリ

ひどい例だったので、もうちょっと改良してみる。

```html:counter-prop.html
<!DOCTYPE html>
<meta charset="UTF-8">
<title>counter prop - Mithril.js</title>

<script src="mithril.min.js"></script>
<!--[if IE]><script src="es5-shim.min.js"></script><![endif]-->

<body>
<div id="$counterElement"></div>
</body>

<script>

//コンポーネント定義
var counterComponent = {
	//コントローラ
	controller: function () {
		var ctrl = this;

		//カウンター
		ctrl.counter = m.prop(0);

		//タイマーでカウントアップ
		setInterval(function () {
			ctrl.counter(ctrl.counter() + 1);
			m.redraw(true);
		}, 1000);
	},
	//ビュー
	view: function (ctrl) {
		return m('div', 'count: ' + ctrl.counter());
	}
};

//HTML要素にコンポーネントをマウント
m.mount($counterElement, counterComponent);

</script>
```

ちょっと変えてみた。
ControllerとViewだけのアプリだ。
`m.prop()`も試しに使ってみた。
この例では`m.prop()`もあまり役に立つように見えないが`m.withAttr()`と一緒に使うといいよ。

**TIPS:** ちなみに
`document.getElementById('element')`とかjQueryの`$('#element')`というのは
シンプルに`element`と省略できるよ。知ってた?
本当は`window.element`や`window['element']`という事なんだ。
だからJavaScriptから扱いたいHTMLエレメントのidにはハイフンとか使わないで
JavaScriptの変数名の規約に合わせておくと扱いやすいよ。
`<div id="$elementName">`の様にidを$で始めると`$elementName`という変数で扱えるようになる。jQuery使ってるみたいでいいよね。
※古いブラウザで使うにはbodyタグが閉じていないといけないけどね。
※グローバル変数がたくさんできるので注意が必要だけどね。

## controller関数

`controller`関数は、コンストラクタとして呼ばれている。
だから `return {counter: m.prop(0)};` の様にオブジェクトを作って返してもいいみたいなんだけど、どちらかと言うと`this`のプロパティに追加するパターンを使う方がいいかなぁ、と思う。
どちらでもいいか。好みの問題か。

## view関数と`m()`

`view`関数は、文字列でも、数字でも、配列を返しても、`m()`の結果のオブジェクトを返してもいいらしい。
めちゃめちゃ柔軟性があるなぁ。`m()`の引数の後半も同様に柔軟性がある。

```js:view関数の返り値の例
return 'count: ' + counter;                       // 文字列
return [m('div', 'count: '), m('div', counter)];  // m('div')を2つ持つ配列
return m('div', 'count: ' + counter);             // m()のコンテンツは文字列
return m('div', 'count: ', counter);              // m()のコンテンツは文字列と数字
return m('div', ['count: ', counter]);            // m()のコンテンツは配列
return m('div', [['count: '], [[[counter]]]]);    // m()のコンテンツは複雑な配列
```

viewで2つのHTMLエレメントを返すために`div`で囲む必要もない。配列で十分だ。
※文字列を配列で返す場合はDOMとしてテキストノード要素がわかれているみたい。

## サンプルのToDoアプリをちょっと改良してみた

例題になっているToDoアプリを改良して、テキスト入力時にEnterキーを受け付けるようにしてみた。

```html:todo-app.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ToDo App - Mithril.js</title>

<script src="mithril.min.js"></script>
<!--[if IE]><script src="es5-shim.min.js"></script><![endif]-->
</head>

<body>
<div id="$todoElement"></div>
</body>

<script>

//コンポーネント定義
var todoComponent = function () {
	'use strict';

	// モデル: Todoクラスは2つのプロパティ(title : string, done : boolean)を持つ
	function Todo(data) {
		this.title = m.prop(data.title);
		this.done  = m.prop(false);
	};

	// モデル: TodoListクラスはTodoオブジェクトの配列
	var TodoList = Array;

	// コントローラ・オブジェクトctrlは
	// 表示されているTodoのリスト(list)を管理し、
	// 作成が完了する前のTodoのタイトル(title)を格納したり、
	// Todoを作成して追加(add)が可能かどうかを判定し、
	// Todoが追加された後にテキスト入力をクリアする

	// このアプリケーションは、todoComponentコンポーネントでコントローラとビューを定義する
	var todoComponent = {

		// コントローラは、モデルの中のどの部分が、現在のページと関連するのかを定義している
		// この場合は1つのコントローラ・オブジェクトctrlですべてを取り仕切っている
		controller: function (listArg) {
			var ctrl = this;

			// アクティブなToDoのリスト
			ctrl.list = new TodoList();

			// 引数に渡されたtodoタイトルリストからToDoのリストに追加
			if (listArg) {
				listArg.forEach(function (title) {
					ctrl.list.push(new Todo({title: title}));
				});
			}

			// 新しいToDoを作成する前の、入力中のToDoの名前を保持するスロット
			ctrl.title = m.prop('');

			// ToDoをリストに登録し、ユーザが使いやすいようにtitleフィールドをクリアする
			ctrl.add = function () {
				var val = ctrl.title() ||
					document.getElementById('$title').value; // for ie8
				if (val) {
					ctrl.list.push(new Todo({title: val}));
					ctrl.title('');
				}
				return false;
			};

		},

		// ビュー
		view: function (ctrl) {
			return [
				m('form', {onsubmit: ctrl.add},
					m('input', m_on('change', 'value', ctrl.title, {id: '$title'})),
					m('button[type=submit]', {onclick: ctrl.add}, 'Add')),
				m('table', [ctrl.list.map(function(todo) {
					return m('tr', [
						m('td', [
							m('input[type=checkbox]', m_on('click', 'checked', todo.done))
						]),
						m('td', {style: {textDecoration: todo.done() ? 'line-through' : 'none'}}, todo.title())
					]);
				})])
			];
		}
	};

	// HTML要素のイベントと値にプロパティを接続するユーティリティ
	function m_on(eventName, propName, propFunc, attrs) {
		attrs = attrs || {};
		attrs['on' + eventName] = m.withAttr(propName, propFunc);
		attrs[propName] = propFunc();
		return attrs;
	}

	return todoComponent;

}();

// アプリケーションの初期化
// HTML要素にコンポーネントをマウント
m.mount($todoElement, m.component(todoComponent, ['first task', 'second task']));
// m.mount($todoElement, todoComponent);

</script>
</html>
```

`controller`関数に引数を追加して、`m.component()`を使ってみた。
`m.component()`を複数の場所で使える部品にするには、コツが必要な気がする。

P.S.
Chrome, Firefox, ie8, ie11, Edge で動作確認しました。
React.js の JSX の様な MSX というのもあるが、そちらが好きな人はそちらをどうぞ。