EasyUI + Qunee 组件同步示例
HTML第三方组件种类繁多,小到按钮,工具栏,大到树图,表格,布局框架,以及各种图表,每种组件都有其优势,比如EasyUI的树和表格,Bootstrap的表单,Qunee的拓扑图等,一个应用需要整合多种组件,本文将以一个示例来介绍EasyUI与Qunee组件的同步使用
引入相关js和css文件
本例用到jquery, bootstrap, easyui和qunee,分别引入相关文件
<script type="text/javascript" src="http://demo.qunee.com/js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="http://demo.qunee.com/js/bootstrap/bootstrap.min.js?v=1.3"></script>
<script type="text/javascript" src="http://demo.qunee.com/jquery-easyui-1.3.6/jquery.easyui.min.js"></script>
<script src="http://demo.qunee.com/lib/qunee-min.js"></script>
<script src="common.js"></script>
<link rel="stylesheet" href="http://demo.qunee.com/js/bootstrap/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="http://demo.qunee.com/jquery-easyui-1.3.6/themes/gray/easyui.css">
<link rel="stylesheet" type="text/css" href="http://demo.qunee.com/jquery-easyui-1.3.6/themes/icon.css">
使用EasyUI布局框架
EasyUI支持东南西北中区域布局,类似Java Swing中的BorderLayout,本例中,左侧放置树图,中间为拓扑图
<body class="easyui-layout">
<div data-options="region:'west',split:true" border="false" style="width:200px;padding-left: 10px;">
<h3 style="border-bottom:1px solid #ddd;padding:0 0 3px 5px;margin-top: 0px;">拓扑视图</h3>
<ul id="tree" class="easyui-tree"></ul>
</div>
<div data-options="region:'north'" border="false" style="height:60px;"><h3 style="text-align: center;">Qunee + EasyUI 同步示例</h3></div>
<div id="center_panel" data-options="region:'center'" style="padding-right: 10px;">
<div class="easyui-tabs" data-options="fit:true,border:false,plain:true">
<div title="网络视图" id="graph_panel" class="q-panel">
<div id="toolbar" class="q-toolbar"></div>
<div id="canvas_panel" class="q-content">
<div id="canvas" class="q-canvas"></div>
<div id="toolbox"></div>
</div>
</div>
<div title="JSON" style="padding: 10px;" >JSON</div>
</div>
</div>
<div id="footer" data-options="region:'south',border:false">Copyright © 2014 <a href="http://qunee.com">Qunee.com</a></div>
</body>
配置风格样式
本人喜欢简洁风格,故而删除了大部分的边框和背景,并采用了灰色模板
<style>
#graph_panel {
height: 100%;
}
.tabs-panels .panel-body{
border-left: solid 1px #DDD;
border-right: solid 1px #DDD;
}
.tree-node {
height: 20px;
}
.q-panel {
padding-top: 40px;
position: relative;
}
.q-toolbar {
padding: 5px;
}
.q-panel .q-toolbar {
position: absolute;
top: 0px;
height: 40px;
width: 100%;
z-index: 1;
}
.q-panel .q-content {
height: 100%;
background-color: #FFF;
overflow: hidden;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
position: relative;
}
.q-canvas {
height: 100%;
}
#canvas_panel {
position: relative;
overflow: hidden;
}
#canvas {
width: 100%;
background-color: #FFF;
outline: none;
overflow: hidden;
}
#toolbar {
background-color: #F8F8F8;
border-bottom: solid 1px #DDD;
padding: 5px;
}
#toolbar .btn, #toolbar .btn-group {
margin-right: 5px;
}
#toolbar .btn-group .btn {
margin-right: 0px;
}
#toolbox {
position: absolute;
top: 0px;
background-color: #F8F8F8;
padding: 5px;
}
#toolbox > img, #toolbox > button {
display: block;
padding: 8px 7px 0 7px;
border-radius: 0px;
}
.layout-split-west {
border-right: 5px solid rgba(255, 255, 255, 0);
}
#center_panel {
border: none;
}
.panel {
-webkit-box-shadow: none;
box-shadow: none;
}
#footer {
text-align: center;
padding: 8px;
border-top: solid 1px #DDD;
background-color: #EEE;
}
.node_icon{
background: url('images/node_icon.png') no-repeat;
background-size: 18px;
background-position:center;
}
.edge_icon{
background: url('images/edge_icon.png') no-repeat;
background-size: 18px;
background-position:center;
}
</style>
界面效果
添加数据
这里采用了模拟数据,使用json格式,数据如下: 节点数据包含编号、名称以及父节点编号等属性,而连线数据则需要起始结束节点的编号
{
"nodes": [
{
"id": 1,
"name": "001"
},
{
"id": 2,
"name": "R1",
"parent": 1
},
...
],
"relations": [
{
"from": 1,
"to": 2
},
{
"from": 1,
"to": 3
},
...
]
}
加载数据
根据json数据,创建对应的图元数据和树节点数据
function initDatas(){
Q.loadJSON("testData.json", function(json){
var topoNodes = json.nodes;
var relations = json.relations;
initTopology(topoNodes,relations);
graph.callLater(function(){
var layouter = new Q.TreeLayouter(graph);
layouter.doLayout();
graph.moveToCenter();
})
var datas = [];
var map = {};
graph.graphModel.forEachByBreadthFirst(function(d){
var name = d.name || d.type;
var data = {text: name, id: d.id, iconCls: getTreeIcon(d)};
map[d.id] = data;
var parent = d.parent;
if(!parent){
datas.push(data);
return;
}
parent = map[parent.id];
var children = parent.children;
if(!children){
children = parent.children = [];
}
children.push(data);
});
$('#tree').tree({
data: datas
});
syncSelectionTreeAndGraph("tree", graph);
syncDataTreeAndGraph("tree", graph);
});
}
function initTopology(topoNodes,topoRelations)
{
var map = {};
for(var i=0;i<topoNodes.length;i++)
{
var node = topoNodes[i];
var qNode = new Q.Node();
qNode.name=node.name;
qNode.location = new Q.Point(node.x,node.y);
graph.graphModel.add(qNode);
map[node.id] = qNode;
}
for(var i=0;i<topoNodes.length;i++)
{
var node = topoNodes[i];
var parent = node.parent;
if(parent){
parent = map[parent];
if(parent){
map[node.id].parent = parent;
}
}
}
for(var i=0;i<topoRelations.length;i++)
{
var relation = topoRelations[i];
var nodeFrom = map[relation.from];
var nodeTo = map[relation.to];
if(nodeFrom && nodeTo){
var edge = graph.createEdge(nodeFrom, nodeTo);
edge.info = relation;
}
}
}
到此时,界面已初步呈现了
组件状态同步
然后初始化工具栏,对树图和拓扑图填充数据,最后实现两组件的状态同步,包括选中同步和数据同步
首先数据同步
因为树图上不可编辑,所以这里只需要监听Graph组件图元的变化事件,在增加和删除图元时,分别对Tree组件进行处理
function syncDataTreeAndGraph(treeId, graph){
treeId = "#" + treeId;
graph.listChangeDispatcher.addListener(function(evt){
var data = evt.data;
switch (evt.kind) {
case Q.ListEvent.KIND_ADD :
var treeData = {data:[{id: data.id, text: data.name, iconCls: getTreeIcon(data)}]};
$(treeId).tree('append', treeData);
break;
case Q.ListEvent.KIND_REMOVE :
Q.forEach(data, function(node){
var node = $(treeId).tree('find', node.id);
if(node){
$(treeId).tree('remove', node.target);
}
});
break;
case Q.ListEvent.KIND_CLEAR :
break;
}
});
}
选中状态同步
需要分别监听Tree的"onSelect"事件,以及graph的selectionChangeDispatcher事件派发器,实现双向同步,有了监听器,一切变得容易
function syncSelectionTreeAndGraph(treeId, graph){
treeId = "#" + treeId;
var selectionAjdusting;
graph.selectionChangeDispatcher.addListener(function(evt){
if(selectionAjdusting){
return;
}
selectionAjdusting = true;
var selection = [];
graph.selectionModel.forEach(function(node){
var node = $(treeId).tree('find', node.id);
if(node){
selection.push(node.target);
}
});
$(treeId).tree('select', selection);
selectionAjdusting = false;
});
$(treeId).tree({onSelect: function(){
if(selectionAjdusting){
return;
}
selectionAjdusting = true;
var selected = $(treeId).tree("getSelected");
if(selected){
var node = graph.getElement(selected.id);
graph.selectionModel.set(node);
if(node){
ensureVisible(node);
}
}
selectionAjdusting = false;
}});
}