はじめに
オリジナルアプリを作成中、ストックしたレシピの検索を非同期で実装したいと思い挑戦。
その中でいくつか躓いた点があったため記録として残します。
以下が最終的な形です。
完成の動きはQiitaのストック一覧の検索
を目指しました。
もくじ
1. 検索フォーム設置
2. リレーション関係であるデータの取得
3. 非同期検索機能の実装
4. aタグのリンク
5. 日付のformatの表示方法
6. レンダリングの並び順を変える
条件
macOS: "11.2.3 Big Sur"
Laravel Framework: "6.20.27"
PHP: "8.0.7"
nginx: "1.18"
mysql: "8.0"
vue: "2.6.14"
使用するファイル
note.blade.php
ストック一覧のページ
AsyncSearch.vue
非同期の検索処理
1. 検索フォーム設置
検索に使用するフォームを作成します。
<template>
<div>
<input type="text" class="search_note" v-model="keyword" placeholder="ノート内を検索">
</div>
<!-- 一部省略 -->
</template>
<script>
data() {
return {
keyword: '',
}
},
</script>
2. リレーション関係であるデータの取得
blade側からストックしたレシピのデータをAsyncSearch.vue
へ渡します。
<async-search
:stock-recipes = '@json($recipes)' // ストックされたレシピ情報が格納
>
</async-search>
vue側では、blade
から送られてきた、「ストックされている全レシピの情報の配列データ($recipes
)」をv-for
で1つずつ表示する処理を記述します。
<template>
<!-- 一部省略 -->
<div class="post-recipe-card" v-for="recipe in stockRecipes" :key="recipe.id">
<h3 class="card-title"> {{ recipe.title }} </h3>
</div>
<!-- 一部省略 -->
</template>
<script>
export default {
props: {
StockRecipes: {
type: Array,
default: [],
},
},
data() {
return {
keyword: '',
stockRecipes: this.StockRecipes,
}
},
</script>
例えば、レシピのタイトル
を表示させたい場合は、以下のように記述することで表示することができます。
{{ recipe.title }}
ただ、レシピに紐づく作成者の名前
やタグ
なども上記のようなノリで書こうとしたら以下のエラーが...。
<!-- レシピの作成者の名前を表示させたい -->
{{ recipe.user.name }}さん
<!-- Console上で以下のエラー -->
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
どうしたものか...となった時に、同じ境遇の方がいました。
ということで、Controllerでデータの取得の記述を少し修正。
N+1問題をすでに解消しているならば、ここは問題ないかなと思います。
$recipes = $user->note->sortByDesc('created_at')
->load('user', 'stocks', 'tags', 'mealType', 'mealClass');
これでリレーション関係であるデータの表示も実装完了!
3. 非同期検索機能の実装
メインの実装!
computed
に非同期検索の処理を記述します。
<script>
computed: {
// フォームに入力された文字に該当するデータの表示
filterRecipes() {
let filtered = [];
for (let i in this.stockRecipes) {
let recipe = this.stockRecipes[i];
if (
recipe.title.indexOf(this.keyword) !== -1 ||
recipe.meal_type.name.indexOf(this.keyword) !== -1 ||
recipe.meal_class.name.indexOf(this.keyword) !== -1
) {
filtered.push(recipe);
}
}
return filtered;
},
},
</script>
if
文で、各データに検索対象の文字が含まれていないかをチェックしています。
(以下は、title
、meal_type.name
、meal_class.name
をチェックの対象としています。)
<script>
if (
recipe.title.indexOf(this.keyword) !== -1 ||
recipe.meal_type.name.indexOf(this.keyword) !== -1 ||
recipe.meal_class.name.indexOf(this.keyword) !== -1
)
</script>
非同期検索の記述は以下を参考にしました。
4. aタグのリンク
レシピのタイトルまたは画像をクリック → そのレシピの詳細画面に遷移
という実装がしたく記述方法に少し悩んでいたところ、またまた同じ境遇の方がいたためこちらを参考にしました。
・ URLをvue.js側で定義する。
・ Bladeでは、 {{ route('') }}
で記述していたが、Vueではhttpを直接記述。
・ URL中にあるid
など、動的に変化するものは${}
で囲む。
<a :href="`http://localhost/recipes/${recipe.id}`">{{ recipe.title }}</a>
これで画面遷移の実装は完了!
5. 日付のformatの表示方法
現状
{{ recipe.created_at }}
と記述しているため、以下のような日付の表記になっています。
このままではどこか味気ないので、以下のように年月日表記にしたいところ...。
blade
では以下の記述で日付フォーマットを変更していましたが、vue
ではこの記述はできません。
{{ $recipe->created_at->format('Y年m月d日') }}に投稿
探してみると、どうやらvue-moment
というライブラリで変更できるそう。
早速、vue-moment
をインストール!
npm install vue-moment
Vue.jsにインポート
<script>
// vue-momentをインポート
import moment from "moment"
// 省略
</script>
ただ、これだけではフォーマットの変更はできないため以下のように
{{ recipe.created_at | createdDate }}
と記述することで、filters
に送られてきたデータを指定した日付のformatに変換して表示することができます。
<template>
{{ recipe.created_at | createdDate }}に投稿
// 省略
</template>
<script>
import moment from "moment"
// 日付のフォーマットを変更
filters: {
createdDate: function (date) {
return moment(date).format("YYYY年MM月DD日");
}
},
// 省略
</script>
filters
については以下を参考にしました。
6. レンダリングの並び順を変える
現状
v-for
で表示されるものは昇順で表示されるため、最新のストックしたレシピが下に下に表示されるようになっています。
降順にしたい場合、computed
で順番を入れ替えたデータを渡してあげることで解決できます。
<script>
// 一部省略
computed: {
reverseRecipes() {
let filtered = [];
for (let i in this.stockRecipes) {
let recipe = this.stockRecipes[i];
if (recipe.title.indexOf(this.keyword) !== -1 ||
recipe.meal_type.name.indexOf(this.keyword) !== -1 ||
recipe.meal_class.name.indexOf(this.keyword) !== -1) {
filtered.push(recipe);
}
}
return filtered.slice().reverse(); // 変更
},
},
</script>
おわり
Vue.jsの基本的なところで躓いたところが多かったですが、Vue.jsの記述やデータの渡し方などかなり勉強になりました。
まだまだ記述は学習するところが多いので引き続き学習を続けます!
【参考】
実装にあたり以下のサイトも参考にさせて頂きました。