
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、少し前に公開した Laravel で Svelte を使う 〜 よりシンプルを求めて2 〜 という記事では、最近よく見かけるようになったSvelte
を試してみました。
すると、嬉しいことにツイッターで shirocake さんから「Alpine.js は比較的シンプルですよ」という情報をいただいたので今回は Alpine.js を使ってシンプル探索をしてみたいと思います。
ちなみに、Alpine.js
はVue.js
に影響を受けてつくられたフレームワークで、書き方がホントによく似ています。
例えば、友達の家に行って年の近い弟がいたら、「うわっ、似てるなー」ってなるようなイメージです(笑)
実際には、v-if
→ x-if
になるなど、ほぼv
の部分がx
に置き換わっただけなのでVue.js
での開発があればすぐに使えるようになると思いますよ。
そこで
今回は前回と同様にLaravel + Alpine.js
でCRUD
(登録、表示、変更、削除)を実装してみます。
ぜひ何かの参考になりましたら嬉しいです。
【追記:2022.02.24】
shirocake さんからgetter
を使う方法を教えていただいたのでコードを少し変更しました。getter
を使うと変数のように使えます。再度ありがとうございます。m(_ _)m
「貴重な情報
ありがとうございます」
開発環境: Laravel 8.x、Alpine.js 3.8.1、axios 0.25.0
目次 [非表示]
前提として
今回も前回(& 前々回)と同じくarticles
テーブルを使用します。
そのため、もしまだarticles
テーブルを用意していない場合は以下を参考にして作成しておいてください。
参考ページ: Laravel + Livewire で CRUD を実装してみる 〜 よりシンプルを求めて 〜:DBまわりを用意する
なお、実際のテーブルはこうなります。
コントローラをつくる
コントローラも前回と全く同じになりますので、以下を参考にしてください。
参考ページ: Laravel で Svelte を使う 〜 よりシンプルを求めて2 〜:コントローラーをつくる
ビューをつくる
さぁ、ここからが今回のメインになります。
実際のコードは以下になります。
※ なお、今回はVue
との比較がしやすいように、変数やメソッドはVue
っぽく並べています。
resources/views/article/index.blade.php
<html>
<head>
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
<div class="p-5">
<h1 class="text-3xl mb-4">
Alpine.js のCRUDサンプル
</h1>
<div x-data="article" x-init="getArticles" class="grid grid-cols-2 gap-7">
<div>
<table class="w-full text-sm mb-5">
<thead>
<tr>
<th class="border p-2">タイトル</th>
<th class="border p-2">本文</th>
<th class="border p-2">操作</th>
</tr>
</thead>
<tbody>
<!-- ① x-for でループしてます -->
<template x-for="article in articles">
<tr>
<td class="border px-2 py-1" x-text="article.title"></td>
<td class="border px-2 py-1" x-text="article.content"></td>
<td class="border px-2 py-1 text-right">
<button
type="button"
class="bg-yellow-500 text-yellow-50 rounded p-2 text-xs"
@click="onEdit(article)">
変更
</button>
<button
type="button"
class="bg-red-600 text-red-50 rounded p-2 text-xs"
@click="onDelete(article)">
削除
</button>
</td>
</tr>
</template>
</tbody>
</table>
<!-- ② x-show は、直接タグにセットしても OK -->
<button
type="button"
class="bg-blue-500 text-blue-50 rounded p-2 text-xs"
x-show="hasPrevPage"
@click="onMovePage('prev')">
前へ
</button>
<button
type="button"
class="bg-blue-500 text-blue-50 rounded p-2 text-xs"
x-show="hasNextPage"
@click="onMovePage('next')">
次へ
</button>
</div>
<div>
<div class="text-green-700 p-3 bg-green-300 rounded mb-3" x-show="resultMessage">
<!-- ③ x-text でデータを表示します -->
<span x-text="resultMessage"></span>
</div>
<div class="mb-3">
<label for="title">タイトル</label>
<br>
<!-- ④ x-model で双方向バインディングします -->
<input id="title" type="text" class="border w-full p-1" x-model="params.title">
</div>
<div class="mb-4">
<label for="content">本文</label>
<br>
<textarea id="content" rows="7" class="border w-full p-1" x-model="params.content"></textarea>
</div>
<!-- ⑤ @**** でイベントをセットできます -->
<button type="submit" class="bg-purple-700 text-purple-50 p-2 rounded" x-show="isModeCreate" @click="onSubmit">登録する</button>
<button type="submit" class="bg-blue-700 text-blue-50 p-2 rounded" x-show="isModeEdit" @click="onSubmit">変更する</button>
</div>
</div>
</div>
<!-- ⑥ ここに defer がないとうまくいきません -->
<script defer src="https://unpkg.com/alpinejs@3.8.1/dist/cdn.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.25.0/axios.min.js"></script>
<script>
const article = () => {
return {
// data
mode: 'create', // `create` or `edit`
params: {
title: '',
content: '',
},
articles: [],
page: 1,
resultMessage: '',
hasPrevPage: false,
hasNextPage: false,
// methods
getArticles() {
this.articles = [];
const url = '{{ route('article.list') }}?page=' + this.page;
axios.get(url)
.then(response => {
this.articles = response.data.data;
this.hasPrevPage = response.data.prev_page_url !== null;
this.hasNextPage = response.data.next_page_url !== null;
});
},
onEdit(article) {
// ⑦ - 1 スプレッド構文を使うと省コードになります
this.params = { ...article }; // オブジェクトの複製
this.mode = 'edit';
},
onMovePage(mode) {
this.page += (mode === 'prev') ? -1 : 1;
this.getArticles();
},
onSubmit() {
if(confirm('送信します。よろしいですか?')) {
let url = '';
let additionalParams = {};
if(this.isModeCreate === true) {
url = '{{ route('article.store') }}';
} else if(this.isModeEdit === true) {
const articleId = this.params.id;
url = `{{ route('article.update', '') }}/${articleId}`;
additionalParams = { _method: 'PUT' };
}
// ⑦ - 2 スプレッド構文を使うと省コードになります
const data = { // オブジェクトの合体
...this.params,
...additionalParams
};
axios.post(url, data)
.then(response => {
if(response.data.result === true) {
this.getArticles();
this.params = {
id: '',
title: '',
content: '',
};
this.resultMessage = '保存が完了しました!';
setTimeout(() => { // 3 秒後にメッセージをクリア
this.resultMessage = '';
}, 3000);
}
});
}
},
onDelete(article) {
if (confirm('削除します。よろしいですか?')) {
const url = '{{ route('article.destroy', '') }}/' + article.id;
axios.delete(url)
.then(response => {
if (response.data.result === true) {
this.getArticles();
}
});
}
},
// Computed
// ⑧ 実際はちょっと違いますが Computed の代わりにしてます
get isModeCreate() {
return this.mode === 'create';
},
get isModeEdit() {
return this.mode === 'edit';
}
};
};
</script>
</body>
</html>
では、少しコードが長いのでひとつずつ見ていきましょう。
① x-for でループしてます
x-for
は、Vue
と同じようにデータをループさせる記述です。
そのため、テーブルでリストを作るのに重宝するでしょう。
【 ご注意】
なお、これはAlpine.js
のクセというか特徴なのですが、x-for
は<template></template>
タグの中だけで有効です。
つまり、<div>
や<li>
タグには直接使うことはできません。
また、これはx-if
もこれと同じで、今回のコードで使っていないのはこれが原因です。
② x-show は、直接タグにセットしてもOKです
先ほどご紹介したように、x-if
は<template></template>
タグにセットしなければいけませんが、x-show
は直接<div>
などのタグに書き込むことができます。
※ そのため、(使いどころが違うのですが)個人的にはVue
で慣れているのでx-show
の方が好きかな、というカンジでした。
③ x-text でデータを表示します
これもVue
と同じで、タグの中身の値を指定する記述です。
また、x-html
も存在しているので、HTMLタグを有効にすることもできます
④ x-model で双方向バインディングします
v-model
と同様で、<input>
タグの中身が変われば、自動的に変数の中身も変更し、さらに、その逆もやってくれるという「リアクティブ」な機能です。
⑤ @***** を使ってイベントをセットできます
@click="*****"
でクリックイベントをセットすることができます。
なお、これもVue
と同様にx-on:click
という書き方もできます。
⑥ ここに defer がないとうまくいきません
これも特徴のひとつと言っていいと思うのですが、どうやらAlpine.js
は「先に個別コードを用意してから本体を呼び出さないとエラーになる」ようです。
そのため、defer
をセットすることで最後にAlpine.js
本体が読み込まれるようにしています。
⑦ スプレッド構文を使うと省コードになります
なお、これはAlpine.js
には直接関係はないのですが、便利な書き方なので「スプレッド構文(Spread operator
)」ご紹介させてください。
例えば、私は以前「オブジェクトを複製する」場合、以下のように書いていました。
const clonedData = Object.assign({}, data);
しかし、実はもっとシンプルに書くことができます。
const clonedData = { ...data };
いかがでしょう。
省コードで見やすくなりました。
ちなみに、スプレッド構文は、次のように2つのオブジェクトを合体させることもできます。
const mergedData = {
...data1, //
1つ目のオブジェクト
...data2 //
2つ目のオブジェクト
};
ぜひ便利なのでやってみてください
⑧ 実際はちょっと違いますが Computed の代わりにしてます
Vue
には、値を一時的にキャッシュしてくれる(つまり、同じ処理を何回も実行しない)機能Computed
がありますが、どうやらAlpine.js
には存在しないようでしたので、このような形で代用しました。
【追記:2022.02.24】
getter
を使う方法へ変更しました。
getter
を使うと、以下のように通常の変数のように使うことができるようになります。
if(this.isModeCreate === true) { //
getter なので () はいらない
} else if(this.isModeEdit === true) { //
getter なので () はいらない
}
情報ありがとうございます
なお、以下は通常の関数として使う方法で、投稿当時のものです。
そのため、Vue
では変数のように使うことができますが、今回はコード内では以下のように通常の関数のようにして使っています。
if(this.isModeCreate() === true) {
// 省略
} else if(this.isModeEdit() === true) {
// 省略
}
※ ただし、x-show
の中では変数のように使えます。
x-show="isModeCreate"
ルートをつくる
では、最後にルートです。
routes/web.php
use App\Http\Controllers\ArticleController;
// 省略
Route::prefix('article')->controller(ArticleController::class)->group(function() {
Route::get('/', 'index')->name('article.index');
Route::get('/list', 'list')->name('article.list');
Route::post('', 'store')->name('article.store');
Route::put('/{article}', 'update')->name('article.update');
Route::delete('/{article}', 'destroy')->name('article.destroy');
});
※ なお、Laravel 8.80
から有効になったcontroller()
メソッドを使っています。省コード、バンザイ
テストしてみる
では、テストを…と思いましたが前回と全く同じ挙動ですので、今回は割愛します。もしSvelte
版でよろしければ、以下をご参照ください。
参考ページ: テストしてみる
企業様へのご提案
今回のように、もしAlpine.js
を使った開発されていらっしゃったらお力になれるかと思います。
ぜひご希望でしたら、お問い合わせからご連絡ください。
お待ちしております。
ちなみに: 使ってみた感想
今回はじめてAlpine.js
を本格的に使ってみた感想をまとめてみました。(初見なので違っていたらご指摘ください。m(_ _)m)
いいなと思った部分
- Vueに慣れていれば、学習コストはとても低い
- Vue 3 で失ってしまった(と個人的に思ってる)シンプルさが残されている
- ビルドなしでも使える
- Blade 内に書けるので、PHPの変数や定数を埋め込むことができる
- (作者が Livewire も手がけているということで)Laravel との相性がいい
気になったところ
- x-for や x-if を使う場合、<template></template> タグを書かないといけない
- Vue にある computed がない
- (shirocake さんもご指摘になられていましたが)知名度がまだあまりない(ただし、GitHub でのスターは 2万近くあります。2022.02.02 現在)
おわりに
ということで、今回はLaravel + Alpine.js
でCRUD
機能をつくってみました。
今回も世界の天才がつくったフレームワークを体験することで、いろいろと勉強になることがありました。いつも感謝です。m(_ _)m
ちなみに、こういうテクノロジーを自分でつくりあげることができる天才たちはやっぱりスゴイですよね。
もし作品が人気になってもずっとメンテナンスが必要ですし、世界中の人が使い出すと、バージョンアップの際にいろんな意見が出てくるし…で、なかなか大変なんじゃないでしょうか。
faker.js
&color.js
の話じゃないですが、こういった方々は金銭的にも裕福になってほしいものです。(貧者の一灯というやつですが、私は少し前にCanonical
(Ubuntu
)に寄付しました)
ぜひ皆さんもAlpine.js
を試してみてくださいね。
ではでは〜
「卵 + 粉チーズ
→ 混ぜて電子レンジ
= フワフワ卵焼きできます」