Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

Yahoo! UI LibraryでJSONからツリービューを生成する

改修プロジェクト。
もともとツリービューがあったのだけど、レスポンスが遅いと。

もとはDestroydrop » Javascripts » Treeを参考に
プロトタイプを作ってビューを生成していた。
要素の数だけnewしていたため、とても遅かった。


サーバーサイドでJSON形式にして、そのままツリーに利用できないか考えていたけど、Yahoo! UI LibraryがまさにJSONからツリーを生成するクラスを提供していた。

http://developer.yahoo.com/yui/treeview/


これを使ってJSONからツリーを生成できた。
f:id:jyukutyo:20090225172021j:image

<html>
	<head>
		<link rel="StyleSheet" href="css/treeview.css" type="text/css" />
		<script type="text/javascript" src="js/yahoo-dom-event.js"></script>
		<script type="text/javascript" src="js/treeview-min.js"></script>
		<style type="text/css">
			.icon-root { display:block; height: 22px; padding-left: 20px; background: transparent url(img/check0.gif) 0 0px no-repeat; } 
			.icon-folder { display:block; height: 22px; padding-left: 20px; background: transparent url(img/check1.gif) 0 0px no-repeat; }	
			.icon-leaf { display:block; height: 22px; padding-left: 20px; background: transparent url(img/check2.gif) 0 0px no-repeat; }	
		</style> 
	</head>
	<body>
	
		<div id="treeDiv1">
		</div>
	
		<script type="text/javascript">
		//global variable to allow console inspection of tree: 
		var tree; 
		 
		//anonymous function wraps the remainder of the logic: 
		(function() { 
		 
		    //function to initialize the tree: 
		    function treeInit() { 
		        buildRandomTextNodeTree(); 
		    } 
		     
		    //Function  creates the tree and  
		    function buildRandomTextNodeTree() { 
		     
		        //instantiate the tree: 
		        tree = new YAHOO.widget.TreeView("treeDiv1", [  
					    {type:"text", label:"List 0", labelStyle:"ygtvlabel icon-root",
					    expanded:true, hasIcon:false,
					    children: [ 
					        {type:"text", label:"List 0-0", 
					        labelStyle:"ygtvlabel icon-folder", 
					        children: [ 
					            "item 0-0-0", 
					            "item 0-0-1" 
					        ]}, 
					        {type:"text", label:"item 0-1", 
					        labelStyle:"ygtvlabel icon-folder",
					        children: [ 
					            {type:"text", label:"elsewhere", 
					            labelStyle:"ygtvlabel icon-leaf",
					            children: [ 
					                "item 0-1-0", 
					                "item 0-1-1" 
					            ]} 
					        ]} 
					    ]} 
					]); 
 
			        //The tree is not created in the DOM until this method is called: 
			        tree.draw();
			        tree.subscribe('clickEvent',function(e) {
			        	alert(e.node.label);
			        });
			    } 
			    //Add an onDOMReady handler to build the tree when the document is ready
			    YAHOO.util.Event.onDOMReady(treeInit);
			})();
 		</script>
</html>

基本的にはTreeViewをnewするときの第2引数にJSONを渡す。
プロパティとしてlabelがツリーの項目名、typeがNodeの種類(textならTextNode。ほかにHtmlNodeもある)、childrenが子ノードとなる。


実際にJavaではこういうフィールドを持ったJavaBeansを作った。

	private String type = "text";
	
	private String label;

	private XxxDto data;
	
	private String labelStyle;
	
	private boolean expanded;
	
	private boolean hasIcon = true;
	
	private List children;

dataはイベントリスナーでNodeが渡されたときに利用するデータを入れておく。複数あるならDtoにして、いくつかのフィールドを定義しておけばいい。
expandedをtrueにすると最初からツリーが開いている。
hasIconは十字のアイコンに当たる部分の有無を表す。


JavaオブジェクトからJSONへの変換は、今回Maven - Json-lib::Welcomeを使った。
JSONIC - simple json encoder/decoder for javaの方が使いやすそうだったけど、JDKが1.5以降のみ対応だったため、Json-libを使った。


ちなみにJson-libでの変換は簡単だった。

		String[] excluedeProperties = {"id"};
		JsonConfig config = new JsonConfig();
		config.setExcludes(excluedeProperties);
		JSONArray json = JSONArray.fromObject(root, config);

出力したくないプロパティは、JsonConfigに配列で渡す。
あとはJSONArray.fromObject()にJSONにしたいオブジェクトとConfigを渡すだけ。


話が少しJavaにずれたけど、CSSはこんな感じ。

.ygtvitem {
	
}

.ygtvitem table {
	margin-bottom: 0;
	border: none;
}

.ygtvrow td {
	border: none;
	padding: 0;
}

.ygtvrow td a {
	text-decoration: none;
}

.ygtvtn {
	width: 18px;
	height: 22px;
	background:
		url(../img/treeview-sprite.gif)
		0 -5600px no-repeat;
}

.ygtvtm {
	width: 18px;
	height: 22px;
	cursor: pointer;
	background:
		url(../img/treeview-sprite.gif)
		0 -4000px no-repeat;
}

.ygtvtmh,.ygtvtmhh {
	width: 18px;
	height: 22px;
	cursor: pointer;
	background:
		url(../img/treeview-sprite.gif)
		0 -4800px no-repeat;
}

.ygtvtp {
	width: 18px;
	height: 22px;
	cursor: pointer;
	background:
		url(../img/treeview-sprite.gif)
		0 -6400px no-repeat;
}

.ygtvtph,.ygtvtphh {
	width: 18px;
	height: 22px;
	cursor: pointer;
	background:
		url(../img/treeview-sprite.gif)
		0 -7200px no-repeat;
}

.ygtvln {
	width: 18px;
	height: 22px;
	background:
		url(../img/treeview-sprite.gif)
		0 -1600px no-repeat;
}

.ygtvlm {
	width: 18px;
	height: 22px;
	cursor: pointer;
	background:
		url(../img/treeview-sprite.gif)
		0 0px no-repeat;
}

.ygtvlmh,.ygtvlmhh {
	width: 18px;
	height: 22px;
	cursor: pointer;
	background:
		url(../img/treeview-sprite.gif)
		0 -800px no-repeat;
}

.ygtvlp {
	width: 18px;
	height: 22px;
	cursor: pointer;
	background:
		url(../img/treeview-sprite.gif)
		0 -2400px no-repeat;
}

.ygtvlph,.ygtvlphh {
	width: 18px;
	height: 22px;
	cursor: pointer;
	background:
		url(../img/treeview-sprite.gif)
		0 -3200px no-repeat;
}

.ygtvloading {
	width: 18px;
	height: 22px;
	background:
		url(../img/treeview-loading.gif)
		0 0 no-repeat;
}

.ygtvdepthcell {
	width: 18px;
	height: 22px;
	background:
		url(../img/treeview-sprite.gif)
		0 -8000px no-repeat;
}

.ygtvblankdepthcell {
	width: 18px;
	height: 22px;
}

.ygtvchildren {
	
}

* html .ygtvchildren {
	height: 2%;
}

.ygtvlabel,.ygtvlabel:link,.ygtvlabel:visited,.ygtvlabel:hover {
	margin-left: 2px;
	text-decoration: none;
	background-color: white;
	cursor: pointer;
}

.ygtvcontent {
	cursor: default;
}

.ygtvspacer {
	height: 22px;
	width: 12px;
}

.ygtvfocus {
	background-color: #c0e0e0;
	border: none;
}

.ygtvfocus .ygtvlabel,.ygtvfocus .ygtvlabel:link,.ygtvfocus .ygtvlabel:visited,.ygtvfocus .ygtvlabel:hover
	{
	background-color: #c0e0e0;
}

.ygtvfocus a,.ygtvrow td a {
	outline-style: none;
}

.ygtvok {
	width: 18px;
	height: 22px;
	background:
		url(../img/treeview-sprite.gif)
		0 -8800px no-repeat;
}

.ygtvok:hover {
	background:
		url(../img/treeview-sprite.gif)
		0 -8844px no-repeat;
}

.ygtvcancel {
	width: 18px;
	height: 22px;
	background:
		url(../img/treeview-sprite.gif)
		0 -8822px no-repeat;
}

.ygtvcancel:hover {
	background:
		url(../img/treeview-sprite.gif)
		0 -8866px no-repeat;
}

.ygtv-label-editor {
	background-color: #f2f2f2;
	border: 1px solid silver;
	position: absolute;
	display: none;
	overflow: hidden;
	margin: auto;
	z-index: 9000;
}

.ygtv-edit-TextNode {
	width: 190px;
}

.ygtv-edit-TextNode .ygtvcancel,.ygtv-edit-TextNode .ygtvok {
	border: none;
}

.ygtv-edit-TextNode .ygtv-button-container {
	float: right;
}

.ygtv-edit-TextNode .ygtv-input input {
	width: 140px;
}

.ygtv-edit-DateNode .ygtvcancel {
	border: none;
}

.ygtv-edit-DateNode .ygtvok {
	display: none;
}

.ygtv-edit-DateNode .ygtv-button-container {
	text-align: right;
	margin: auto;
}

クラス名が非常にわかりづらいけど、意味はこうらしい。

Icon styles:

* ygtvtn: First or middle sibling, no children
* ygtvtm: First or middle sibling, collapsible
* ygtvtmh: First or middle sibling, collapsible, hover state
* ygtvtp: First or middle sibling, expandable
* ygtvtph: First or middle sibling, expandable, hover state
* ygtvln: Last sibling, no children
* ygtvlm: Last sibling, collapsible
* ygtvlmh: Last sibling, collapsible, hover state
* ygtvlp: Last sibling, expandable
* ygtvlph: Last sibling, expandable, hover state
* ygtloading: Loading dynamic data indicator

YUI 2: TreeView


結果としてはレスポンスもすごく早くなった。