利用 D3 產生 Radio Button List 的小技巧
嚴格來講, D3 並不是操作 DOM 元素的最好工具。使用純 JavaScript 或者其它 framework 都可以把這種事情處理得很好。但是如果一定要使用 D3 來做的話, 以下要講的這個小技巧或許可以幫上小忙。
先講在前面。基本上這篇要介紹的小技巧可以算是個 hack。因為使用基本的語法就「應該」可以把事情做好了。
假設我們要利用 D3 來產一個文字在小圓圈之後的 Radio Button, 我們可能會直覺地這樣寫:
d3.select('#radioSelectTypeHolder')
.append('input')
.attr('type', 'Radio')
.append('label')
.text('abc')
這樣就會產生如下的 HTML 碼:
<input type="Radio">
<label>abc</label>
</input>
以上是一個語法上完全沒問題的 XML 碼。但是事實上那個 label 可能是不會出現的; 有些瀏覽器只會 render 出來一個小圓圈 (即 radio button)。最主要的原因, 在於 HTML 中 input 這個標籤是不應該有 end tag 的 (也就是那個 </input>), 所以這一段就不是標準的 HTML 碼。結果這段 HTML 就不一定會正確顯示了。
但如果我們把順序對調一下呢?
d3.select('#radioSelectTypeHolder')
.append('label')
.text('abc')
.append('input')
.attr('type', 'Radio')
那麼它會產生一個完全 OK 的 HTML 碼, 也可以順利地顯示。
<label>
abc
<input type="Radio">
</label>
但問題來了。我們不是說要產生的是一個「說明文字在後」的 Radio Button 嗎? 畢竟我們最終要做出來的 Radio Button List 是如下圖所示的樣子:
問題就出在瀏覽器並不允許 label 標籤被包在 input 標籤的裡面; 這樣的話, 這個 label 仍舊不會正確地顯示, 有時候它就好像根本不存在一樣。但反過來卻是正確的。
要解決這個問題, 我們就必須採用另一個比較不直覺的方法。也就是先建立一個上層的 DOM 元素, 再分別附加上 input 元素, 再分別附加上 label 元素:
let generationTypeData = ["Number", "Area", "HumanName", "CompanyName", "Address"];
let parentDiv = d3.select('#radioSelectTypeHolder')
.selectAll('span')
.data(generationTypeData)
.enter()
.append('span');
let radios = parentDiv
.append('input')
.attr('name', function(d) { return radioGenerationTypeId; })
.attr('type', function(d) { return 'Radio'; })
.attr('value', function(d) { return d; })
.attr('id', function(d) { return 'radioGenerationType'+d; })
.property("checked", function(d, i) {return i===0;})
let labels = parentDiv
.append('label')
.attr('for', function(d) { return 'radioGenerationType'+d; })
.text(function(d) {return d;})
換句話說, 我們先建立好一個上層元素 parentDiv, 然後依次產生所有的 Radio Buttons, 再依次產生對應的 Labels。這樣就可以了。