ace是基于bootstrap的一个优秀样式实现.其中菜单树TreeView是一个相当好用的一个目录树结构。不过因为文档不全,坑还很多。整合的使用的时候还是花费了一番心血的。
简单的就不说了,大家把样例代码拿到本地,一个基本的菜单树就可以跑。下面提一下,抽取代码整理成自己应用时碰到的一些问题。
首先是显示问题:
TreeView的节点是动态绑定的,所以原先是很容易使用的。只要绑定好要显示树的html节点即可,例如:
<div id="tree1"></div>
就要准备相关的目录树的数据,数据结构如下:
var data = {
“菜单标识”:{
name:"要显示的菜单名",
type:"item/folder",//item表示端节点,folder表示还有其他儿子节点
。。。..这里可以放置其他节点内容,这里挺重要,下面说道如何控制其事件处理时会提到
additionalParameters:{//如果有子节点,则需要即type为folder时需要
children:{//固定和additionalParameters配套的
“子菜单标识”:{
name:"子菜单显示的名称",
type:"还是子菜单的类型item/folder",
....其他属性名
}
}
}
}
}
这样就整理出对应的目录树的结构了。
下面还要如此包装一下这个数据:
var DataSourceTree = function(options) {
this._data = options.data;
this._delay = options.delay;
}
DataSourceTree.prototype.data = function(options, callback) {
var self = this;
var $data = null;
if (! ("name" in options) && !("type" in options)) {
$data = this._data;
//the root tree
callback({
data: $data
});
return;
} else if ("type" in options && options.type == "folder") {
if ("additionalParameters" in options && "children" in options.additionalParameters) $data = options.additionalParameters.children;
else $data = {}
}
if ($data != null)
setTimeout(function() {
callback({
data: $data
});
},
parseInt(Math.random() * 500) + 200);
}
var showData = new DataSourceTree({
data: resultData
});
好,要显示的数据,就封装在showData 中了,然后进行初始化,如下:
$('#tree1').ace_tree({
dataSource: showData ,
multiSelect: false,
loadingHTML: '<div class="tree-loading"><i class="icon-refresh icon-spin blue"></i></div>',
'open-icon': 'icon-minus',
'close-icon': 'icon-plus',
'selectable': true,
'selected-icon': 'icon-ok',
'unselected-icon': 'icon-remove'
});
这样这棵树,就可以显示了。但是上面的数据,通过样例,还是可以很容易获取到其用法的,简单的单步跟踪一下即可。
下面最坑的就是如何获取这棵树上的点击事件。这个表面上没有例子,很难找到对应的方法和内容的,经过我漫长努力,还是找到了。核心关键就是fuelux.tree.min.js文件。
这个是底层声明treeview的基本框架代码:
(function(a, c) {
var b = function(e, d) {
this.$element = a(e);
this.options = a.extend({},
a.fn.tree.defaults, d);
this.$element.on("click", ".tree-item", a.proxy(function(f) {
this.selectItem(f.currentTarget)
},
this));
this.$element.on("click", ".tree-folder-header", a.proxy(function(f) {
this.selectFolder(f.currentTarget)
},
this));
this.render()
};
b.prototype = {
constructor: b,
render: function() {
this.populate(this.$element)
},
populate: function(f) {
var e = this;
var d = f.parent().find(".tree-loader:eq(0)");
d.show();
this.options.dataSource.data(f.data(),
function(g) {
d.hide();
a.each(g.data,
function(h, j) {
var i;
if (j.type === "folder") {
i = e.$element.find(".tree-folder:eq(0)").clone().show();
i.find(".tree-folder-name").html(j.name);
i.find(".tree-loader").html(e.options.loadingHTML);
var k = i.find(".tree-folder-header");
k.data(j);
if ("icon-class" in j) {
k.find('[class*="icon-"]').addClass(j["icon-class"])
}
} else {
if (j.type === "item") {
i = e.$element.find(".tree-item:eq(0)").clone().show();
i.find(".tree-item-name").html(j.name);
i.data(j);
if ("additionalParameters" in j && "item-selected" in j.additionalParameters && j.additionalParameters["item-selected"] == true) {
i.addClass("tree-selected");
i.find("i").removeClass(e.options["unselected-icon"]).addClass(e.options["selected-icon"])
}
}
}
if (f.hasClass("tree-folder-header")) {
f.parent().find(".tree-folder-content:eq(0)").append(i)
} else {
f.append(i)
}
});
e.$element.trigger("loaded")
})
},
selectItem: function(e) {
if (this.options.selectable == false) {
return
}
var d = a(e);
var g = this.$element.find(".tree-selected");
var f = [];
if (this.options.multiSelect) {
a.each(g,
function(i, j) {
var h = a(j);
if (h[0] !== d[0]) {
f.push(a(j).data())
}
})
} else {
if (g[0] !== d[0]) {
g.removeClass("tree-selected").find("i").removeClass(this.options["selected-icon"]).addClass(this.options["unselected-icon"]);
f.push(d.data())
}
}
if (d.hasClass("tree-selected")) {
d.removeClass("tree-selected");
d.find("i").removeClass(this.options["selected-icon"]).addClass(this.options["unselected-icon"])
} else {
d.addClass("tree-selected");
d.find("i").removeClass(this.options["unselected-icon"]).addClass(this.options["selected-icon"]);
if (this.options.multiSelect) {
f.push(d.data())
}
}
if (f.length) {
this.$element.trigger("selected", {
info: f
})
}
},
selectFolder: function(e) {
var d = a(e);
var f = d.parent();
if (d.find("." + this.options["close-icon"]).length) {
if (f.find(".tree-folder-content").children().length) {
f.find(".tree-folder-content:eq(0)").show()
} else {
this.populate(d)
}
f.find("." + this.options["close-icon"] + ":eq(0)").removeClass(this.options["close-icon"]).addClass(this.options["open-icon"]);
this.$element.trigger("opened", d.data())
} else {
if (this.options.cacheItems) {
f.find(".tree-folder-content:eq(0)").hide()
} else {
f.find(".tree-folder-content:eq(0)").empty()
}
f.find("." + this.options["open-icon"] + ":eq(0)").removeClass(this.options["open-icon"]).addClass(this.options["close-icon"]);
this.$element.trigger("closed", d.data())
}
},
selectedItems: function() {
var e = this.$element.find(".tree-selected");
var d = [];
a.each(e,
function(f, g) {
d.push(a(g).data())
});
return d
}
};
a.fn.tree = function(e, g) {
var f;
var d = this.each(function() {
var j = a(this);
var i = j.data("tree");
var h = typeof e === "object" && e;
if (!i) {
j.data("tree", (i = new b(this, h)))
}
if (typeof e === "string") {
f = i[e](g)
}
});
return (f === c) ? d: f
};
a.fn.tree.defaults = {
multiSelect: false,
loadingHTML: "<div>Loading...</div>",
cacheItems: true
};
a.fn.tree.Constructor = b
})(window.jQuery);
这段代码是加密后,反向格式化的,所以里面的变量都是abc之类的,但是不要紧,大家关注一下上面的onclick函数调用,对调用了selectItem,继续跟踪
selectItem最后又调用了this.$element.trigger("selected", {
info: f
})
这就是底牌的关键了,this.$element就是当前我们绑定树的那个html节点的jquery包装器,然后,selected不解释,大家自己看看jquery的trigger方法。后面的f就是当前被你选中节点的对应的你的数据结构,注意上面数据结构中,自己定义的除name和type外的其他数据,都可以在这里获取到。所以这就简单了。
$("#tree1").bind('selected',function(a,b,c,d){
alert(0);
});
搞定,至于详细参数,自己跟踪吧。
使用例子其实在众联breeze中的工具部分大量的用到。众联Breeze是一个不错的开发框架,很全,前后端都有,里面的工具是一个集成的在线编辑工具,很有意思,有情趣的朋友可以上去翻翻。
另外,在相关资源中也可以找到treeviewgadget,这个是我们用gadget将整个怪兽封装了一层,拿起即用方便的多。