防止 Select2 選擇後自動排序
Select2 可以將原本 HTML 的 Select 增加更多豐富的功能,只要透過 CSS 選擇器將目標的 Select 替換掉就可以:
<!-- HTML 部分 --><select id="sample-select" multiple="multiple"><option value="A">A</option><option value="B">B</option><option value="C">C</option></select>
// JavaScript 部分$('#sample-select').select();
Select2 雖然功能很完善,但是有些看似方便的功能實際上可能是所不需要的。
How to prevent Select2 auto sorting
HTML 5 原生的 Select 提供多個選項的功能,只要加上 multiple
的屬性,在 Select2 中也可以設定 multiple
(參考 Configuration API),讓你可以選擇多個選項,但是使用後發現,選擇出來的選項會按照字母或是數字來排序,這或許對有些人來說是很方便的,但是因為某些需求上,這個功能對我造成了另一種困擾。
延伸上面的範例,假設今天我先選了 C
,再選了 A
,這個時候 A
會自動被排序到最前面去,但或許你可能不希望這個順序被更動。
在 Select2 的文件中,也沒有相關的設定選項可以讓你去停止自動排序這件事情,這個時候只能靠 workaround 來解決了!🙀
$('#sample-select').on('select2:select', function (event) {const element = event.params.data.element;const $element = $(element);$element.detach();$(this).append($element).trigger('change');});
這段 JavaScript 非常的簡單,執行說明如下:
- 先綁定 select2 的事件
select2:select
- 接著當事件被觸發時,取得該目標元素
- 複製該元素
- 移除元素
- 把複製的元素在
append
到 select2 上,再 triggerchange
事件
這樣在選擇選項的時候,排序就不會一直亂跳了。
Server Side Rendering Problem
如果先從後端將資料 query 好後,再送到前端進行 render,以 Laravel 的 Blade 為範例:
<select id="sample-select" multiple="multiple">@foreach ($options as $option)<optionvalue="{{ $option->id }}"@if (something...)selected="selected"@endif>{{ $option->name }}</option>@endforeach</select>
在某些條件下,option
是符合條件的,這時候它會被選擇,但是你發現 Select2 還是會自動幫你做排序 😥,這時候該怎麼解決?
我們先將它拆為兩個部分:
- 確定會被選擇的選項
- 尚未被選擇的選項
尚未被選擇的選項沒有順序的要求,而確定會被選擇的選項則需要,這時候要就考慮各自 render 的時間點,必須將 query 出來的資料按照上面的部分分成兩組。
$selectedOptions = [/* ... */];$unSelectedOptions = [/* ... */];return view('something',['selectedOptions' => $selectedOptions,'unSelectedOptions' => $unSelectedOptions,]);
尚未被選擇的選項可以直接在 Server Side 的時候直接 render 出來,而確定會被選擇的選項則是透過前端 JavaScript 載入完成後,再更新 Select 的選項。
尚未被選擇的選項:
<select id="sample-select" multiple="multiple">@foreach ($unSelectedOptions as $option)<optionvalue="{{ $option->id }}">{{ $option->name }}</option>@endforeach</select>
確定會被選擇的選項:
// ?
在上面描述中,事先在後端處理好兩種選擇的狀況,並分成兩組陣列,所以這時候把確定會被選擇的選項放到 data-*
attribute 中:
<selectid="sample-select"data-selected-options="{{ json_encode($selectedOptions) }}"multiple="multiple"><!-- ... --></select>
這時候在前端可以藉由 data-selected-options
取得被選擇的選項:
const selectedOptions = $('#sample-select').data('selected-options') || [];
selectedOptions
內容大概如下:
[{ id: 1, text: 'Hello' },{ id: 2, text: 'World' }]
id
和text
是 Select2 所要求的格式
接著把它們都轉成為 HTMLOptionElement:
const options = selectedOptions.map(o => {const option = new Option(o.text, o.id, true, true);// 如果你想為 option 加上其他額外的 attribute,可以透過 `setAttribute` 完成// 例如:`option.setAttribute('data-say-hi', 'Hi')`// 結果會是:<option data-say-hi="Hi">...</option>return option;});
轉換成 HTMLOptionElement 後,得到一個新的 options
,接著將這些 options
append 到 select,並 trigger change
事件:
$('#select-sample').append(...options);$('#select-sample').trigger('change');
這時候網頁剛載入進來的時候,你可以發現 select 的區塊都是一開始什麼都沒有,直到 JavaScript 整個載入完成後,會將這些 option 給 append 上去。