To checked or not to checked-表單元件的不明確選取狀態「indeterminate」與偽類「:indeterminate」

DowYuu言

當一個群組中有很多checkbox時,有時會搭配一個有全選功能的checkbox讓使用者可以不用點到手痠,增加使用者體驗。

如果群組中的checkbox全部都被選取了,那全選就會自動勾起,沒問題。
如果群組中的checkbox全部都沒有被選取,那全選就會自動取消勾起,沒問題。
如果群組中的checkbox只有部分被選取,那全選就…嗯?To checked or not to checked?

瀏覽器支援 Browser compatibility

支援表在此,IE6+,可以舒服使用。

indeterminate狀態(不明確狀態)基本使用

先提一下checkboxindeterminate(不明確)狀態沒辦法用HTML屬性設置(HTML中沒有這個屬性),僅能靠JavaScript去設置。

HTML:

1
2
3
4
5
<input type="checkbox" id="ck0"><label for="ck0">沒勾選</label>
<input type="checkbox" id="ck1" checked><label for="ck1">勾選</label>
<input type="checkbox" id="ck2"><label for="ck2">我是不明確狀態,要用js設置</label>
<input type="checkbox" id="ck3" checked><label for="ck3">我是不明確狀態並且有勾選,看不出來齁</label>
<input type="checkbox" id="ck4" indeterminate><label for="ck4">錯誤示範,不明確狀態用HTML設置是無效的</label>

JavaScript:

1
2
3
4
5
6
7
// JavaScript
document.getElementById('ck2').indeterminate = true; // 設indeterminate值為true
let ck2Ind = document.getElementById('ck2').indeterminate; // 取indeterminate值,ck2Ind = true

// jQuery
$('#ck3').prop('indeterminate', true); // 設indeterminate值為true
let ck3Ind = $('#ck3').prop('indeterminate'); // 取indeterminate值,ck3Ind = true

只要點擊indeterminate狀態的checkbox後,會自動取消indeterminate狀態(取得indeterminate值會取得false),恢復到一般的checkbox勾選或不勾選的狀態,使用者怎麼點都無法回到indeterminate狀態。

點checkbox看看indeterminate值會不會變動:

要知道,indeterminate狀態和checked狀態是獨立的兩種狀態,你可以在indeterminate狀態下同時是勾選的,或是在indeterminate狀態下不是勾選的,長的還都一個樣,就是一個不明確狀態的樣式蓋在checkbox上的感覺。

這意味indeterminate狀態僅僅是視覺上的不同而已,目的在於優化使用者在前端的交互,且表單送出時也只會依據checked狀態送出,完全不管indeterminate狀態與否喔。

CSS偽類 :indeterminate

:indeterminate代表不確定的表單元件,不同表單元件會在不同情況下觸發該偽類,並不是所有表單元件用js設置indeterminate狀態為true就會套用到:indeterminate(只有checkbox是如此),使用時要注意IE的兼容性

元件 長相 情況
checkbox indeterminate狀態被JavaScript設置為true
radio 在相同name中的所有radio都沒有選取
progress 處於不確定狀態(未設置value)

表單元件本身沒辦法使用太多CSS屬性,但可以還是運用CSS選擇器做出不同變化,radio的應用可見下方

HTML:

1
2
3
<input type="checkbox" id="pseudo-ck"><label for="pseudo-ck"></label>
<progress class="pseudo-pg" id="pseudo-pg-0"></progress><label for="pseudo-pg-0"></label>
<progress class="pseudo-pg" id="pseudo-pg-1" value="40" max="100"></progress><label for="pseudo-pg-1"></label>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pseudo-ck + label,
.pseudo-pg + label{
  margin-left: 20px;
}
#pseudo-ck:checked + label:before{
  content: '您已同意所有上述同意書項目';
  color: #666;
}
#pseudo-ck:not(:checked) + label:before{
  content: '請勾選同意書項目';
  color: red;
}
#pseudo-ck:not(:checked):indeterminate + label:before{
  content: '必須要「勾選所有」上述同意書項目,才可進行到下一步!';
  color: red;
}
.pseudo-pg:indeterminate + label::before{
  content: '讀取執行進度中,請稍後...';
  color: red;
}
.pseudo-pg + label::before{
  content: '執行中';
  color: #666;
}

radio元件應用

radio設置indeterminate狀態沒什麼用

radio元件在用JavaScript設置了indeterminate狀態後,在外觀上並沒有區別,但與checkbox不同,有選取後indeterminate值不會自動變為false,有設就一直為true,沒設就一直為falseindeterminate狀態對radio來說似乎沒什麼使用必要,radio比較有用的運用是配合偽類 :indeterminate(詳見下方)。

點radio看看indeterminate值會不會變動:

radio推薦應用-偽類:indeterminate

上面有提到的CSS偽類:indeterminate會在相同name中的所有radio都沒有選取的狀況下被套用,自己覺得radio比較能實際應用在這部分,但請注意IE不支援

HTML:

1
2
3
4
5
6
<div>早餐想吃什麼呢?</div>
<ul class="breakfasts">
  <li><input type="radio" id="bf0" name="breakfast"><label for="bf0">鍋燒冬粉</label></li>
  <li><input type="radio" id="bf1" name="breakfast"><label for="bf1">蛋餅</label></li>
  <li><input type="radio" id="bf2" name="breakfast"><label for="bf2">鐵板麵</label></li>
</ul>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  .breakfasts{
    display: flex;
  }
  .breakfasts li + li{
    margin-left: 10px;
  }
  .breakfasts input:indeterminate + label{
    color: red;
  }
  .breakfasts li:last-child input:indeterminate + label::after{
    content: '* Oops!你還沒有選擇早餐要吃什麼喔!';
    margin-left: 15px;
    font-weight: bold;
  }
早餐想吃什麼呢?

checkbox全選功能應用

前面有說到indeterminate狀態僅僅是視覺上的不同而已,目的在於優化使用者在前端的交互,也有提到像是全選功能中,若你只有勾選其中幾個選項,那顆全選checkbox在一般情況下無論勾選與不勾選意義上都不是那麼到位,這裡就可以用indeterminate狀態來解決這個情況。

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
<ul>
  <li><input type="checkbox" id="cks_all"><label for="cks_all">全選</label></li>
  <li>
    <ul class="options">
      <li><input type="checkbox" id="cks0" name="cks"><label for="cks0">馬來貘</label></li>
      <li><input type="checkbox" id="cks1" name="cks"><label for="cks1">山貘</label></li>
      <li><input type="checkbox" id="cks2" name="cks"><label for="cks2">中美貘</label></li>
      <li><input type="checkbox" id="cks3" name="cks"><label for="cks3">南美貘</label></li>
      <li><input type="checkbox" id="cks4" name="cks"><label for="cks4">卡波馬尼貘</label></li>
    </ul>
  </li>
</ul>

jQuery:

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
$(function(){

  let $options = $('.options'); // 選項列表

  // 變動選項列表中的checkbox
  $options.on('change', 'input[type="checkbox"]', function(){
    let cksName = this.name, // 取得選項列表中的checkbox的name
      $allCks = $('#'+cksName+'_all'); // 全選checkbox
    if($options.find('input[name="'+cksName+'"]').length === $options.find('input[name="'+cksName+'"]:checked').length){
      // checkbox數量 = 勾選的checkbox數量,即勾選全選,非不明確狀態
      $allCks.prop('indeterminate', false).prop('checked', true);
    }else if($options.find('input[name="'+cksName+'"]:checked').length === 0){
      // 勾選的checkbox數量 = 0,即不勾選全選,非不明確狀態
      $allCks.prop('indeterminate', false).prop('checked', false);
    }else{
      // checkbox數量 != 勾選的checkbox數量,且勾選的checkbox數量 != 0
      // 為不明確狀態,且不勾選全選
      $allCks.prop('indeterminate', true).prop('checked', false);
    }
  })

  // 點擊全選checkbox
  $('#cks_all').on('click', function(){
    let $cks = $options.find('input[name="'+this.id.split('_')[0]+'"]'); // 選項列表中的checkbox
     // 將選項列表中的checkbox設為全選checkbox本身的勾選狀態
    $cks.prop('checked', $(this).prop('checked'));
  })

});

完畢灑花!