2019.9.28
Vueでカクテルデータベースをリファインする話#6【v-for】
やっとここまできたよ・・・・・・(吐血)
v-forとかいう仕組みを使って、パパっとデータ読み出しを実装します。やってることは超単純なのですぐできると思います。
要するにforeachです
日本語でいうと
リストレンダリングっていうらしいですね。
めんどくさそうなとことしては、ソート周りですかね。現状でもすっごい雑なソートを導入してるんですが、もうちょっとエレガントに解決できる方法を探したいところ。
ドキュメントを読んでると、元となった配列自体をソートすると勝手に再描画してくれるみたいな書き方されてるような気がするんですけど・・・・・・そうだったらめちゃくちゃラクですね。
とりあえず表示だけはサクっとやっちゃいましょうか。
<template>
<div>
<div class="result_text result_cocktail">
<span id="txt_result">カクテル検索結果</span> {{this.cocktails.length}}件ヒットしました。(検索語句:<b>{{this.searched_word}}</b>)
</div>
<div class="result" id="cocktail_result">
<table class="cocktail_table">
<thead>
<tr>
<th class="column_base sort" data-sort="column_base">ベース</th>
<th class="column_name sort" data-sort="column_name">名前</th><th class="column_name_eng sort" data-sort="column_name_eng">Name</th>
<th class="column_sweet sort" data-sort="column_sweet">甘さ</th>
<th class="column_rate sort" data-sort="column_rate">評価</th>
<th class="column_strength sort" data-sort="column_strength">度数</th>
<th class="column_movie sort" data-sort="column_movie">動画</th>
</tr>
</thead>
<tbody class="list">
<tr class="" v-for="c in this.cocktails">
<td class="column_base"><a href="http://localhost/php/Cocktail/public/category/detail/10">{{c.base.base_name}}<span class="base_eng">({{c.base.base_eng}})</span></a></td>
<td class="column_name"><a href="http://localhost/php/Cocktail/public/cocktail/detail/7">{{c.name}}</a></td>
<td class="column_name_eng"><a href="http://localhost/php/Cocktail/public/cocktail/detail/7">{{c.name_eng}}</a></td>
<td class="column_sweet">甘さ{{c.sweet_degree}}</td>
<td class="column_rate" >rate{{c.rate}}</td>
<td class="column_strength">{{c.volume}}度</td>
<td class="column_movie">{{c.youtube}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState("search",{
count: state => {
if(state.fromNameResult===undefined){return undefined}
return state.fromNameResult.count
},
is_searching:state=>state.is_searching,
cocktails:state=>state.fromNameResult.Cocktail,
searched_word:state=>state.search_word
}),
},
}
</script>

なんのこともありませんね。めちゃくちゃ簡単に実装できました。
表示の調整に関しては算出プロパティとか使えば簡単に実装できるので割愛して、大きな障壁となりそうなのは
ソートですね。
目標としては、カラムの文字をクリックしたら、その列を元にソート、逆ソートされるみたいなやつをやりたいと思います。
とりあえず
参考サイト開いてぱぱっとやっていきたいなぁと思いまする。
いろんな実装例を見て、基本的な動作の流れを考えてみましょう。
算出プロパティでクラスの管理して、@clickでソートメソッドを呼び出し、sliceで配列をコピーして返す、みたいな流れがオーソドックスなもののようですね。
theadとtbodyでコンポーネントを分けるのが賢そうですが、まずは1ページで呼び出してみましょう。
ここでまず気をつけないといけないのは算出プロパティ、つまり
computedは引数を持つことが出来ないということですね。
なんでそういう仕様なのかはわからないんですが、たぶん外部からの状態に依存したくない・・・・・・?ってことかな?
つまり、例えば上の画像だと、甘さは数字で表現していますが現行のように「甘口」「辛口」などという表記にわけるためには、コンポーネントで切り分けてpropに渡してあげて、っていう操作が必要になるっぽいですね。ということを頭に片隅においておかなくてはなりません。
それはともかくとして、ソート機能ですね。
これはめっちゃ簡単に実装できました。今までの苦労はなんだったんだってぐらい簡単です。Vueを取り入れて正解でしたね。リアクティブな解決がここまで便利だとは・・・・・・。
ただ、javascriptネイティブの比較関数だとソートの並び順に不満がでてきてしまうので
このエントリを参考に手を加えたコードがこちら。
<template>
<div>
<div class="result_text result_cocktail">
<span id="txt_result">カクテル検索結果</span> {{this.results.length}}件ヒットしました。(検索語句:<b>{{this.results.searched_word}}</b>)
</div>
<div class="result" id="cocktail_result">
<table class="cocktail_table">
<thead>
<tr>
<th class="column_base sortable" :class="sortedClass('base')" @click="sortBy('base')">ベース</th>
<th class="column_name sortable" :class="sortedClass('name')" @click="sortBy('name')">名前</th><th class="column_name_eng sortable" :class="sortedClass('name_eng')" @click="sortBy('name_eng')">Name</th>
.....(略)
</tr>
</thead>
<tbody class="list">
<tr class="" v-for="c in sortedList">
<td class="column_base"><a href="http://localhost/php/Cocktail/public/category/detail/10">{{c.base.base_name}}<span class="base_eng">({{c.base.base_eng}})</span></a></td>
<td class="column_name"><a href="http://localhost/php/Cocktail/public/cocktail/detail/7">{{c.name}}</a></td>
<td class="column_name_eng"><a href="http://localhost/php/Cocktail/public/cocktail/detail/7">{{c.name_eng}}</a></td>
.....(略)
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Helper from "../../Modules/Helper"
export default {
data:function(){
return {
results:null,
sort: {
key: '',
isAsc: false
}
}
},watch: {
'$route' (to, from) {
// ルートの変更の検知...
this.results= this.cocktails.slice();
}
},
created: function () {
// `this` は vm インスタンスを指します
console.log(this.cocktails)
this.results= this.cocktails.slice();//「読み込み時のみ」ストアの配列をコピーする(でないとリアクティブ変更になってしまう)
this.results.searched_word= this.searched_word;
},
computed: {
...mapState("search",{
count: state => {
if(state.fromNameResult===undefined){return undefined}
return state.fromNameResult.count
},
is_searching:state=>state.is_searching,
cocktails:state=>state.fromNameResult.Cocktail,
searched_word:state=>state.search_word
}),
//ソート関数
sortedList: function() {
let list = this.results.slice();
if(this.sort.key) {
list.sort((a, b) => {
if(this.sort.key==="base"){
//ベースの場合、IDではなく名前で
a = a[this.sort.key].base_name;
b = b[this.sort.key].base_name;
}else if(this.sort.key==="sweet_degree"){
//甘さの場合、undefinedは常に-99扱い
if(a[this.sort.key]===null && b[this.sort.key]===null){return 0}
if(a[this.sort.key]===null){return 1}
if(b[this.sort.key]===null){return -1}
return a[this.sort.key]- b[this.sort.key];//数字ソート
}else {
//それ以外(通常) 英文字は大文字小文字が混在してるとバグるので調整
console.log(a[this.sort.key])
a = (!!a[this.sort.key])?a[this.sort.key].toLowerCase():a[this.sort.key];//nullだとtoLowerCaseが呼び出せないので
b = (!!b[this.sort.key])?b[this.sort.key].toLowerCase():b[this.sort.key];
}
if(this.sort.key==="base"||this.sort.key==="name"){
//ひらがなとカタカナを統一する
a = Helper.katakanaToHiragana(a.toString());
b = Helper.katakanaToHiragana(b.toString());
}
return (a === b ? 0 : a > b ? 1 : -1) * (this.sort.isAsc ? 1 : -1);
});
}
return list;
}
},
methods:{
sortBy (key) {
this.sort.isAsc = this.sort.key === key ? !this.sort.isAsc : false;
this.sort.key = key;
},
sortedClass: function(key) {
return this.sort.key === key ? `sorted ${this.sort.isAsc ? 'asc' : 'desc' }` : '';//class="sorted asc(desc)"
}
}
}
</script>
実際のコード丸コピペです。
関係ないコメントが残ってますね
HTML部分は単純ですね。v-forで算出プロパティを読み込んでループを回します。
なぜ直接読み込まないのかというと、javascriptのソート関数が破壊的メソッドなので、コピーした配列を返す必要があったためです。
条件分岐がいろいろありますけど、そのままだとうまいこと評価できないところがあったのでこうせざるを得ませんでした。もうちょっとエレガントに書きたかったというか、後々のために関数の切り出しとかできたら良いなあとは思いつつも、別にいま最適化しなくてもそこまで複雑じゃないしいっかなって感じです。
んでまぁ、sortっていうオブジェクトに状態を保存しておく、っていう単純な構成ですね。
watch部分によくわからん記述がありますが、これに関しては明日のネタにします。