[WIP] rendr.js

![Rendr](https://qiita-image-store.s3.amazonaws.com/0/10765/99fd9600-0296-4232-c9ed-435da9d3abea.png)

先程たまたまRendrが話題に上り、すっかり放置していた下書きを思い出した。せっかくなので放出しとく。Hello World的なブランクアプリから始めていくチュートリアル構成のようだ。

中に書かれていないのでここに書くけども、Rendrはウェブアプリのフレームワーク。Node.jsとBackbone.jsの両方を利用し、バックとフロントとを透過的に扱う。初期状態のHTMLをサーバー側で構築する事で、高速化と非JS環境（検索エンジンとか）への対応が期待できる。という代物だったと記憶している。

日付が「2014/04/10 07:37」とあったので、当時は[v0.5.0-rc1](https://github.com/rendrjs/rendr/tree/cf634b5be4dedba0a23170a36f63660460cdb1d3)だったはず。現在は[v1.0.3](https://github.com/rendrjs/rendr/tree/5532a66de86bd64c20944276eccb5c87a59f902c)まで進んでるみたい。読むならそこら辺注意してくださいねー。

---
---
---

* ✘ render.js
* ✔ [rendr.js](https://github.com/rendrjs/rendr)

## 何もしない、静的ファイルを表示するだけのexpressアプリ

rendrを使う前の準備。expressを使ったサーバーの構築。

### npm install

```bash
$ npm i express rendr
```

### index.js

エントリーポイント。

```js
var express = require('express');
var rendr = require('rendr');
var app = express();

app.use(express.static(__dirname + '/public'));

if (require.main === module) {
  app.listen(3030);
}

exports.app = app;
```

### app/routes.js

とりあえず空だけど、必須。

```js
module.exports = function(match) {
};
```

### public/index.html

これは今表示するだけなので何でも良いや。

```html
<!DOCTYPE html>
<title>Rendr.js</title>
<h1>Hello world!</h1>
```

次項で表示されるのが確認出来たら、削除して構わない。

### 起動

```bash
node index
```

* http://localhost:3030

## テンプレートを使って画面を表示する

まだモデルは使わないけど、controllerとview、templateを利用して出力する。

### app/routes.js

ホームページのルーティングを作る。"misc#home"で、"misc" controllerの"home" actionを指定。

```js
module.exports = function(match) {
	match('', 'misc#home');
};
```

### index.js

expressではなくrendrアプリが動くようにする。以下を追加。

```js
app.use(rendr.createServer());
```

### app/controllers/misc_controller.js

ディレクトリ名とファイル名の両方に"controller"が付く。こういう命名規則らしい。

```js
module.exports = {
	home: function(params, callback) {
		callback();
	}
};
```

### npm install

たぶん他の選択肢もあるんだろうと思うけれど。

```
$ npm i rendr-handlebars
```

### app/views/misc/home.js

```js
var View = require('rendr/shared/base/view');
module.exports = View.extend({
  className: 'misc_home_view',
}, {
	id: 'misc/home'
});
```

本当はrendrのViewを直接拡張するんじゃなくて、rendrのviewを拡張したものをアプリ共通の基底viewみたいに用意して、個別のViewはさらにそれを拡張するようにした方が良い。rendrの例だと `app/views/base` というのを用意している。

### app/templates/misc/home.hbs

```html
<h1>Hello world!</h1>
```

Handlebarsのテンプレートね。これ（に限らず全てのテンプレート）をコンパイルしたものを `app/templates/compiledTemplates` に配置する。次項参照。

### Gruntfile.js

`grunt handlebars`を使えるようにする。

```js
var path = require('path');

var stylesheetsDir = 'assets/stylesheets';

module.exports = function(grunt) {
	grunt.initConfig({
		handlebars: {
			compile: {
				options: {
					namespace: false,
					commonjs: true,
					processName: function(filename) {
						return filename.replace('app/templates/', '').replace('.hbs', '');
					}
				},
				src: "app/templates/**/*.hbs",
				dest: "app/templates/compiledTemplates.js",
				filter: function(filepath) {
					var filename = path.basename(filepath);
					// Exclude files that begin with '__' from being sent to the client,
					// i.e. __layout.hbs.
					return filename.slice(0, 2) !== '__';
				}
			}
		}
	});

  grunt.loadNpmTasks('grunt-contrib-handlebars');
};
```

### npm install

```bash
$ npm i grunt grunt-contrib-handlebars
```
### grunt

```bash
$ grunt handlebars
```

これで `app/templates/compiledTemplates` が出力される。

### app/templates/__layout.hbs

サーバー側でHTMLを出力する際に使われる。これは事前コンパイル（compiledTemplatesの作成）の必要がないみたい。

```html
<!DOCTYPE html>
<title>Hello world!!</title>
{{{body}}}
```

### 起動

```bash
node index
```

* http://localhost:3030

## Backbone.Model/Collectionで情報を取得する、ための画面

### app/routes.js

追加する。

```js
	match('/tasks', 'tasks#index');
```

### app/controllers/tasks_controller.js

後でCollectionを使うようにする。今はまだ。

```js
module.exports = {
	home: function(params, callback) {
		this.app.fetch({
			// collection: { collection:'Tasks', params:params }
		}, callback);
	}
};
```

### app/views/task/index.js

```js
var View = require('rendr/shared/base/view');
module.exports = View.extend({
	className: 'tasks_index_view'
}, {
	id: 'tasks/index'
});
```

### app/templates/tasks/index.hbs

```html
<h1>Tasks Index</h1>
```

### 動作確認

* http://localhost:3030/tasks

"Tasks Index"が表示されれば成功。

## クライアント側のスクリプトを準備

### npm install

```bash
$ npm i --save grunt-browserify@1.3.2 rendr-handlebars
```

Browserifyが2系だとうまくいかないので、1系を指定する。（執筆時現在）

### assets/vendor/jquery.min.js

適当に持ってくる。

### Gruntfile.js

追加する。

```js
    browserify: {
      options: {
        debug: true,
        alias: [
          'node_modules/rendr-handlebars/index.js:rendr-handlebars'
        ],
        aliasMappings: [
          {
            cwd: 'app/',
            src: ['**/*.js'],
            dest: 'app/'
          }
        ],
        shim: {
          jquery: {
            path: 'assets/vendor/jquery.min.js',
            exports: '$'
          }
        }
      },
      app: {
        src: [ 'app/**/*.js' ],
        dest: 'public/mergedAssets.js'
      }
    }
```

### grunt

```bash
$ grunt browserify
```

これで`public/mergedAssets.js`が出来上がる。

### app/router.js

クライアント側のrouting？　そのまま使えば良いや。

必要なら、`.extend()`を実装していないので、自力で継承する。

```js
var Router = require('rendr/client/router');
module.exports = Router;
```

### app/views/task/index.js

イベントの操作を追加。

```js
var View = require('rendr/shared/base/view');
module.exports = View.extend({
	className: 'tasks_index_view',

	events: {
		'click .js-get': 'onclickGet'
	},

	onclickGet: function(event) {
		alert('onclickGet');
	}
}, {
	id: 'tasks/index'
});
```

### app/templates/__layout.hbs

JSファイルを読み込んで起動するように。

```html
<!DOCTYPE html>
<title>Hello world!!</title>
{{{body}}}
<script src="/mergedAssets.js"></script>
<script>
(function() {
	var App = window.App = new (require('app/app'))({{json appData}});
	App.bootstrapData({{json bootstrappedData}});
	App.start();
})();
</script>
```

### 確認

ボタンを押すとアラートが出るようになったはず。

## APIサーバーへ接続

### index.js

rendrの設定に、APIの通信先を設定する。

```js
app.use(rendr.createServer({
	dataAdapterConfig: {
		'default': {
			host: 'localhost:3000',
			protocol: 'http'
		}
	}
}));
```

### アダプター

`/api/*` がAPIサーバーへのアダプターになっているみたい。自動で用意されている。

* http://localhost:3030/api/-/tasks

### APIサーバー

WWWサーバー（rendr）とは別に用意する必要がある。

例えばこんな感じ。

```js
var express = require('express');
var app = express()
app.use(function(req, res, next) {  // どんなアクセスもこれを返す
	res.set('Content-Type', 'application/json');
	var data = [];
	for (var i=0; i<10; i++) {
		data.push({ id:i+1, title:'Task#'+i });
	}
	res.send(200, JSON.stringify(data));
});
app.listen(3000);
```

### 確認

別コンソールからAPIサーバーを `node api-server.js`で起動しておくと、先の`/api/*`にJSONが表示れるはず。

## Backbone.Model/Collectionで情報を取得

クライアント側を、普通のBackboneと同じような方法で実装する。

### app/views/tasks/index.js

ボタン押下時の処理で`fetch()`するようにする。

```js
	initialize: function() {
		var tasks = this.collection = new Tasks();
		tasks.app = this.app;
		this.listenTo(tasks, 'add', this.addItem);
		this.listenTo(tasks, 'error', function(){ console.error('ERR', arguments); });
	},

	onclickGet: function(event) {
		var tasks = this.collection;
		tasks.fetch();
	},

	addItem: function(task) {
		console.log(task.toJSON());
	}
```

`tasks.app=this.app`を設定しないと`fetch`が失敗する。ここ、instanceのより良い作成方法があるはず。あとで確認しよう。

### app/collections/tasks.js

```js
var Collection = require('rendr/shared/base/collection');
var Task = require('../models/task');
module.exports = Collection.extend({
	model: Task,
	url: '/tasks'
}, {
	id: 'Task'
});
```

### app/model/task.js

```js
var Model = require('rendr/shared/base/model');
module.exports = Model.extend({
	url: '/tasks/:id'
}, {
	id: 'Task'
});
```

## 確認

ボタンを押すと、取得した配列がコンソールに出力されるはず。

## クライアント側で出力する

情報を取得できたので、張り切って出力する。

### app/views/tasks/index.js

CollectionとSubView（これから作る）を読み込んで、

```js
var Tasks = require('../../collections/tasks');
var TaskItemView = require('./task_item');
```

出力します。

```js
	addItem: function(task) {
		var itemView = new TaskItemView({ app:this.app, model:task });
		itemView.render().$el.appendTo(this.$list);
	}
```

### app/templates/tasks/index.hbs

出力先を追加。

```html
<ul class="js-task-list"></ul>
```

### app/views/tasks/task_item.js

```js
var View = require('rendr/shared/base/view');
var Task = require('../../models/task');
module.exports = View.extend({
	className: 'tasks_task_item_view'
}, {
	id: 'tasks/task_item'
});
```

名前とか、こういうので良いのかどうかよくわかんない……。

### app/templates/tasks/task_item.hbs

```html
<li>
	<label>
		<input type="checkbox" name="task[id]" value="{{id}}" />
		<span class="title">{{title}}</span>
	</label>
</li>
```

### 確認

"GET"ボタンを押すと、テンプレートを使って画面に出力されるはず。

## サーバー側で描画

### 

---
---
---

日記はここで途切れている。