質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Vue.js

Vue.jsは、Webアプリケーションのインターフェースを構築するためのオープンソースJavaScriptフレームワークです。

Vuex

Vuexは、Vue.js アプリケーションのための状態管理ライブラリです。アプリケーション内で使用するコンポーネントのための集中データストアを提供。コンポーネント同士でデータをやり取りし、処理のフローを一貫させたり、データの見通しを良くすることができます。

Q&A

1回答

2018閲覧

絞り込み検索機能について、複数・並列のフィルタをどう処理したらいいのか

chibi144

総合スコア64

Vue.js

Vue.jsは、Webアプリケーションのインターフェースを構築するためのオープンソースJavaScriptフレームワークです。

Vuex

Vuexは、Vue.js アプリケーションのための状態管理ライブラリです。アプリケーション内で使用するコンポーネントのための集中データストアを提供。コンポーネント同士でデータをやり取りし、処理のフローを一貫させたり、データの見通しを良くすることができます。

0グッド

0クリップ

投稿2019/11/12 02:15

編集2019/11/12 02:20

前提・実現したいこと

Vue.js、Vuexを使って用語集っぽいものを作っているのですが、絞り込み機能の実装に苦戦しています。

↓元になるCSVファイル。

見出語見出語読み語義タグ簡易版フラグ
林檎りんご赤くて甘い。果物1
人参にんじんオレンジ色で独特の風味。野菜1
菊芋きくいも「豚いも」と呼ばれたことも。野菜0
西瓜すいか熊本が生産量1位。野菜,果物1

用語集には通常版と簡易版があり、
簡易版フラグが1になっているもののみ、簡易版でも表示します。

想定しているフィルタ

イメージ説明

フィルタはすべてandの関係です。
templateではフィルタがかけられた後のデータ(getter/filteredData)を展開します。

検索条件の操作パネルで管理するフィルタ

state/filterの中に条件を保存しています。
「検索条件をクリア」(action/filterReset)により値が初期化されます。

  1. 「見出語」を対象としたテキスト入力検索(state/filter.text)
  2. 各行あ段のボタンを配置し、「見出語読み」を参照して絞り込み(state/filter.char)
  3. 用語のグルーピングをタグとする。(state/filter.tag)

すべてのタグをボタンとして配置し、「タグ」を参照して絞り込み
※1つの用語に対して複数個のタグがつくことを想定

操作パネル以外のフィルタ

通常版/簡易版をURLで切り分けます。(state/systemMode)
例)/glossary?mode=simple
「検索条件をクリア」(action/filterReset)により値が初期化されません。

課題

  1. getter/filteredDataの汚さをどうにかしたいのですが、方法がわかりません。

1つ1つのフィルタ処理を関数(action?getter?)として切り出す?

  1. 五十音のフィルタを実装するにあたって、参考にできるサイト・記事が見つけられませんでした。

何かもっと良い方法があれば教えていただきたいです。

  1. 「動きはするが、とにかく汚い。改善方法はわからない」という状況に陥ったとき、皆さんはどう解決しているのでしょうか。

filterやmapのcallbackの引数が乱雑なのはご容赦ください。
今回、template側は関係ないと思い記載を省いていますが、必要であれば追記・修正依頼をお願いします。

該当のソースコード

store/glossary.js

抜粋

javascript

1const getters = { 2 filteredData : state => { 3 var filteredData = state.data.slice() 4 5 // 五十音によるフィルタ 6 if (state.filter.char.length > 0) { 7 // 取得する文字の範囲を求める 8 // 選択した文字~次の文字の1文字前の範囲を取得 9 var premissionRange = [] 10 state.filter.char.map((c, i, self) => { 11 var startIndex = state.charList.indexOf(c) 12 var nextIndex = startIndex + 1 13 // 「わ行(五十音リスト最後の文字)」が来てしまったときの処理 14 // 現状、範囲を「わ」のみするという処理が書かれているが、アルファベット対応するなら使えない 15 if ( state.charList.length === startIndex + 1 ) nextIndex = startIndex 16 var startCharCode = c.charCodeAt(0) 17 var finishCharCode = state.charList[nextIndex].charCodeAt(0)-1 18 premissionRange.push([startCharCode, finishCharCode]) 19 }) 20 filteredData = filteredData.filter(x => { 21 var charCode = x.見出語読み.slice(0, 1).charCodeAt(0) 22 return premissionRange.some(val => { 23 return charCode >= val[0] && charCode <= val[1] 24 }) 25 }) 26 } 27 28 // タグによるフィルタ 29 if (state.filter.tag.length > 0) { 30 filteredData = filteredData.filter(x => { 31 return state.filter.tag.some(c => { 32 return x.タグ ? ~x.タグ.indexOf(c) : null 33 }) 34 }) 35 } 36 37 // 入力によるフィルタ 38 if (state.filter.text) { 39 var pattern = new RegExp('^' + state.filter.text + '.*', 'g') 40 filteredData = filteredData.filter(v => pattern.test(v.見出語) || pattern.test(v.見出語読み)) 41 } 42 43 // モードによるフィルタ 44 if (state.systemMode === 'simple') { 45 console.log(true) filteredData = filteredData.filter(v => v.簡易版フラグ) 46 } 47 48 return filteredData 49 } 50};

store全体

javascript

1const data = dataLoad() 2 3function dataLoad () { 4 var data = require('@/assets/csv/glossary.csv') 5 // 複数個カンマ区切りで渡されることを想定 6 const objectHeader = ['タグ'] 7 data = JSON.parse(JSON.stringify(data), function(key, value){ 8 if (~objectHeader.indexOf(key) && typeof(value) === 'string') value = value.split(',') 9 return value 10 }) 11 return data 12} 13 14const initialState = () => ({ 15 data: data, 16 filter: { 17 char: [], 18 tag: [], 19 text: '' 20 }, 21 charList: ['あ', 'か', 'さ', 'た', 'な', 'は', 'ま', 'や', 'ら', 'わ'], 22 searchMethod: { 23 method: ['TEXT_FORWARD', 'TEXT_FULL', 'TEXT'], 24 mode: 'TEXT_FORWARD' 25 } 26}); 27 28const state = () => ({ 29 ...initialState(), 30 systemMode: 'default' 31}) 32 33const getters = { 34 filteredData : state => { 35 var filteredData = state.data.slice() 36 37 // 五十音によるフィルタ 38 if (state.filter.char.length > 0) { 39 // 取得する文字の範囲を求める 40 // 選択した文字~次の文字の1文字前の範囲を取得 41 var premissionRange = [] 42 state.filter.char.map((c, i, self) => { 43 var startIndex = state.charList.indexOf(c) 44 var nextIndex = startIndex + 1 45 // 「わ行(五十音リスト最後の文字)」が来てしまったときの処理 46 // 現状、範囲を「わ」のみするという処理が書かれているが、アルファベット対応するなら使えない 47 if ( state.charList.length === startIndex + 1 ) nextIndex = startIndex 48 var startCharCode = c.charCodeAt(0) 49 var finishCharCode = state.charList[nextIndex].charCodeAt(0)-1 50 premissionRange.push([startCharCode, finishCharCode]) 51 }) 52 filteredData = filteredData.filter(x => { 53 var charCode = x.見出語読み.slice(0, 1).charCodeAt(0) 54 return premissionRange.some(val => { 55 return charCode >= val[0] && charCode <= val[1] 56 }) 57 }) 58 } 59 60 // タグによるフィルタ 61 if (state.filter.tag.length > 0) { 62 filteredData = filteredData.filter(x => { 63 return state.filter.tag.some(c => { 64 return x.タグ ? ~x.タグ.indexOf(c) : null 65 }) 66 }) 67 } 68 69 // 入力によるフィルタ 70 if (state.filter.text) { 71 var pattern = new RegExp('^' + state.filter.text + '.*', 'g') 72 filteredData = filteredData.filter(v => pattern.test(v.見出語) || pattern.test(v.見出語読み)) 73 } 74 75 // モードによるフィルタ 76 if (state.systemMode === 'simple') { 77 filteredData = filteredData.filter(v => v.簡易版フラグ) 78 } 79 80 return filteredData 81 } 82}; 83 84const actions = { 85 toggleCharFilter ({ commit, state }, char) { 86 if (state.filter.char.includes(char)) { 87 commit('removeCharFilter', char) 88 } else { 89 commit('addCharFilter', char) 90 } 91 }, 92 toggleTagFilter ({ commit, state }, tag) { 93 if (state.filter.tag.includes(tag)) { 94 commit('removeTagFilter', tag) 95 } else { 96 commit('addTagFilter', tag) 97 } 98 }, 99 setTextFilter ({commit}, text) { 100 commit('setTextFilter', text) 101 }, 102 changeSearchMethod ({commit, state}, mode) { 103 state.searchMethod.method.includes(mode) 104 ? commit('changeSearchMethod', mode) 105 : commit('changeSearchMethod', 'TEXT_FORWARD') 106 }, 107 setSystemMode ({commit}, mode) { 108 commit('setSystemMode', mode) 109 } 110}; 111 112const mutations = { 113 addCharFilter (state, payload) { 114 state.filter.char.push(payload) 115 }, 116 removeCharFilter (state, payload) { 117 state.filter.char = state.filter.char.filter(x => x !== payload) 118 }, 119 addTagFilter (state, payload) { 120 state.filter.tag.push(payload) 121 }, 122 removeTagFilter (state, payload) { 123 state.filter.tag = state.filter.tag.filter(x => x !== payload) 124 }, 125 setTextFilter (state, payload) { 126 state.filter.text = payload 127 }, 128 filterReset (state) { 129 Object.assign(state, initialState()) 130 }, 131 changeSearchMethod (state, payload) { 132 state.searchMethod.mode = payload 133 }, 134 setSystemMode (state, payload) { 135 state.systemMode = payload 136 } 137}; 138 139export default { 140 namespaced: true, 141 state, 142 getters, 143 actions, 144 mutations 145}

補足情報(FW/ツールのバージョンなど)

Vue: 2.6.10
Vuex: 3.1.0

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

データはサーバー側で管理してapiを用意しキーワードとマッチ方法を渡して
絞り込んだ値をjsonで返して、jsで表示するのが妥当だと思いますが
常に全データをやり取りすると相当なオーバーヘッドとメモリの無駄遣いだと思います

どうしてもクライアント側でフィルタ処理をしたいなら
jsonが配列型ならjsのfilterで処理すればいいでしょう

sample

jsonをクライアントでフィルタするならこう
※よみがなを前方、部分、完全で一致させる

javascript

1var data=[["林檎","りんご","赤くて甘い。","果物",1],["人参","にんじん","オレンジ色で独特の風味。","野菜",1],["菊芋","きくいも","「豚いも」と呼ばれたことも。","野菜",0],["西瓜","すいか","熊本が生産量1位。","野菜,果物",1]]; 2var keyword="りん"; 3var zenpou=data.filter(x=>new RegExp("^"+keyword).test(x[1])); 4console.log(zenpou); 5var keyword="ん"; 6var bubun=data.filter(x=>new RegExp(keyword).test(x[1])); 7console.log(bubun); 8var keyword="きくいも"; 9var kanzen=data.filter(x=>new RegExp("^"+keyword+"$").test(x[1])); 10console.log(kanzen);

※あとはデータの持ち方と、複合時の指定の仕方次第

投稿2019/11/12 02:50

編集2019/11/12 06:43
yambejp

総合スコア114829

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

chibi144

2019/11/12 04:19

ご回答ありがとうございます。 ①DB操作に不慣れな人にも元データを弄ってもらいたい。 ②↑の人たちのために編集用の画面を開発するコストに見合うほどのリターンが見込めない。 ③500語程度の登録で頭打ちになる。(用語の性質上、大幅な増加はありえない) といった事情から、csvファイルをloaderでjsonにして取り込む形で実装してみています。 違う質問になってしまいますが、 トータルで500語くらいの場合、 サーバー側で管理する(条件を変えるたびにAPIにアクセスし直してデータを取得し直す)のと、 フロント側で管理する(データの取得はローカルから1回。配列操作で表示用のデータを生成する)のでは どれくらいパフォーマンスが異なるものなのでしょうか。
yambejp

2019/11/12 06:33

500語くらいなら微妙なところですね jsonデータにしてajaxで読み込めばキャッシュも期待できるので クライアントでできないことはないかなとは思います
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問