Mithril.jsでMarkdownエディタを作った

![CodeDesigner.gif](https://raw.githubusercontent.com/niikunihiro/codedesigner/images/CodeDesigner.gif)



このエントリは、[Mithril.js Advent Calendar 2016](http://qiita.com/advent-calendar/2016/mithriljs) の23日目の記事です。
ブラウザで使えるMarkdownエディタをMithril.jsで作ってみたので、事例紹介します。

## Mithril.jsを選んだ理由

ドキュメントが日本語化されていたので、とっつき易かったのと、フレームワークなのに、とにかく、覚えることが少ないという印象で選びました。
経緯としては、こんな感じです

  - Vue.jsで試作したけどrouterがプラグインだったりと、なんとなく面倒に...
  - Mithril.jsは全部込みでしかも速いし、覚えることが少なさそうだった。
  - あと、購入していた（が積ん読になっていた）[黒ムツ本](https://www.oreilly.co.jp/books/9784873117447/)が手元にあったのが大きかったです。


## アプリの構成

- Node.js（サーバサイド）
- Mithril.js（クライアントサイド）
- webpack（ビルドと開発サーバ）

## ソースのディレクトリ構造

```
├── component
│   ├── editor.js
│   ├── header.js
│   └── side_menu.js
├── model
│   └── doc_model.js
├── app.js
└── view_model.js
```


#### componentディレクトリ

Mithril.jsのコンポーネントファイル（controllerとviewのあるオブジェクトを返すモジュール）を設置

#### modelディレクトリ

Mithril.jsのモデルファイルを設置

#### app.js

アプリのエントリポイント

#### view_model.js

Mithril.jsのビューモデル

## index.htmlの中身

初めてSPA作ったんですが、HTMLは本当に空っぽで拍子抜けしました。ほとんどJavaScriptで生成するんですね。
大雑把な構造として、ヘッダーエリアと、コンテンツエリアにわかれています。コンテンツエリアはサイドメニューとエディタエリアに分かれています。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>CodeDesigner</title>
  <link rel="stylesheet" href="/assets/bundle/style.css">
</head>
<body>
<section id="header"></section>

<section class="main-contents">
  <div class="columns">
    <div id="documents" class="column is-2"></div>
    <div id="designer" class="column"></div>
  </div>
</section>

<script type="text/javascript" src="/assets/bundle/app.js"></script>
</body>
</html>
```


## app.jsの中身

`m.component()`で生成したコンポーネントを`m.mount()`でidタグ内に突っ込んでくイメージです。
エディタエリアだけ動的に切り替わるので、`m.mount()`ではなく、`m.route()`を使っています。

```js:app.js
"use strict";

var m = require("mithril");

// コンポーネント
var Header   = require('./component/header');
var SideMenu = require('./component/side_menu');
var Editor   = require('./component/editor');

// ヘッダー
m.mount(document.getElementById('header'), m.component(Header));
// サイドメニュー
m.mount(document.getElementById('documents'), m.component(SideMenu));

m.route.mode = "pathname";
// エディター部分
m.route(document.getElementById('designer'), '/', {
  '/': Editor,
  '/:title/:id' : Editor
});
```


## コードの流れ

以下では、Saveボタンをクリックした時の、コードの流れを追ってみます。

### ビュー

ビューでは、`m()`でHTMLを作成していきます。
ここでは、Saveボタンを作成し、コントローラーのメソッドを呼んでいます。（`{onclick: ctrl.save}`の部分です）

```js:editor.jsのビューの一部
m('a.button.is-primary', {onclick: ctrl.save}, [
    m('span', 'Save')
]),
```

### コントローラー

コントローラーでは、ビューモデルのメソッドを呼びます（`vm.insert();`の部分です）

```js:editor.jsのコントローラーの一部
this.save = function () {
	var link = vm.insert();
	m.route(link);
};
```

### ビューモデル

ビューモデルでは、モデルのメソッドを呼びます（`doc_model.save(vm.edit());`の部分です）

```js:view_model.jsの一部
insert: function () {
	var doc = doc_model.save(vm.edit());
	// 省略
},
```


### モデル

モデルにロジックを書きます。今回の処理ではデータをローカルストレージに保存しています。

```js:doc_model.jsの一部
Doc.save = function (text) {
  var
    data = {
      title : text.split(/\r\n|\r|\n/)[0].replace('# ', ''),
      body : text,
    },
    json
  ;

  json = JSON.stringify(data);
  localStorage.setItem('docs', json);
  return new Doc(data);
};
```

まとめると、こんな感じです。

1. ビュー : コントローラーのメソッドでイベントを登録する
- コントローラー : ビューモデルのメソッドを呼ぶ
- ビューモデル : モデルのメソッドを呼ぶ
- モデル : ロジックを書く

コントローラーとビューモデルのやってることが同じじゃね？って感じですが、コントローラーは基本的にビューモデルのメソッドを呼ぶだけで、ビューモデルでは、一時的に保持するデータやコンポーネントをまたぐデータをプロパティに持たせておいて、それを使ってモデルのメソッドを呼ぶというように使い分けました。


## ビューの生成について
[テンプレートコンバータ](http://arthurclemens.github.io/mithril-template-converter/index.html)という変換サービスがあるので、それを使うのが早いです。
EmmetでHTMLを一度書いて、テンプレートコンバータでビューを生成した後で、動的に変えたいパラメータだけをMithrilで処理するようにしました。
ただ、ビューは書いてるうちに慣れると思います。


## サーバサイド
  - node.jsで作成  
と言ってもサイト内のURLでページ遷移しないようにしたくらいです。


## 感想

覚えることが少なくて、情報源も[公式サイト](http://mithril-ja.js.org/index.html)と黒ムツ本で必要充分でした。もうちょっと使い込んでからですが、仕事でも使ってみたいと思います。あと、jQuery不要論がなんとなく理解できた気がします。