LaravelとVue.jsを使い、TwitterやQiitaのように画面を切り替えずにボタンクリックでユーザーのフォローとフォロー解除をできるようにしてみます。
今回は下記の構成で作ってみます。
LaravelとVue.jsはもうちょっとバージョンが低くても大丈夫だと思います。Bootstrapについては、今回通信中のローディング表示にspinnerを使っているため最新のものが望ましいです。(4.0だと動きませんが、そこの部分のCSSをコピペしてくれば一応動きます)
ユーザーはLaravelのデフォルトのもので大丈夫です。ただし今回の例はモデルをapp/Models
に移動していますので、同様の構成にしておく必要があります。ただ、他の設定ファイルの修正などもあり面倒ではありますので、逆に当記事に書いているuse App\Models\User
等をuse App\User
等に読み替えて試していただいたほうが楽かもしれません。
あとはフォローするユーザーとフォローされるユーザーを繋げるための中間テーブルを作ります。
マイグレーションは下記のような感じです。普通の命名規則としては双方のテーブルの単数名を組み合わせるのですが、そうするとuser_userになってしまい変な感じとわかりにくい感じがしたので適当に変えています。
Schema::create('follow_users', function (Blueprint $table) {
$table->unsignedInteger('user_id');
$table->unsignedInteger('followed_user_id')->index();
$table->unique(['user_id', 'followed_user_id']);
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('followed_user_id')->references('id')->on('users')->onDelete('cascade');
});
直接テーブルを操作したいので一応モデルも作っておきます。中間テーブル用のPivotを継承し、今回のテーブル構成に合わせて設定もいくつか行っています。
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class FollowUser extends Pivot
{
protected $table = 'follow_users';
public $timestamps = false;
protected $guarded = [];
}
ユーザー(User) 中間テーブル(FollowUser) フォローされるユーザー(User)
という構成のリレーションを設定します。具体的にはLaravelのマニュアルでいうところのMany To Many
で、belongsToMany
で設定します。
ユーザーのモデルに下記のメソッドを追加します。
public function followUsers()
{
return $this->belongsToMany(self::class, 'follow_users', 'user_id', 'followed_user_id')
->using(FollowUser::class);
}
普通はこんなに設定は不要でマニュアル通りで良いのですが、今回は同じテーブル同士のMany To Manyのためちょっと設定が増えています。
FollowUserControllerに下記のフォローとフォロー解除用のメソッドを追加します。
public function store(Request $request)
{
$followedUser = User::findOrFail($request->input('id'));
FollowUser::firstOrCreate([
'user_id' => Auth::id(),
'followed_user_id' => $followedUser->id,
]);
return response()->json(['result' => true]);
}
public function destroy($id)
{
$followedUser = User::findOrFail($id);
$user = Auth::user();
$user->followUsers()->detach($followedUser->id);
return response()->json(['result' => true]);
}
ファイルの戦闘のnamespaceのあとに上記で利用しているクラスのuseも追加しておきます。
use App\Models\FollowUser;
use App\Models\User;
上記のような感じで、attach, detachを使うことで割当と解除を行うことができます。上記だとattachを使っていませんが、複数のタブを開いていて変な操作をした場合などに重複エラーが出てしまい、わざわざそのための処理を入れるのも面倒だったためfirstOrCreate呼び出しで完結するようにしています。
各メソッドの最初のユーザー取得も実際には不要ですが、一応変なデータが出来ないようにユーザーの存在チェック代わりに入れています。
サーバー側は出来たので、あとは実際にフォローをトグルするためのUIをVue.jsで作っていきます。
ひとまずコンポーネントのスクリプトの定義周りは下記のような形にしました。(TypeScriptで書いているので、JavaScriptの場合は適宜置き換えてください)
import { Vue, Component, Prop } from 'vue-property-decorator'
import axios from 'axios'
@Component
export default class UserFollow extends Vue {
@Prop({
type: String,
required: true
})
id!: string
@Prop({
type: Boolean,
default: false
})
following!: boolean
currentFollowing = this.following
sending = false
最初のフォロー状態はプロパティで受け取りますが、あとはボタン操作によって切り替えるため、コンポーネント内ではcurrentFollowingを使っていきます。ローディング表示や、ボタンの連打などを防ぐためsendingフラグを入れています。
フォローボタン、フォロー解除ボタンを押した時のメソッドを追加します。(エラー処理は省略しているため適宜入れてください)
async follow() {
if (this.sending) {
return
}
this.sending = true
const data = { id: this.id }
await axios.post('/follow-users', data)
this.currentFollowing = true
this.sending = false
}
async unfollow() {
if (this.sending) {
return
}
this.sending = true
await axios.delete(`/follow-users/${this.id}`)
this.currentFollowing = false
this.sending = false
}
あとは上記を呼び出すためのボタンを配置します。
<template>
<div>
<button v-if="currentFollowing" type="button" class="btn btn-point btn-raised" @click="unfollow">
<div v-if="sending" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">Sending...</span>
</div>
<div v-else>フォロー中</div>
</button>
<button v-else type="button" class="btn btn-default btn-raised" @click="follow">
<div v-if="sending" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">Sending...</span>
</div>
<div v-else>
フォローする
<i class="material-icons">add</i>
</div>
</button>
</div>
</template>
sendingがtrueの場合はspinnerのくるくるローディングが表示されます。また、フォロー状態によってボタンも切り替わります。
以上のような感じでスムーズに使えるフォロー、フォロー解除ボタンを作ってみました。参考にできる部分があれば是非利用してみてください。その他何かこんなものの作り方を教えて欲しい! みたいなあればお気軽にコメントいただければと思います。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント