索引
插件已不再维护,建议使用渲染元素实现
ht.Node目前支持用图片、矢量来渲染,但是对于某些应用(比如SCADA),经常需要在界面中嵌入一些可交互元素(文本框,按钮等),
此时普通Node就有些力不从心了。为此HT扩展实现了ht.HtmlNode,它可以将HTML元素渲染到拓扑上,既保持HTML元素的可交互性,
又能实现普通Node的缩放旋转等功能。
HtmlNode提供了如下API:
html通过setHtml和getHtml操作,表示要渲染的HTML内容,可以是HTML文本字符串、DOM对象或HT组件(如GraphView)context通过setContext和getContext操作,用于数据绑定padding通过setPadding和getPadding操作,表示HTML内容边框和Node选中边框之间的距离,默认为6scalable通过setScalable和getScalable操作,表示HTML内容是否可拉伸。值为boolean型(默认为true):true表示当调整HtmlNode的宽高时,用transform样式调整HTML内容的缩放false表示当调整HtmlNode的宽高时,改变HTML内容的第一个元素的with和height使其适应HtmlNode的尺寸。这意味着:如果此参数设为false,用户设置的
HTML内容应该只有一个根元素setDraggerImage、getDraggerImage用于设置或获取HtmlNode右上角的拖拽图标,必须是通过ht.Default.setImage注册的图片的名字,默认为node_draggersetDraggerImageWidth、getDraggerImageWidth用于设置或获取HtmlNode右上角拖拽图标的宽度setDraggerImageHeight、getDraggerImageHeight用于设置或获取HtmlNode右上角拖拽图标的高度GraphView上扩展的API:
adjustHtmlNodeIndex,默认为true,表示自动调整HTML内容的z-index,让其在选中时置顶显示;设为false可提高性能HtmlNode的html属性可以设置为HTML文本,然后可以按照操作一般Node的方式设置position、width、height等属性。
示例:
var node = new ht.HtmlNode();
node.setPosition(200, 100);
node.setHtml("<div class='wrapper'>\n\
<img width='20' height='24' src='data:image/png;base64,'>\n\
Hello, I can <i>render</i> any HTML!</div>");
dataModel.add(node);
上例中HtmlNode将一段HTML文本渲染到拓扑上,HTML元素不但可以响应用户动作,而且可以利用拓扑组件对其进行缩放旋转等操作。需要注意:
为了使HTML元素响应用户动作,HtmlNode屏蔽了在body上的拖拽移动功能,当HtmlNode被选中时,其右上角会出现一个十字方向标记,
拖拽它即可实现拖拽移动HtmlNode。
怎样修改已经展现出来的HTML内容?最直接的方式是给HTML元素指定id,通过document.getElementById('id')获取到DOM对象,然后对其进行操作。
除此之外,HtmlNode还提供了简单的双向数据绑定:
将数据从HtmlNode的属性更新至HTML元素:
HtmlNode集成Handlebars框架以实现将HtmlNode的属性更新至HTML元素。Handlebars可以在运行时将模板中的关键字替换为属性值,Handlebars的详细介绍参考这里:http://handlebarsjs.com/
将HTML元素中的值更新至HtmlNode属性:
HtmlNode在HTML元素的根结点监听change事件,并根据事件目标的bind属性将其value反填回HtmlNode的context属性上,
如下面示例:
示例:
<!--引入handlebars-->
<script src="../../../lib/handlebars.js"></script>
<!--创建HTML模板,{{}}可以将HtmlNode上的属性同步到HTML元素中,而HTML元素的bind属性用于将value同步至HtmlNode-->
<script id="controlpanel-template" type="text/x-handlebars-template">
<div class="panel">
<div class="title">Dashboard</div>
<div class="content">
<p><span class="label">KPI:</span><span class="value">{{value}}</span></p>
<p>
<span class="label">Stop:</span>
<span class="value"><input type="checkbox" {{#if isStopped}}checked{{/if}} bind="isStopped"></span>
</p>
</div>
</div>
</script>
......
var node = new ht.HtmlNode();
node.setPosition(200, 100);
node.setHtml(document.getElementById("controlpanel-template").innerHTML);
node.setContext(context);
dataModel.add(node);
/*
* 每隔一秒刷新HtmlNode的context属性,HtmlNode自动将其同步到HTML元素
*/
setInterval(function() {
if(!context.isStopped){
context.value=parseInt(100*Math.random());
graphView.invalidateData(node);
}
}, 1000);
setHtml的参数可以是以下三种类型:
HTML文本字符串,如setHtml("<div>html text</div>")DOM对象,开发人员可以通过DOM API创建HTML元素,然后通过setHtml交给HtmlNode渲染HT对象,HtmlNode也可以渲染GraphView,TablePane等HT组件,需要注意:如果渲染HT组件,最好将scalable
设为false示例:
var htmlNode = new ht.HtmlNode();
htmlNode.setPosition(200, 50);
htmlNode.setName("Rendering HTML text");
htmlNode.setHtml("<div class='htmlWrapper'>Node's new name:<input type='text' value='{{value}}' bind='value'>\n\
<input type='button' value='Modify' nodeid='{{nodeid}}' onclick='modifyNodeName(event)'/></div>");
htmlNode.setContext({
value:htmlNode.getName(),
nodeid:htmlNode.getId()
});
dataModel.add(htmlNode);
var div=document.createElement("div");
div.className="domWrapper";
div.innerHTML="Rendering DOM";
var domNode = new ht.HtmlNode();
domNode.setHtml(div);
domNode.setPosition(200, 150);
dataModel.add(domNode);
var htNode = new ht.HtmlNode();
htNode.setHtml(createTable());
htNode.setPosition(500,100);
htNode.setName("Alarm Statistics(TablePane)");
htNode.setScalable(false);
dataModel.add(htNode);
接下来看一个综合应用,有些应用需要在图元的周围显示一些附属信息,比如状态图标、统计表或Chart等。接下来的示例中有两个Node,
一个作为实际展示的Node,另一个作为统计表跟随在第一个Node右侧。为了节省空间,统计表应该可以展开合并,看下面的示例:
首先定义两个模板,作为HtmlNode合并和展开时的HTML内容:
<script id="table-template" type="text/x-handlebars-template">
<div class="wrapper" nodeid="{{id}}" onclick="tableIconClickHandler(event)">
<div class="table_title">Real-time monitoring<span class="shrink"></span></div>
<table class="list_table">
{{#each rows}}
<tr>
<td>{{id}}</td>
<td>{{temperature}}</td>
<td>{{pressure}}</td>
<td>{{time}}</td>
</tr>
{{/each}}
</table>
</div>
</script>
<script id="table-min-template" type="text/x-handlebars-template">
<span class="open" nodeid="{{id}}" onclick="tableIconClickHandler(event)"></span>
</script>
接下来声明Host节点和统计表节点:
var host = new ht.Node();
host.setName("This is Host");
dataModel.add(host);
var tableNode = new ht.HtmlNode();
tableNode.setHtml(document.getElementById("table-template").innerHTML);
tableNode.setPosition(230, 100);
tableNode.setContext({
id: tableNode.getId(),
rows: [
{
id: 1,
temperature: 23.3,
pressure: 231.6,
time: "18:12"
},
{
id: 2,
temperature: 29.3,
pressure: 231,
time: "18:13"
},
{
id: 3,
temperature: 25,
pressure: 211.6,
time: "18:14"
},
{
id: 4,
temperature: 22.4,
pressure: 111.6,
time: "18:15"
},
{
id: 5,
temperature: 37.4,
pressure: 171.6,
time: "18:19"
}
]
});
tableNode.setWidth(300);
tableNode.setHeight(200);
dataModel.add(tableNode);
tableNode.setPosition({x: host.getPosition().x + host.getWidth() / 2 + tableNode.getWidth() / 2 + 10,
y: host.getPosition().y - host.getHeight() / 2 + tableNode.getHeight() / 2});
最后还有一个函数处理用户在统计表右上角的点击事件:
function tableIconClickHandler(e) {
var maxInnerHTML = document.getElementById("table-template").innerHTML,
minInnerHTML = document.getElementById("table-min-template").innerHTML;
if (e.target.className === "shrink") {
var nodeid = e.currentTarget.getAttribute("nodeid"),
node = dataModel.getDataById(nodeid),
host = node.getHost(),
newWidth = 16 + node._padding,
newHeight = 16 + node._padding,
originWidth = node._originWidth,
originHeight = node._originHeight,
differenceWidth = originWidth - newWidth,
differenceHeight = originHeight - newHeight;
ht.Default.startAnim({
finishFunc: function() {//动画执行结束时调用,改变统计表的内容和位置
node.setHtml(minInnerHTML);
node.setPosition({x: host.getPosition().x + host.getWidth() / 2 + node.getWidth() / 2 + 10,
y: host.getPosition().y - host.getHeight() / 2 + node.getHeight() / 2});
},
action: function(t) {//动画每前进一帧就都会调用此方法,在这里调整统计表的尺寸的位置
node.setWidth(originWidth - differenceWidth * t);
node.setHeight(originHeight - differenceHeight * t);
node.setPosition({x: host.getPosition().x + host.getWidth() / 2 + node.getWidth() / 2 + 10,
y: host.getPosition().y - host.getHeight() / 2 + node.getHeight() / 2});
}
});
} else if (e.target.className === "expand") {
var nodeid = e.currentTarget.getAttribute("nodeid"),
node = dataModel.getDataById(nodeid),
host = node.getHost(),
originWidth = node._originWidth,
originHeight = node._originHeight;
node.setHtml(maxInnerHTML);
var newWidth = node.getWidth(),
newHeight = node.getHeight(),
differenceWidth = newWidth - originWidth,
differenceHeight = newHeight - originHeight;
node.setWidth(originWidth);
node.setHeight(originHeight);
ht.Default.startAnim({
action: function(t) {
node.setWidth(originWidth + differenceWidth * t);
node.setHeight(originHeight + differenceHeight * t);
node.setPosition({x: host.getPosition().x + host.getWidth() / 2 + node.getWidth() / 2 + 10,
y: host.getPosition().y - host.getHeight() / 2 + node.getHeight() / 2});
}
});
}
}