0%

做出一個連動選擇表單

這個其實蠻常看到的,最常出現是在透過縣市選擇區域, 例如說先點選新北市, 再從新北市各分區裡面挑一個, 例如說新店區爾爾. 通常拿到的可能是一般陣列裡面包著物件的格式, 因此底下就在敘述如何改變資料的結構.

資料結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
menu: [
{
type: '私房小物',
title: 'Zippo打火機',
},
{
type: '私房小物',
title: '台灣菸酒公司紀念狀',
},
{
type: '最美的地方',
title: '山頂上',

},
{
type: '最美的地方',
title: '西湖河畔',
},
{
type: '鼎泰豐',
title: '小籠包',
},
{
type: '私房小物',
title: '3231股票一張',
},
{
type: '鼎泰豐',
title: '粒粒分明炒飯',
},
],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"sort": [
"私房小物",
"最美的地方",
"鼎泰豐"
],
"map": {
"私房小物": {
"sort": [
"Zippo打火機",
"台灣菸酒公司紀念狀",
"3231股票一張"
],
"map": {
"Zippo打火機": {
"index": 0
},
"台灣菸酒公司紀念狀": {
"index": 1
},
"3231股票一張": {
"index": 5
}
}
},

要改成上述的結構,可以看到會由obj裡面包含兩種資料型態. 一種是陣列, 一種是物件

  • 陣列的部分, 是比較簡單的, 因為是要做排序, 所以以sort為名稱, 裡面放入type.
  • 物件的部分, 是層層疊疊的形式, 所以以map為名稱, 而map裡面藏有以title命名的物件.

可以看到資料是有層疊關係的, 因為有私房小物, 所以展開私房小物有三項, 而每一項又可以展開index.

暖身練習

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var auntie = { 
name: '陳小美',
ages: 22,
bwh: {
strength: 34,
agility: 25,
intelligence: 96
},
single: true
};

// 可以透過`[]`取得物件資料
auntie.bwh["strength"] // 34
auntie.bwh["xxx"] // undefined
auntie.bwh["xxx"] = 123
console.log(auntie)
1
2
3
4
5
6
7
{ 
bwh:
strength: 34
agility: 25
intelligence: 96
xxx: 123
}

可以看到把xxx加入變成key值, 並且加入123這個value

實戰部分

記得已經知道目前想要的資料結構, 那就來寫寫看吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typeList(){
// 一開始已經知道資料結構了, 所以寫成這樣.
let obj = {
sort: [],
map: {}
}
// 透過迴圈, 所以可以一一檢驗.
this.menu.forEach((item, index) => {
let {type, title} = {item}

// 如果在物件的物件中沒有這個key, 那我就在物件的陣列中把這個type加入
// 第一層先push type進去
if(!obj.map[type]){
obj.sort.push(type)

// 如果有, 那我還要在初始化一次
obj.map[type] = {
sort: [],
map: {}
}

// 初始化好了以後要塞入什麼值?

// 在sort裡面要放入title
// 第二層再push title進去
obj.map[type].sort.push(title)

// 在最後放入與之對應的index
obj.map[type].map[title] = {index}
}
})
return obj
}

這要真得很了解物件的結構才會做得出來.

外部資源參考

這邊來推薦下這個網站, 可以把資料轉成比較好理解的格式.
基本上很簡單, 只要在Vue devtool裡面點選 => 點點點, 就可以貼在這個網站的text在裡面
正確的作法是改成底下的資料結構形式, 這樣就很直覺了

select 表單

程式結束以後, 來看下畫面的部分.

  • 第一個下拉式選單, 在select標籤綁定全域的資料input.type並且與option :value中的item對應
  • 第二個下拉式選單, 在select標籤綁定全域的資料input.title並且透過第一個下拉式選單type, 得知與之對應的title.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<select
name=""
id=""
v-model="input.type"
>
<option :value="null">請選擇</option>
<option
v-for="item in typeList.sort"
:value="item"
>{{item}}</option>
</select>

<select
name=""
id=""
v-model="input.title"
>
<option :value="null">請選擇</option>
<option
:value="item"
v-for="item in titleList.sort"
>{{item}}</option>
</select>

titleList實作

1
2
3
4
5
6
7
8
9
10
11
titleList(){
// 因為在重新選擇type以後, 可能會沒有相對應的title, 因此畫面會變白, 這樣做就讓空白畫面變成請選擇
this.input.title = null

// 因為有選擇type, 所以要給出相對應的title, 這樣就可以和上述一樣的透過sort找到底下的陣列
if(this.input.type){
return this.typeList.map[this.input.type]
}else{
return []
}
}

畫面渲染

通常兩個連動的select都選擇完後, 就會有相對應的畫面.
這邊是以顯示index為例.

這個時候還要再用一個computed把資料彙整出來

1
2
3
4
5
6
7
8
content(){
if(this.input.title){
return this.titleList.map[this.input.title]
}else{
// 這樣就呼應了前面的原始資料
return null
}
}

v-if 細節注意

因為content是要等到, titleList也被選起的時候才會渲染出index.
所以這邊要下一個v-if, 當有content的時候, 才去content return回來的陣列找出index

1
2
3
<div class="number" style="font-size:32px; margin-left:10px;" 
v-if="content">{{content.index + 1}}
</div>

總結

現在這樣乍看突然覺得層級好分明.
因為要做到渲染, 所以sort是專門給v-for所使用. 而map裡面的type是要與sort的type相對應的.

後記 - 不改變資料結構的作法

現在想到另外一種方法, 但是這個只有在vue裡面才有作用, 這個filter就是e.target.value, 要和item中county值相同
但是問題是, 現在這個item.properties.county這不是單一值….

1
2
3
4
5
6
7
8
vm.dataAry.forEach(item => {
if (vm.filter === item.properties.county){
vm.townFilter.push(item.properties.town)
}else{
console.log(123)
// => 一直印出123...
}
})

所以我想到的方法是透過陣列的find方法

會回傳第一個滿足所提供之測試函式的元素值`, 這樣回傳的就是單一值, 而不是回傳一堆一樣的東西

圖示如下

1
2
forEach => '新北市' === '新北市*100'
find => '新北市' === '新北市'

節錄最後連動資料, 所運行的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vm.town.length = 0

vm.dataAry.find(item => {
if (vm.select.filter === item.properties.county){
vm.town.push(item.properties.town)
}
})
vm.townFilter = vm.town.filter((item, index, arr) => {
return vm.town.indexOf(item) === index;
});

return vm.dataAry.filter(item => {
return item.properties.county == vm.select.filter && item.properties.town == vm.select.filter2
});