Riot.js 3.3 と Lumen 5.4 でつくる 初めてのSPA つなぎこみ編
はじめに
最近 Riot が気になっていたので、
Laravel製軽量フレームワーク Lumen 5.4 と Riot 3.3 で簡単なブログを作ってみることにした。
今回はいよいよフロントエンドとバックエンドをつなぎこむ。 フロントエンドの ajax まわりを実装していく。
他のエントリーはこちら。
スポンサーリンク
作るもの
いろいろ頑張って最終的にこんな感じのブログを作る。
- 記事一覧の表示(カテゴリーの絞り込みあり)
- 記事詳細の表示
- 記事の新規作成・更新
記事一覧

記事詳細

記事編集

記事保存

前提
- Lumen 5.4.5
- Riot.js 3.3.1
手順
- ナビゲーション画面(ajax でカテゴリー一覧を取得)
- 一覧画面(ajax で記事一覧を取得)
- 詳細画面(ajax で記事詳細を取得)
- 編集画面(ajax で新規作成・更新処理)
1. ナビゲーション
ナビゲーションに表示カテゴリーをマスターデータから取ってくる。
カスタムタグの <script> 部分を変更する。
navbar.tag
<script>
// this.categories = [
// {id: 1, name: 'Tech'},
// {id: 2, name: 'Book'},
// {id: 3, name: 'Hobby'},
// {id: 4, name: 'Others'},
// ]
// console.log(this.categories)
// ajax 呼び出しの中では、`this` が Riot のタグインスタンスではなく、ajax のレスポンスオブジェクトを参照してしまうため、`this` を `self` に代入。
var self = this
$.ajax({
type : 'GET',
url : window.location.origin + '/api/v1/categories',
data : {}
})
.done(function(res){
self.categories = res
// テンプレート変数と子要素を更新
self.update()
})
.fail(function(){
// bootstrap modal を表示
$("#connection-error-dialog").modal();
// bootstrap modal が閉じた時のイベント
$('#connection-error-dialog').on('hidden.bs.modal', function (e) {
window.location.href = window.location.origin
})
})
.always(function(){
console.log('GET categories')
})
</script>
ポイント
var self = thisとすることで Riot のタグインスタンスを ajax 内でも使えるようにする。self.update()で現在のタグインスタンス上のすべてのテンプレート変数を更新する。(参考)$("#connection-error-dialog").modal();は Bootstrap の modal 関数$('#connection-error-dialog').on('hidden.bs.modal', function (e) {});も同じく Bootstrap の modalイベント。
2. 一覧画面
list.tag
<script>
// this.list = [
// {
// id: 1,
// title: 'Hello, world!',
// text: 'Some quick example text to build on the card title and make up the bulk of the cards content.',
// categories: ['Tech', 'Book'],
// },
// {
// id: 2,
// title: 'Hello, world!',
// text: 'Some quick example text to build on the card title and make up the bulk of the cards content.',
// categories: ['Book', 'Hobby', 'Others']
// },
// {
// id: 3,
// title: 'Hello, world!',
// text: 'Some quick example text to build on the card title and make up the bulk of the cards content.',
// categories: ['Hobby', 'Others']
// },
// {
// id: 4,
// title: 'Hello, world!',
// text: 'Some quick example text to build on the card title and make up the bulk of the cards content.',
// categories: ['Others']
// },
// ];
// console.log(this.list)
var self = this
// opts 変数の中にルータから渡されたオブジェクトが格納されている
console.log(opts)
var params = {}
if (opts.categoryName) params.category = opts.categoryName
$.ajax({
type : 'GET',
url : window.location.origin + '/api/v1/posts',
data : params
})
.done(function(res){
self.list = res
for (var i=0; i<self.list.length; i++) {
// タイトルが60文字以上だった場合省略
if (self.list[i].title.length >= 60) {
self.list[i].title = self.list[i].title.slice(0, 60) + '...'
}
// テキストが100文字以上だった場合省略
if (self.list[i].text.length >= 100) {
self.list[i].text = self.list[i].text.slice(0, 100) + '...'
}
self.list[i].title = self.list[i].title.replace(/(\r\n)|\n|\r/g, "<br />")
self.list[i].text = self.list[i].text.replace(/(\r\n)|\n|\r/g, "<br />")
}
self.update()
// ajax 終了前にタグがマウントされてしまうため、
// タグを再マウントして描画しなおす。
riot.mount('raw')
})
.fail(function(){
$("#connection-error-dialog").modal();
$('#connection-error-dialog').on('hidden.bs.modal', function (e) {
window.location.href = window.location.origin
})
})
.always(function(){
console.log('GET posts.')
});
</script>
ポイント
- ルータから渡された変数は
optsでアクセスできる。ここでは、カテゴリーでソートされたときのカテゴリー名を取得する。 - テンプレート内にある カスタムタグ
<raw>は、ajax 通信が終わる前にマウントされてしまうため、ajax 通信が終わったあとにriot.mount('raw')で再マウントすることで描画し直している。
3. 詳細画面
ここは一覧画面とほぼ同じ。
post.tag
<script>
// this.id = 1
// this.title = 'Hello, world!'
// this.text = 'Some quick example text to build<br/> on the card title and make up the bulk of the cards content.'
// this.categories = ['Tech', 'Book']
var self = this
console.log(opts)
var id = opts.id
$.ajax({
type : 'GET',
url : window.location.origin + '/api/v1/posts/' + id,
data : {}
})
.done(function(res){
self.id = res.id;
self.title = res.title.replace(/(\r\n)|\n|\r/g, "<br />")
self.text = res.text.replace(/(\r\n)|\n|\r/g, "<br />")
self.categories = res.categories
self.update()
riot.mount('raw')
})
.fail(function(){
$("#connection-error-dialog").modal();
$('#connection-error-dialog').on('hidden.bs.modal', function (e) {
window.location.href = window.location.origin
})
})
.always(function(){
console.log('GET post.')
});
</script>
4. 編集画面
edit.tag
<script>
// this.categories = [
// {id: 1, name: 'Tech'},
// {id: 2, name: 'Book'},
// {id: 3, name: 'Hobby'},
// {id: 4, name: 'Others'},
// ]
// console.log(this.categories)
var self = this
var id = opts.id // 'new' か 記事ID
self.post = {}
// category の取得
$.ajax({
type : 'GET',
url : window.location.origin + '/api/v1/categories',
data : {}
})
.done(function(res){
self.categories = res
self.update()
})
.fail(function(){
$("#connection-error-dialog").modal();
$('#connection-error-dialog').on('hidden.bs.modal', function (e) {
window.location.href = window.location.origin
})
})
.always(function(){
console.log('GET categories.')
})
// 更新の場合、デフォルト値を設定
if (id !== 'new') {
$.ajax({
type : 'GET',
url : window.location.origin + '/api/v1/posts/' + id,
data : {}
})
.done(function(res){
self.post = res
self.update()
})
.fail(function(){
alert('Connection error.');
})
.always(function(){
console.log('GET post.')
})
}
cancel() {
history.back()
}
submit(e) {
e.preventDefault()
var form = $('#edit-form')
var type = 'POST';
var url = window.location.origin + '/api/v1/posts'
var data = form.serialize()
// 更新の場合はPUT
if (id !== 'new') {
type = 'PUT';
url = window.location.origin + '/api/v1/posts/' + id
data = form.serialize() + '&id=' + id
}
$.ajax({
type : type,
url : url,
data : data
})
.done(function(res){
console.log('Submitted.')
$("#saved-dialog").modal();
$('#saved-dialog').on('hidden.bs.modal', function (e) {
window.location.href = window.location.origin
})
})
.fail(function(){
$("#connection-error-dialog").modal();
$('#connection-error-dialog').on('hidden.bs.modal', function (e) {
window.location.href = window.location.origin
})
})
.always(function(){
console.log('GET categories.')
})
}
</script>
ポイント
- 編集画面は新規作成と更新があるため、更新の場合は既存データを取得して表示させる
まとめ
Riot と Lumen でブログを作った。
Riot は本当に簡単で、やりたいことは大体ドキュメントを見ればすぐわかる。
書き方もシンプルだし、今後 SPA を作るときは Riot が最も有力な候補となりそう。
関連記事
スポンサーリンク