2011年12月20日火曜日

[JavaScript]childNodes と children で子ノードを取得する

いまさらですが知らなかったのでまとめました・・

childNodesプロパティ


JavaScriptでDOMを利用した処理をする場合、あるノードの子ノードを取得し、その子ノードに対して何らかの処理を行うというのは、よくあるパターンです。
今までの記事でも、子ノードを取得する場合、下記のように、DOMのchildNodesプロパティを使って取得していました。

サンプルHTML
<ul id="sample_list">
    <li>this</li>
    <li>is</li>
    <li>a</li>
    <li>test</li>
</ul>

サンプルコード
var ul = document.getElementById("sample_list");
var ch = ul.childNodes;
for (var i = 0, len = ch.length; i < len; i++) {
    console.log(ch[i].nodeName);
}

実行結果(以下すべて Chrome 16 で実行)
#text //改行がTextNodeとして取得されている
LI
#text
LI
#text
LI
#text
LI

上記のサンプルのように、childNodesプロパティで子ノードを取得した場合は、全ての子ノードのcollectionが返されます。 全ての子ノードということは、当然TextNode も含まれるので、改行やスペースがノードとして取得されています。 

通常、子ノードに対して処理をする場合、TextNodeは無視したい場合がほとんどなので、子要素のcollectionをフィルタリングする必要があります。 

サンプルコード
var ul = document.getElementById("sample_list");
var ch = ul.childNodes;
for (var i = 0, len = ch.length; i < len; i++) {
    if (ch[i].nodeName === 'LI'){
       console.log(ch[i].nodeName);
    }
}

実行結果
LI
LI
LI
LI

childrenで代用


childNodes の代わりに、childrenを使うと、TextNode は含まない、要素ノードだけのcollectionが返されます。
childrenは、IE6をはじめほぼ全てのブラウザで実装されているため、クロスブラウザの対応は特に必要ありません。

 サンプルコード
var ul = document.getElementById("sample_list");
var ch = ul.children;
for (var i = 0, len = ch.length; i < len; i++) {
    console.log(ch[i].nodeName);
}

実行結果
LI
LI
LI
LI

他にも要素ノードだけを対象にしたDOM非標準プロパティとしては、firstElementChild, lastElementChild, nextElementSibling などがありますが、children以外はブラウザによっては実装されていない場合があるので、使用には注意が必要です。

まとめ


子ノードの取得には、children がつかえる!

参考文献
ハイパフォーマンスJavaScript p50
ハイパフォーマンスJavaScript
ハイパフォーマンスJavaScript

2011年12月15日木曜日

[JavaScript]option要素にdisabledを設定する

form内の複数のselect要素に対して、一方を選択すると、他方のselectのoptionが自動的に絞り込まれるという処理を、以下のサンプルHTMLに対して、JavaScriptを使って実現してみようと思います。

サンプルコード
<form>
    <select id="upper">
       <option value="1">first</option>
       <option value="2">second</option>
    </select>
   
    <select id="downer">
       <option value="1_1">first_first</option>
       <option value="1_2">first_second</option>
       <option value="2_1">second_first</option>
       <option value="2_2">second_second</option>
    </select>
</form>

仕様は、
  • id="upper" のselect要素のvalueと、id="downer"のselet要素の子のoption要素のvalueの1文字目を比較し、値が一致するdownerのoptionだけを、ユーザーが選択可能とする。
とします。

最初に作ったプログラム


最初に、条件に一致しないdownerのoption要素に、disabled属性を設定してみました。

サンプルコード
window.onload = function(){
    var upper_select = document.getElementById("upper"),
        downer_select = document.getElementById("downer");

        autoSelectOptions(upper_select, downer_select);
        upper_select.onchange = function(){autoSelectOptions(upper_select, downer_select)};
}

function autoSelectOptions(upper_select, downer_select) {
    var selected_value = upper_select.value,
        downer_ch = downer_select.childNodes,       
        first_hit_flag = true;

    for (var i=0, len=downer_ch.length; i < len; i++) {

       if (downer_ch[i].tagName && downer_ch[i].tagName === 'OPTION' ){

           downer_ch[i].disabled = true;  //一旦すべてのoptionを選択不可にする
           downer_ch[i].selected = false; //選択状態をリセットする

           if (selected_value === downer_ch[i].value.split('_')[0]) {

               downer_ch[i].disabled = false; //選択可能にする

               if (first_hit_flag === true) {
                   //一番上の選択可能optionを選択状態表示させる
                   downer_ch[i].selected = true;
                   first_hit_flag = false;
               }
           }
       }
    }
}

落とし穴にはまる


ふふふ、このくらい簡単々と調子に乗ってたら、このコードではIE6, 7では正しく動作しませんでした。IE6, 7 では、option要素にはdisabled を設定できないということが原因です。。

というわけで、IE6, 7にも対応したのが以下のコードです。 ポイントは、window のonload イベントが呼ばれたときに、id=downerのselect要素の子のoption要素に対してcloneNode()メソッドを呼び出し、optionのクローンを作成していることです。

autoSelectOptions関数が呼ばれるたびに、一旦downerのoption要素を全て削除してから、複製化しておいたoptionから、条件に一致する要素のみ挿入しています。

サンプルコード
window.onload = function(){
    var doc = document,
             upper_select = document.getElementById("upper"),
             downer_select = document.getElementById("downer"),
             downer_options = downer_select.childNodes,
             cloned_options = new Array();
       
   //初期表示されるdownerのoptionのクローンを保存しておく
   for( var i=0, len=downer_options.length; i < len; i++){
       if (downer_options[i].tagName && downer_options[i].tagName.toLowerCase() === 'option') {
           cloned_options.push(downer_options[i].cloneNode(true));
       }
   }

   autoSelectOptions(upper_select, downer_select, cloned_options);
   upper_select.onchange = function(){autoSelectOptions(upper_select, downer_select, cloned_options)};
}

function autoSelectOptions(key_select_box, target_select_box, option_list){

   if (typeof key_select_box    === 'undefined' ||
       typeof target_select_box === 'undefined' ||
       typeof option_list       === 'undefined') {
           return;
   }
   
   var first_node_flag = true,
       ch = target_select_box.childNodes;
       
   for (var i=ch.length - 1; i >= 0; i-- ){
       //selectの子ノードを一旦すべて削除する。
       ch[i].parentNode.removeChild(ch[i]);
   }
   
   var fragment = document.createDocumentFragment();
   for (var i = 0, len = option_list.length; i < len; i++) {
       
       if (option_list[i].tagName && option_list[i].tagName.toLowerCase() == 'option'){
           option_list[i].selected = false;
               
           if (option_list[i].value.split('_')[0] === key_select_box.value){
               fragment.appendChild(option_list[i]);
               if (first_node_flag) {
                   option_list[i].selected = true;//最初に挿入するノードを,選択状態にする
                   first_node_flag = false;
               }
           }
       }
   }    
   //documentに条件に一致するのoptionのみ追加
   target_select_box.appendChild(fragment);    
}
IE6, 7 ともに動作することを確認しました! 一旦option要素をすべてremoveするなどちょっと強引ですが、仕様は満たされてるので良しとします。

まとめ


  • IE6, IE7 では、optionのdisabled属性は、設定できないので注意する!

参照URL
Forms in HTML documents http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.6
Node.cloneNode - MDN https://developer.mozilla.org/En/DOM/Node.cloneNode

2011年12月1日木曜日

[jQuery]要素の大きさを取得する

Javascript を使って要素の大きさを取得する場合、要素に設定されているpadding, border などのスタイルの幅を考慮する必要がよくあります。
下のスタイルを適用された、div要素について、JavaScript と jQueryで高さを取得しながら、jQueryの便利メソッドについて確認してみます。

css
div#target{
    height: 100px;
    width: 100px;
    padding: 10px;
    margin: 10px;
    border: 1px solid #FFF;
    background-color: #CCC";
}


jQueryを使わないでやってみる


上のスタイルが適用された要素の大きさを、純粋なjavascriptで取得するには、window.getComputedStyle() (IE8ではcurrentStyle) メソッドを呼び出して、CSSStyleDeclaration オブジェクトを取得し、オブジェクトのgetPropertyValue()メソッドを使ってプロパティにアクセスします。(参照)
下の例だと、style.getPropertyValue("height")は要素そのものの高さのみ取得できるので、paddingやborderの幅を考慮する場合は、別途処理が必要になります。

var elem = document.getElementById('target');
var style = window.getComputedStyle(elem, null); // IE8では不可
alert( style.getPropertyValue("height") );              // 100px
alert( style.getPropertyValue("padding-top") );         // 10px
alert( style.getPropertyValue("padding-bottom") );      // 10px
alert( style.getPropertyValue("border-top-width") );    // 1px
alert( style.getPropertyValue("border-bottom-width") ); // 1px


jQueryを使ってシンプルに


jQueryを使えば、状況にあわせて、要素の大きさを簡単に取得できます。
例えば高さを取得する場合は、hight(), innerHeight(), outerHeight() が使えます。
それぞれのメソッドが返す値を、下のコードで確認してみました。

$( '#target' ).height();      //100
$( '#target' ).innerHeight(); //120 上下のpaddingの幅が含まれる
$( '#target' ).outerHeight(); //122 paddingの幅 + 上下のborderの幅が含まれる

上の実行結果から、
  • .height() は要素そのものの高さを返す
  • .innerHeight() はpadding を含む高さを返す
  • .outerHeight() はpadding に加えて、border の幅も含む高さを返す
  • .margin の値は影響しない

ということがわかりました。
もちろん以上の動作はwidth(), innerWidth(), outerWidth() でも同じです。


注意点


$(window)に対して innerWidth(), innerHeight(), outerWidth(), outeHeight() を実行すると、NaNが返されます。
window の大きさを取得したい場合は、$(window).height(), $(window).width() でOK。


まとめ


要素の大きさをjQueryで取得するには、
  • .height() で要素そのものの高さを
  • .innerHeight() で padding の幅を含めた高さを
  • .outerHeight() で padding と border の幅を含めた高さを取得できる!
(.width() .innerWidth() .outerWidth() も同様)


参考文献
jQueryクックブック p140
jQueryクックブック
jQueryクックブック