Dynamic Select box from JSON
このページではツリー構造のリストをselectボックスで表示させて、さらにツリーの親子関係にあるselectボックスを連動
させる方法を説明する。
今回の例では、表示するデータをJSON(JavaScript Object Notation)で保存してある。
ツリーの構造はDojo Campus1)から確認できる。
あるいは、次節で示すJSONデータをJSON Editor2)にコピーして確認することも可能である。
JSON データ
今回のexampleで使用したJSONデータを次に示す。テストの為、Dojo Campusに載っているデータに加えて“Cairo”を追加した。
{ identifier: 'name', label: 'name', items: [ { name:'Africa', type:'continent', index: '1', children:[{_reference:'Egypt'}, {_reference:'Kenya'}, {_reference:'Sudan'}] }, { name:'Egypt', type:'country', index: '1.1', children:[{_reference:'Cairo'}]}, { name:'Cairo', type:'city', index: '1.1.1'}, { name:'Kenya', type:'country', index: '1.2', children:[{_reference:'Nairobi'}, {_reference:'Mombasa'}]}, { name:'Nairobi', type:'city', index: '1.2.1'}, { name:'Mombasa', type:'city', index: '1.2.2'}, { name:'Sudan', type:'country', index: '1.3', children:[{_reference:'Khartoum'}]}, { name:'Khartoum', type:'city', index: '1.3.1'}, { name:'North America', type:'continent', index: '2', children:[{_reference:'Mexico'}, {_reference:'Canada'}, {_reference:'United States of America'}] }, { name:'Mexico', type:'country', index: '2.1', children:[{_reference:'Mexico City'}, {_reference:'Guadalajara'}]}, { name:'Mexico City', type:'city', index: '2.1.1'}, { name:'Guadalajara', type:'city', index: '2.1.2'}, { name:'Canada', type:'country', index: '2.2', children:[{_reference:'Ottawa'}, {_reference:'Toronto'}]}, { name:'Ottawa', type:'city', index: '2.2.1'}, { name:'Toronto', type:'city', index: '2.2.2'}, { name:'United States of America', type:'country', index: '2.3'}, { name:'Asia', type:'continent', index: '3', children:[{_reference:'China'}, {_reference:'India'}, {_reference:'Russia'}, {_reference:'Mongolia'}] }, { name:'China', type:'country' , index: '3.1'}, { name:'India', type:'country', index: '3.2'}, { name:'Russia', type:'country', index: '3.3'}, { name:'Mongolia', type:'country', index: '3.4' }, { name:'Australia', type:'continent', index: '4', children:{_reference:'Commonwealth of Australia'}}, { name:'Commonwealth of Australia', type:'country', index:'4.1'}, { name:'Europe', type:'continent', index: '5', children:[{_reference:'Germany'}, {_reference:'France'}, {_reference:'Spain'}, {_reference:'Italy'}] }, { name:'Germany', type:'country', index:'5.1' }, { name:'France', type:'country', index: '5.2' }, { name:'Spain', type:'country', index: '5.3' }, { name:'Italy', type:'country', index: '5.4' }, { name:'South America', type:'continent', index: '6', children:[{_reference:'Brazil'}, {_reference:'Argentina'}] }, { name:'Brazil', type:'country', index:'6.1' }, { name:'Argentina', type:'country', index:'6.2' } ]}
ここで、注目すべきところはツリーの階層データを区別するtypeがあるということだ。
“continent”-“country”-“city”の順番の階層になっている。
ちなみに、「name」はデータの識別子(identifier)であり、データベースでいうとPrimary Keyに該当するもので考えてよい。
それはnameが重なってしまうとツリーで唯一のデータを検索できる手立てが無いからである。
画面layout
画面のレイアウトは極シンプルでツリーの代わりに,selectボックスが3個並んである。
画面のソースを次に示す。
<select id="continent"><option value=""></option></select><br/> <select id="country"><option value=""></option></select><br/> <select id="city"><option value=""></option></select>
ここで、空のoptionを追加したのはIEとの交換性の為である。optionがないと正常に動かないので、 注意する必要がある。
初期リストの表示
ここではページが最初に表示された際に初期リストを出力する仕組みを説明する。 大まかに、二段階が必要である。 まずはサーバー側からのJSONデータを読み込む準備段階である。
dojo.require("dojo.data.ItemFileReadStore"); //JSONデータの読込み var store = new dojo.data.ItemFileReadStore({url:'../country.json'});
ここで、利用するのがdojoのコアパッケージにあるItemFileReadStoreである。
次は読み込んだJSONから初期リストを出力するコードを示す。
function init(){ //continent pulldown list dojo.byId('continent').innerHTML=""; store.fetch( { query: { type: 'continent' }, onComplete: function( items ) { dojo.forEach( items, function(item, count) { var opt=document.createElement('option'); opt.appendChild(document.createTextNode(store.getValue( item, "name" ))); opt.setAttribute('value',store.getValue( item, 'index')); dojo.byId('continent').appendChild(opt); }); }, onError: function(e) { console.error( "!!!!",arguments ); }, sort: sortAttributes }); showFirstChildren('continent','country'); showFirstChildren('country','city'); }//init
ツリーの一番上の階層に当たるcontientを表示し、次はcontinentの先頭データからcountry階層の子供を出力する。
次はcountryの先頭データからcity階層の子供を出力するといった具合だ。
showFirstChildrenのメッソドを次に示す。
function showFirstChildren(parent, child){ dojo.byId(child).innerHTML=""; store.fetch( { query: { type: parent }, onComplete: function( items ) { dojo.forEach( items, function( item, count ) { var children = store.getValues( item, "children"); if(count==0){ dojo.forEach( children, function(node) { opt=document.createElement('option'); opt.appendChild(document.createTextNode(store.getValue( node, "name" ))); opt.setAttribute('value',store.getValue( node, 'index')); if(node.type==child) dojo.byId(child).appendChild(opt); }); }//if }); }, onError: function(e) { console.error( "!!!!",arguments ); }, sort: sortAttributes }); }//showFirstChildren
基本的には先頭データの子供を出す為、ループのindex番号であるcountが0の場合のみ、子供の階層に当たるデータのループ処理に 入るのを注目してほしい。
子供リストの出力
次は親階層のselectボックスの値が変わるたび、子供リストを動的に出力する部分を説明する。
ここでは、基本的に自分の子供のみ考慮すればよい。
function onChange(item, parent, child){ var val = item.options[item.selectedIndex].text; store.fetch( { query: { type: parent, name:val }, onComplete: function( items ) { dojo.forEach( items, function( item ) { dojo.forEach(child, function(e){ if(e) dojo.byId(e).innerHTML=""; }); var children = store.getValues( item, "children"); dojo.forEach(children, function(node){ var opt=document.createElement('option'); opt.appendChild(document.createTextNode(store.getValue( node, "name" ))); opt.setAttribute('value',store.getValue( node, 'index')); dojo.forEach(child, function(e){ if(e) dojo.byId(e).appendChild(opt); }); }); }); }, onError: function(e) { console.error( "!!!!",arguments ); }, sort: sortAttributes }); }
ここで、注目するところは選択された値の識別子とJSONの親階層(parent)の識別子を比較して、該当する親の子供階層のみ表示することである。
イベントの登録
最後にイベントの登録について説明する。ここでは、親階層のデータが変わるたびに(onchange)に子供が変わるので、一見自分の子供だけ考えれば
いいと思うかちだが、実はそうではない。
なぜなら、”continent”階層が変わると“country”が変わるし、それに連動して“city”階層も変わるからである。
要するに、自分の階層の下にある全階層にイベントを登録する必要があると言うことだ。
イベント登録コードの一部を次に示す。
dojo.addOnLoad( function() { init(); dojo.connect(dojo.byId('continent'),'onchange',function(e){ onChange(e.target, 'continent','country'); onChange(dojo.byId('country'), 'country','city'); }); dojo.connect(dojo.byId('country'),'onchange',function(e){ onChange(e.target, 'country','city'); }); });
Test Page
<html> <head> <SCRIPT TYPE=“text/javascript” SRC=“test/dojo/dojo.js”></SCRIPT> <script type=“text/javascript”> <!– dojo.require(“dojo.data.ItemFileReadStore”);
JSONデータの読込み var store = new dojo.data.ItemFileReadStore({url:'test/country.json'}); function init(){ continent pulldown list
dojo.byId('continent').innerHTML=""; store.fetch( { query: { type: 'continent' }, onComplete: function( items ) { dojo.forEach( items, function(item, count) { var opt=document.createElement('option'); opt.appendChild(document.createTextNode(store.getValue( item, "name" ))); opt.setAttribute('value',store.getValue( item, 'index')); dojo.byId('continent').appendChild(opt); }); }, onError: function(e) { console.error( "!!!!",arguments );
}
,sort: sortAttributes }); showFirstChildren('continent','country'); showFirstChildren('country','city');
}init function showFirstChildren(parent, child){ dojo.byId(child).innerHTML=“”; store.fetch( { query: { type: parent }, onComplete: function( items ) { dojo.forEach( items, function( item, count ) { var children = store.getValues( item, “children”); if(count==0){ dojo.forEach( children, function(node) { opt=document.createElement('option'); opt.appendChild(document.createTextNode(store.getValue( node, “name” ))); opt.setAttribute('value',store.getValue( node, 'index')); if(node.type==child) dojo.byId(child).appendChild(opt); }); }if
}); }, onError: function(e) { console.error( "!!!!",arguments ); } ,sort: sortAttributes });
}showFirstChildren function onChange(item, parent, child){ var val = item.options[item.selectedIndex].text; store.fetch( { query: { type: parent, name:val }, onComplete: function( items ) { dojo.forEach( items, function( item ) { dojo.forEach(child, function(e){ if(e) dojo.byId(e).innerHTML=“”; }); var children = store.getValues( item, “children”); dojo.forEach(children, function(node){ var opt=document.createElement('option'); opt.appendChild(document.createTextNode(store.getValue( node, “name” ))); opt.setAttribute('value',store.getValue( node, 'index')); dojo.forEach(child, function(e){ if(e) dojo.byId(e).appendChild(opt); }); }); }); }, onError: function(e) { console.error( “!!!!”,arguments ); } ,sort: sortAttributes }); } dojo.addOnLoad( function() { init(); dojo.connect(dojo.byId('continent'),'onchange',function(e){ onChange(e.target, 'continent',['country']); onChange(dojo.byId('country'), 'country',['city']); }); dojo.connect(dojo.byId('country'),'onchange',function(e){ onChange(e.target, 'country',['city']); }); }); var sortAttributes = [{attribute: “name”, descending: true}]; –> </script> </head> <body>
<select id=“continent”><option value=“”></option></select><br/> <select id=“country”><option value=“”></option></select><br/> <select id=“city”><option value=“”></option></select> </body> </html>
コメント