前提・実現したいこと
Vue.js、Vuexを使って用語集っぽいものを作っているのですが、絞り込み機能の実装に苦戦しています。
↓元になるCSVファイル。
見出語 | 見出語読み | 語義 | タグ | 簡易版フラグ |
---|---|---|---|---|
林檎 | りんご | 赤くて甘い。 | 果物 | 1 |
人参 | にんじん | オレンジ色で独特の風味。 | 野菜 | 1 |
菊芋 | きくいも | 「豚いも」と呼ばれたことも。 | 野菜 | 0 |
西瓜 | すいか | 熊本が生産量1位。 | 野菜,果物 | 1 |
用語集には通常版と簡易版があり、
簡易版フラグが1になっているもののみ、簡易版でも表示します。
想定しているフィルタ
フィルタはすべてandの関係です。
templateではフィルタがかけられた後のデータ(getter/filteredData)を展開します。
検索条件の操作パネルで管理するフィルタ
state/filterの中に条件を保存しています。
「検索条件をクリア」(action/filterReset)により値が初期化されます。
- 「見出語」を対象としたテキスト入力検索(state/filter.text)
- 各行あ段のボタンを配置し、「見出語読み」を参照して絞り込み(state/filter.char)
- 用語のグルーピングをタグとする。(state/filter.tag)
すべてのタグをボタンとして配置し、「タグ」を参照して絞り込み
※1つの用語に対して複数個のタグがつくことを想定
操作パネル以外のフィルタ
通常版/簡易版をURLで切り分けます。(state/systemMode)
例)/glossary?mode=simple
「検索条件をクリア」(action/filterReset)により値が初期化されません。
課題
- getter/filteredDataの汚さをどうにかしたいのですが、方法がわかりません。
1つ1つのフィルタ処理を関数(action?getter?)として切り出す?
- 五十音のフィルタを実装するにあたって、参考にできるサイト・記事が見つけられませんでした。
何かもっと良い方法があれば教えていただきたいです。
- 「動きはするが、とにかく汚い。改善方法はわからない」という状況に陥ったとき、皆さんはどう解決しているのでしょうか。
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

バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/11/12 04:19
2019/11/12 06:33