jQuery全盛の今こそYUI

猫も杓子もjQuery。いささかうんざりしてる。
まあYUI3になってYUI2のコンポーネントを捨てる気なのかまだ作る気があるのかギャラリーだけで対応しろってことなのかよくわからんのとCDN半強制はちょっといただけないが、現在のところYUI2で欲しいコンポーネントはそろってる。
jQueryプラグインだと作る人によってインターフェースがまちまちで一貫性がなくなるとかバージョン追うのが大変とか色々あるわけで。だから規模が大きくなってくる、あるいは作る人が増える場合はYUIの方が合わせやすい。
特にイベント処理ではjQueryがいちいちクロージャー作らないとコンテキストがDOM要素そのものになるのがYUIだと自分でコンテキストオブジェクトを指定できる所なんか、自前のjavascriptクラス作る場合には強力。というか俺自身いつも「thisは誰?」って考てしまうからそれが嫌なの。
あと継承とカスタムイベント。jQueryにも継承はあるけど、カスタムイベントを使う場合イベント発火オブジェクトがjQueryにならなきゃいけないなんてちょっと大げさすぎる気がする。event=new CustomEvent();event.fire()の方が好き。
そんな状況下で「階層選択式ドロップダウン」を作った。必要に迫られて作ったのだがそこそこ使えそうなので公開する。

<div id="container"></div>
<input type="hidden" id="selectedValueJson" />
<script>
if(typeof(HieroSelect) == "undefined"){
    var yjson = YAHOO.lang.JSON;
    var yevent = YAHOO.util.Event;
    HieroSelect = function(id,item){
        this.item = item;
        this.id = id;
        this.container = document.getElementById(id);
        this.selectedValueJson = document.getElementById("selectedValueJson" );
        this.init();
    }
    HieroSelect.prototype={
        init : function(){
            this.selectedValues = yjson.parse( this.selectedValueJson.value );
            
            var position = 0;
            this.clearContainer();
            this.createSelect(this.item,position);
                        
        }
        ,clearContainer : function(){
            var children = this.container.childNodes;
            var len = children.length;
            for(var i = len -1;i>=0 ;i--){
                this.container.removeChild(children[i]);
            }
        }
        ,createSelect : function(item,position){
            if(item.subItems.length == 0) return;
        
            var selectedValue = (position < this.selectedValues.length) ? this.selectedValues[position] : "";
            
            var select = document.createElement("select");
            var itemLength = item.subItems.length;
            var option = this.createOption("","(選択してください)");
            select.appendChild(option);
            
            var selectedItem = null;
            for(var i = 0;i <itemLength;i++){
                var subItem = item.subItems[i];
                var option = this.createOption(subItem.text,subItem.text);
                if(subItem.text == selectedValue){
                    option.setAttribute("selected","selected");
                    selectedItem = subItem;
                }
                select.appendChild(option);
            }
            
            yevent.on(select,"change",function(event){ this.onchange(event,item,position); },this,true);
            this.container.appendChild( select );
            
            if(selectedItem != null){
                this.createSelect(selectedItem,position + 1);
            }
            
        } 
        ,createOption : function(value,text){
            var option = document.createElement("option");
            option.setAttribute("value",value);
            var text = document.createTextNode(text);
            option.appendChild(text);
            return option;
        }
        ,onchange : function(event,item,position){
            var select = yevent.getTarget(event);
            var value = select.options[select.selectedIndex].text;
            if(position < this.selectedValues.length){
                this.selectedValues.splice(position);
            }
            this.selectedValues[position] = value;
            if(value!= ""){
                var children = this.container.childNodes;
                var len = children.length;
                for(var i = len -1;i>position;i--){
                    this.container.removeChild(children[i]);
                }
                var selectedItem = this.find(item,value);
                if(selectedItem != null){
                    this.createSelect(selectedItem,position + 1);
                }
            }
            else{
            }
            this.selectedValueJson.value = yjson.stringify(this.selectedValues);
            
        }
        ,find : function(item,value){
            var itemLength = item.subItems.length;
            
            var selectedItem = null;
            for(var i = 0;i <itemLength;i++){
                var subItem = item.subItems[i];
                if(subItem.text == value){
                    selectedItem = subItem;
                }
            }
            return selectedItem;
        }
    }
}
var data = {
  text:"",
  subItems : [
      { text:"選択1",
        subItems: [
           { text : "選択1−1",
            subItems : []
           },
           { text : "選択1−2",
            subItems : []
           },
           { text : "選択1−3",
            subItems : []
           }
        ]
      },
      { text:"選択2",
        subItems: [
           { text : "選択2−1",
            subItems : []
           },
           { text : "選択2−2",
            subItems : []
           },
           { text : "選択2−3",
            subItems : []
           }
        ]
      }
  ]
}
var hiero = new HieroSelect("container",data);
</script>

こんな感じで。dataには { text:"hoge",subItems[] }の形を持つツリーデータを渡す。ツリーのルートが無いと(dataが配列だと)上手くいかない。
最終的には選択したテキストがhiddenフィールドにJSON配列として格納されるので、あとはサーバーサイドで適宜JSON展開して使えばいい。
YAHOO.util.Event,YAHOO.lang.JSONを使っているのでutilities.js(かdom.jsとevent.js)とjson.jsを参照するのを忘れずに。