HT for Web 入门手册

索引<

索引

概述

HT 是什么

HT是基于HTML5标准的企业应用图形界面一站式解决方案, 其包含通用组件、拓扑组件和3D渲染引擎等丰富的图形界面开发类库,提供了完全基于HTML5的矢量编辑器、拓扑编辑器及 3D场景编辑器等多套可视化设计工具,和完善的类库开发手册、工具使用手册、及针对HTML5技术如何进行大规模团队开发的客户深度培训手册。

产品特点

易用、轻量、高性能和跨平台四原则是我们永不停止的追求

开发类库

<script src="ht.js"></script> 

HT的核心开发类库只有一个ht.jsJavaScript(以下简称js)文件,调用ht.Default.getVersion()可获得当前版本号。 我们将确保所有版本向下兼容,这意味着升级产品时,只需要更新ht.js文件,无需修改任何代码即可完成升级。

核心ht.js类库包含了数据模型、树表等通用组件、2D拓扑组件、矢量和3D渲染引擎等核心功能组件, 同时HT提供了众多扩展插件,如对话框、菜单和表单等,可满足其他需求的类库或组件供用户选择使用。

开发工具

无限制,任意文本编辑器皆可。

运行环境

任何支持HTML5标准的浏览器。多年前的ChromeFirefoxSafariOpera版本都已经支持HTML5iOSAndroid等主流移动设备的浏览器也都已支持HTML5IE需要IE9及以上的版本, 如果采用HT for Web 3D则需要IE11及以上版本支持,建议尽量采用最新版本浏览器。

如果项目环境必须使用IE6IE7IE8等老版IE浏览器,或因采用HT for Web 3D, 而现场环境无法升级到IE11,则可以考虑安装Google Chrome Frame插件, 在页面嵌入以下Tag代码片段,该页面就会采用Chrome来渲染。

<meta http-equiv="X-UA-Compatible" content="chrome=1"> 

使用Google Chrome Frame还需要注意一下几点:

可采用嵌入OBJECT元素的解决方案,绕开Google Chrome Frame不支持iframe的问题

<OBJECT ID="ChromeFrame" WIDTH=500 HEIGHT=500 CODEBASE="http://www.google.com"
        CLASSID="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
    <PARAM NAME="src" VALUE="http://www.google.com">
    <embed ID="ChromeFramePlugin" WIDTH=500 HEIGHT=500 NAME="ChromeFrame"
        SRC="http://www.google.com" TYPE="application/chromeframe">
    </embed>
</OBJECT>

Google Chrome Frame已于20141月停止支持和更新,目前Google Chrome Frame以发展到31的版本, 这个版本已满足HT2D3D所需的Canvas功能,因此HT的客户可采用Google Chrome Frame解决兼容IE老版本问题。 其他问题可参考Google Chrome FrameDeveloper GuideTroubleshooting

部分老版本的Android终端系统,对canvas的支持存在bug会出现没擦除干净有残影,以下是段workaround代码:

ht.Default.viewListener = function(view, kind){
    var canvas = view._canvas;
    if(canvas && kind === 'beginValidate'){
        canvas.width = 0;
        canvas.height = 0;
        canvas.style.width = 0;
        canvas.style.height = 0;                    
    }
};

函数简写

HT基于JavaScript语言,由于JavaScript动态语言的灵活性,开发工具在拼写和错误提示方面功能较弱, 因此HT为一些常用的函数提供了简写的命名方式,熟记以下常用函数简写可提高编码效率。

HT简写的几个字母有如下含义:

HT简写函数列表如下:

以下为常见简写示例:

graphView.getDataModel().getSelectionModel().setSelection(data) = graphView.dm().sm().ss(data) graphView.getDataModel().getSelectionModel().addSelectionChangeListener(func) = graphView.dm().sm().ms(func) dataModel.getSelectionModel().getLastData().setAttr('age', 35) = dataModel.sm().ld().a('age', 35)

模型

设计模式

详见数据模型手册

类包层次

HT沿袭常规面对对象语言设计风格,对类和包采用骆驼式命名法,类名以大写开头的,包名以小写开头。 整个框架只占有全局变量'ht',对于常规的前端页面开发意味着ht.js运行后会占用window.ht, 如果ht.js运行在WebWork环境将占用self.ht变量, 如果ht.js运行于Node.js环境将占用module.exports.ht变量。

HT整体框架层次很“扁平化”,ht.*包含模型和系统相关类,ht.widget.*包含通用组件相关类,ht.graph.*包含2D图形组件相关类, ht.graph3d.*包含3D图形组件相关类,考虑到js语言以及HTML5应用特殊性,尽量减少类包层次,简短包函数命名是HT框架API设计的特点, 这样能减少用户学习指数,降低编码工作量,有助于整体开发包精小。

JavaScript语言未提供严谨的面对对象类的语言级支持,为此HT设计了一套类封装体系供用户选择采用, 详见序列化手册

工具类

List

HT提供了ht.List的数组类,该类型对Array数组进行了封装,提供了更易记易用的函数接口:

Notifier

HT提供了事件通知管理器ht.Notifier类,可对其添加监听函数、删除监听函数,以及派发事件到所有监听函数:

Default

ht.Default对象定义了所有组件的默认参数值,以及一系列的工具类函数,详见风格手册

Style

ht.Style对象定义了ht.Data类型的默认style属性值,详见风格手册

Color

ht.Color对象定义了HT系统的所有默认颜色,详见风格手册

JSONSerializer

ht.JSONSerializer序列化类,提供了对DataModel数据模型的JSON格式序列化和反序列化功能, 详见序列化手册

数据类型

ht.Data(简称为Data,下文中将以省去ht.包头的方式进行介绍)是HT最基础的数据类型,用户可将业务信息存储在Data对象属性上, 目前HT提供了NodeEdgeTabColumn等子类,这些类型在不同的视图组件中具有不同的显示效果:TreeView树形组件上Data代表一个树节点; TableView表格组件上Data代表一行记录,列代表Data对象的属性;GraphView图形组件上Node代表一个图形元素; 定义页签时TabView采用Tab类型存储页签信息;定义表格列时TableView采用Column存储列信息。

Data

详见数据模型手册

Node

ht.Node类型是GraphViewGraph3dview呈现节点图元的基础类,继承于Data类。 以下为NodeGraphView拓扑图相关函数属性,与Graph3dView相关函数属性详见3D手册Node除了显示图片外,还能显示多种预定义图形,详见shape章节

以上示例中创建了air11图元吸附上了air13图元,air11改变了尺寸,air13设置了旋转:

ht.Default.setImage('mac', 'res/mac-air.png');            

air11 = new ht.Node();
air11.setName('11-inch MacBook Air');
air11.setImage('mac');
air11.setSize(80, 43);
air11.setPosition(100, 70);                
dataModel.add(air11);

air13 = new ht.Node();
air13.setName('13-inch MacBook Air');                
air13.setImage('res/mac-air.png');
air13.setPosition(260, 70);
air13.setRotation(Math.PI/2);
dataModel.add(air13);

air11.setHost(air13);   

代码将GraphView设置为可编辑,同时只允许air11能编辑大小,air13能旋转,该功能通过设置过滤器实现

graphView.setEditable(true);
graphView.setRectEditableFunc(function(data){
    return data === air11;
});
graphView.setRotationEditableFunc(function(data){
    return data === air13;
});
锚点

锚点是Node上一个重要的概念,节点绘制的是一个矩形区域,而锚点是决定了矩形区域中哪个位置是节点的坐标点位置,锚点值是一个百分比数值,{x:0,y:0}是在区域左上角,{x:1,y:1}是在区域右下角,默认是以{x:0.5,y:0.5}为锚点,也就是图元中心点,如果大于1或者小于0则锚点会在节点矩形区域之外。可以通过node.getAnchornode.setAnchor获取和设置,也可以通过node.getAnchorXnode.setAnchorXnode.getAnchorYnode.setAnchorY方法单独设置获取。

以上案例中可以看出,每行节点的纵坐标是一致的,但是由于锚点的不同导致旋转缩放上的差异。

Edge

ht.Edge类型用于连接起始和目标两个Node节点,两个节点间可以有多条Edge存在,也允许起始和目标为同一节点。 连线的agent指的是目前图形上真正代理连接该连线的节点,当节点位于关闭的Group之内时,Group将代理内部的节点进行连接。 Edge的更多样式属性参见连线风格

可通过new ht.Edge(source, target)直接在构造函数中传入sourcetarget节点对象, 也可构建Edge对象之后再分别设置,该示例重载了graphView.getLabel函数,自定义了图元文字标签, 实现同组多连线合并时,代理连线文字能体现连线组信息:

var edge = new ht.Edge();
edge.setSource(source);
edge.setTarget(target);
dataModel.add(edge);               

edge = new ht.Edge(source, target);
edge.toggle();
dataModel.add(edge);                               

edge = new ht.Edge(source, source);
dataModel.add(edge);                 

graphView.getLabel = function(data){
    if(data instanceof ht.Edge){
        if(data.isEdgeGroupAgent()){
            return data.getEdgeGroupSize() + ' become 1';
        }
    }
    return data.getName();
};

Group

ht.Group类型用于作为父容器包含孩子图元,在GraphView拓扑图上可通过双击进行展开合并,合并时会自定隐藏子孙图元节点, 如果有子节点有连线连接到外部时,合并的Group将代理进行连接。Group的移动会带动孩子节点跟随, 孩子的位置和大小变化也会影响Group的展开图形和position位置。参见Group样式属性

示例中创建了Group对象,通过group.setExpanded(true)设置为展开状态,并未对Group对象指定位置, 而是由后续添加的孩子节点位置来自动影响Group位置。添加孩子通过addChildsetParent都是可选的:

var group = new ht.Group();
group.setName('Double click on me'); 
group.setExpanded(true);
dataModel.add(group);

var node1 = new ht.Node();
node1.setName('Node1');
node1.setPosition(80, 80);
group.addChild(node1);
dataModel.add(node1);

var node2 = new ht.Node();
node2.setName('Node2');              
node2.setPosition(180, 80);
node2.setParent(group);
dataModel.add(node2);

构建了一个styleingroup属性为false的图元,该图元将游离于Group之外,Group移动会带动其跟随, 但该节点位置或大小变化不会影响Group对象:

var node4 = new ht.Node();
node4.setName('The Special One');   
node4.setStyle('ingroup', false);
node4.setPosition(290, 100);
group.addChild(node4);
dataModel.add(node4);

以下代码构建了一个label文字不影响Group的孩子图元,为实现该功能重载了graphView.getBoundsForGroup函数, 对于node3的图元仅返回其node3.getRect()大小,其他图元继续保持原始函数的逻辑, 该例先缓存了默认函数var oldFunc = graphView.getBoundsForGroup, 然后在重载函数中通过oldFunc.call(this, child)的方式进行调用,这使用HT常见的重载技巧, 这样的方式无需定义新类就可以进行函数重载,且可以根据需要调用原始函数逻辑:

var node3 = new ht.Node();
node3.setPosition(130, 140);
node3.s({
    'label.font': 'bold 21px arial',
    'label.color': 'white',
    'label.offset.y': 8,
    'label.background': '#E74C3C'
});                
node3.setName('HT for Web');
node3.setParent(group);
dataModel.add(node3);

var oldFunc = graphView.getBoundsForGroup;
graphView.getBoundsForGroup = function(child){
    if(child === node3){
        return node3.getRect();
    }
    return oldFunc.call(this, child);
};

Shape

详见形状手册

Polyline

详见形状手册

Grid

ht.Grid类型一般用于作为容器,对附属节点(attachNode.setHost(grid))进行网格布局,附属节点可为Grid类型,进而实现嵌套式布局。

SubGraph

ht.SubGraph类型与Group类型有相似之处,他们都会影响孩子图元的呈现方式,不同于Group类型与孩子节点在同层界面展示, SubGraph类型将其孩子包括子孙节点呈现于下一层界面,在GraphView组件上表现为双击SubGraph图元将进入新的界面内容, 在新的界面内容下双击背景可以返回SubGraph图元所在的界面,SubGraph可无限制层层嵌套。

GraphView上与SubGraph相关函数如下:

Tab

详见页签组件手册

Column

详见表格组件手册

Property

详见属性组件手册

数据容器

详见数据模型手册

选择模型

详见数据模型手册

组件

构成

HT框架的组件指的是可视化可交互的视图控件,HT框架基于HTML5技术,因此HT组件的可视化部分本质就是HTML的元素, 大部分HT组件与DataModel数据模型绑定,用户通过操作纯js的语言即可驱动可视化组件,这样屏蔽了HTML底层图形技术复杂性。 HTHTML5技术封装的目的在于提高开发效率和可维护性,但并不意味着不允许用户直接操作HTML原生元素, 有HTML5开发经验的程序员,在了解HT系统机制的前提下,大可运用各种HTML5的技术对HT组件做自定义扩展。

所有HT组件最根层都为一个div组件,可通过组件的getView()函数获得, 默认和自定义交互事件监听一般添加在该div上(getView().addEventListener(type ,func, false)), 渲染层一般由canvas提供。 用户可直接对根divcavnas层设置css背景等样式, 也可以添加新的HTML组件到根div层上,作为canvas的兄弟组件一起呈现。 HT组件一般都以设置positionabsolute的绝对定位方式,box-sizing属性都以设置为border-box

HT的组件大部分都提供了isDisabled()setDisabled(true/false, iconURL)函数可使整个组件处于不可用状态, 一般用于远程加载数据过程暂时让组件处于不可操作状态,iconURL在这种情况下一般可设置代表正在加载状态的gif图片路径。 处于disabled状态的组件会生成一个div遮挡住整个组件,通过ht.Default.disabledBackground可修改遮挡组件背景色。

HT的组件的渲染大部分由中间的canvas组件实现,具体的组件会提供相应的扩展函数供自定义, 例如ListView提供drawRow供函数自定义行绘制,GraphView通过矢量机制自定义图元image在拓扑上的呈现等, 基于canvas渲染的组件如ListViewPropertyViewTreeViewTableViewTreeTableViewGraphView等组件, 都提供了绘制canvas最底层和最顶层的画笔接口:

HT的所有组件都没有采用HTML自带的滚动条功能,完全由HT组件内部自绘制实现,滚动条可自动隐藏, 在组件平移或鼠标滑过边界时动态出现,默认透明的滚动条cover在内容之上,不会影响组件界面布局:

HT组件的坐标原点默认在左上角,大部分组件都有平移功能,平移的可视化效果就是滚动, 分为水平横坐标平移translateX属性,和垂直纵坐标平移translateY属性,两者默认值都为0

有些组件只能水平平移,例如TabViewTableHeader;有些组件只能垂直平移,例如ListViewTreeView; 而GraphViewTableView等则水平和垂直皆可;可重载adjustTranslateXadjustTranslateY函数改变平移逻辑:

HT的组件一般都会嵌入BorderPaneSplitViewTabView等容器中使用,而最外层的HT组件则需要用户手工将getView() 返回的底层div元素添加到页面的DOM元素中,这里需要注意的是,当父容器大小变化时,如果父容器是BorderPaneSplitView 等这些HT预定义的容器组件,则HT的容器会自动递归调用孩子组件invalidate函数通知更新。但如果父容器是原生的html元素, 则HT组件无法获知需要更新,因此最外层的HT组件一般需要监听window的窗口大小变化事件,调用最外层组件invalidate函数进行更新。

为了最外层组件加载填充满窗口的方便性,HT的所有组件都有addToDOM函数,其实现逻辑如下,其中ivinvalidate的简写:

addToDOM = function(){   
    var self = this,
        view = self.getView(),   
        style = view.style;
    document.body.appendChild(view);            
    style.left = '0';
    style.right = '0';
    style.top = '0';
    style.bottom = '0';      
    window.addEventListener('resize', function () { self.iv(); }, false);            
}

配置

改变HT系统默认属性,需要通过全局的htconfig变量名指定,HT系统只在初始化时读取htconfig的配置信息, 因此该属性必须在引入ht.js包之前初始化好,运行状态时修改htconfig变量不会再起作用,示例代码如下:

<script>
    htconfig = {
        Color: {       
            label: '#000',
            labelSelect: '#FFF'          
        },
        Default: {
            toolTipDelay: 100,
            toolTipContinual: true
        },                
        Style: {
            'select.color': '#E74C3C',
            'select.width': 3
        }
    };
</script>           
<script src="ht.js"></script>   

可配置的参数分为三大类,具体属性说明参见风格手册

图片

图片是图形组件的重要资源,树上的图标,拓扑上图元等都可用图片来绘制,HT支持PNGJPG等常规图片格式, 如Node章节的示例所示,图片有两种使用方式:

直接设置路径方式开发方便,无需提前注册图片,但数据模型序列化 时图片路径占用内存较多,将来图片路径变化后不利于管理维护,两种方式都是正确的使用方式,可根据项目情况选中不同方式或混合使用。 如果采用url的路径方式HT内部会自动加载图片,并在onload之后自动更新相应的视图组件。

HT的框架下图片被赋予了更广泛的含义,HT提供了自定义JSON格式的矢量描述形式,以HT标准定义的JSON矢量格式, 也可以作为图片进行注册和使用,HT的矢量方式比传统图片格式更节省空间,缩放不失真,最强大之处在于矢量的所有图形参数, 皆可与Data模型上的数据动态绑定,具体说明参见矢量手册

ht.Default.setImage函数有以下几种调用方式:

采用直接注册imghtml元素的方式,用户需确保img资源已加载,这种方式下HT不会监听其onload事件, 因此不会自动通知视图组件更新。

ht.Default.getImage(name, color)可获取对应的图片元素,在图片加载过程该函数返回空,只有onload之后才能得到相应图元元素, color为颜色参数,一般为空,如果有颜色值则HT内部会构建出对图片进行color染色后的新图片

HT还提供了以下几种针对图片的绘制函数,以下参数中image为可绘制的imgcanvas元素,也可为矢量的json格式, 由于矢量可动态绑定数据模型具有染色功能,因此绘制矢量时可传入data数据、view组件和color染色参数:

动画

HT的数据模型驱动图形组件的设计架构下,动画可理解为将某些属性由起始值逐渐变到目标值的过程, HT提供了ht.Default.startAnim的动画函数,其示例代码如下。

ht.Default.startAnim({
    frames: 12, // 动画帧数
    interval: 10, // 动画帧间隔毫秒数
    easing: function(t){ return t * t; }, // 动画缓动函数,默认采用`ht.Default.animEasing`
    finishFunc: function(){ console.log('Done!') }, // 动画结束后调用的函数。
    action: function(v, t){ // action函数必须提供,实现动画过程中的属性变化。
        node.setPosition( // 此例子展示将节点`node`从位置`p1`动画到位置`p2`。
            p1.x + (p2.x - p1.x) * v,
            p1.y + (p2.y - p1.y) * v
        );
    }
});

ht.Default.startAnim支持Frame-BasedTime-Based两种方式的动画,以上代码为Frame-Based方式, 这种方式用户通过指定frames动画帧数,以及interval动画帧间隔参数控制动画效果。

以下代码为Time-Based方式,该方式用户只需要指定duration的动画周期的毫秒数即可,HT将在指定的时间周期内完成动画, 不同于Frame-Based方式有明确固定的帧数,即action函数被调用多少次,Time-Based方式帧数或action函数被调用次数取决于系统环境, 一般来说系统配置更好的机器,更高效的浏览器则调用帧数越多,动画过程更平滑。由于js语言无法精确控制interval时间间隔, 采用Frame-Based不能精确控制动画时间周期,即使相同的framesinterval参数在不同的环境,可能会出现动画周期差异较大的问题, 因此HT默认采用Time-Based的方式,如果不设置durationframes参数,则duration参数将被系统自动设置为ht.Default.animDuration值。

ht.Default.startAnim({
    duration: 500, // 动画周期毫秒数,默认采用`ht.Default.animDuration`
    action: function(v, t){ 
        ... 
    }
});

startAnim函数会返回一个anim对象,可调用anim.stop(true)终止动画,其中的参数shouldBeFinished代表是否完全未达到的目标改变, 如果为true则会调用anim.action(anim.easing(1))。同时anim还具有anim.pause()anim.resume()可中断和继续动画功能, 以及anim.isRunning()函数判断动画是否正在进行。

注:当anim对象调用stop方法时,anim中定义的finishFunc方法会被执行。

action函数的第一个参数v代表通过easing(t)函数运算后的值,t代表当前动画进行的进度[0~1],一般属性变化根据v参数进行。

以上示例展示了点击背景图元动画移动到点击位置,点击图元自身进行旋转和缩放的动画效果, ht.Default.startAnim中得easing参数是用于让用户定义函数,通过数学公式控制动画, 如匀速变化、先慢后快等效果,可参考http://easings.net/, 示例代码easing.js定义了一系列的动画函数可供选用:

var Easing = {
    swing: function (t) {
        return ( -Math.cos(t * PI) / 2 ) + 0.5;
    },
    /**
    * Begins slowly and accelerates towards end. (quadratic)
    */
    easeIn: function (t) {
        return t * t;
    },
    /**
    * Begins quickly and decelerates towards end.  (quadratic)
    */
    easeOut: function (t) {
        return ( 2 - t) * t;
    },
    // ... 
}

示例中构建通过graphView.setInteractors(null)去掉所有默认交互功能,通过view.addEventListener添加监听器, 同时构建了htmlselect元素用于选择不同easing效果,将其添加到graphView.getView()div组件上, 因此在自定义交互事件中需要对点击在select元素做过滤处理,其中graphView.getLogicalPoint(e)根据交互事件返回逻辑坐标位置。

该例子点击图元时触发图元围绕自身中心旋转一周,同时图元由大变小再恢复原尺寸,该逻辑通过设置frames30帧和interval16毫秒间隔的 Frame-Based方式完成动画;点击背景区域触发图元移动到指定的点击位置,该移动过程通过设置duration500周期的Time-Based方式完成动画。

var select = document.createElement('select');
select.style.position = 'absolute';
select.style.top = '10px';
select.style.right = '10px';
view.appendChild(select);
for(var name in Easing){
    var option = document.createElement('option');
    option.innerHTML = name;
    if(name === 'easeOut'){
        option.setAttribute('selected', 'true');
    }
    select.appendChild(option);
}                  

graphView.setInteractors(null);
var type = "ontouchend" in document ? 'touchstart' : 'mousedown';                                

isAnimating = false;
view.addEventListener(type, function(e){
    e.preventDefault();
    if(isAnimating || e.target === select || !ht.Default.isLeftButton(e)){
        return;
    }
    isAnimating = true;
    var data = graphView.getDataAt(e);
    var easing = Easing[select.value];
    var finishFunc = function(){
        isAnimating = false;
    };
    if(data === toy){
        var size = toy.getSize();
        ht.Default.startAnim({
            frames: 30, 
            interval: 16,
            easing: easing,
            finishFunc: finishFunc,                            
            action: function(v){
                toy.setRotation(Math.PI * v);
                var r = Math.abs(v - 0.5) * 2;
                toy.setSize(size.width * r, size.height * r);
            }
        });                        
    }else{
        var p2 = graphView.getLogicalPoint(e);
        var p1 = toy.getPosition();
        anim = ht.Default.startAnim({  
            duration: 500,
            easing: easing,
            finishFunc: finishFunc,
            action: function(v){
                toy.setPosition(
                    p1.x + (p2.x - p1.x) * v,
                    p1.y + (p2.y - p1.y) * v
                );
            }
        });                        
    }
}, false);

示例直接修改了底层div组件的style背景色,同时增加了顶部和底部的画笔,绘制了click anywhere you want ..的文字信息, 通过移动图元可发现topPainter绘制的内容呈现在图元之上,bottomPainter绘制的内容呈现在图元之下。

view.style.background = '#FCFCFC';
graphView.addTopPainter(function(g){
    ht.Default.drawText(g, 'click anywhere you want ..', '24px Arial', 'lightgray', 50, 100, 0, 0, 'left');
});      
graphView.addBottomPainter(function(g){
    ht.Default.drawText(g, 'click anywhere you want ..', '24px Arial', 'lightblue', 200, 180, 0, 0, 'left');
}); 

HT很多组件的函数也带有动画功能,如setTranslate(x, y, anim)zoomIn(anim)rotate(leftRight, upDown, anim)等函数都带有anim的参数选项,该参数可传入两种类型:

除了通过ht.Default.startAnim调用启动动画外,DataModel上也具有启动调度任务的函数, 可扩展实现为流动、闪烁、大小变化等动画效果,详见调度手册, 如需更强大的描述性动画控制可参考动画插件

属性组件

详见属性组件手册属性面板插件

列表组件

详见列表组件手册

树形组件

详见树组件手册

表格组件

详见表格组件手册

树表组件

详见树表组件手册

工具条组件

详见工具条手册

分割组件

详见分割组件手册

边框面板

详见边框面板手册

折叠组件

详见折叠组件手册

页签组件

详见页签组件手册

拓扑图形组件

拓扑图形组件ht.graph.GraphView(以下简称GraphView)是HT框架中2D功能最丰富的组件,其相关类库都在ht.graph包下。 GraphView具有基本图形的呈现和编辑功能,拓扑节点连线及自动布局功能,电力和电信等行业预定义对象,具有动画渲染等特效, 因此其应用面很广泛,可作为监控领域的绘图工具和人机界面,可作为一般性的图形化编辑工具,可扩展成工作流和组织图等企业应用。

缩放

改变zoom属性值(默认为1)可实现GraphView的缩放功能,以放大或缩小的方式查看拓扑图形组件的全貌或细节。 默认鼠标滚轮和平板上的双指头pinch手势可改变zoom的值。按空格键可重置zoom到默认值1 (该操作同时也重置了translateXtranslateY0)。

以下为缩放相关默认全局参数,可通过 htconfig 自定义:

以下为GraphView缩放相关函数,以下函数中的point参数代表进行缩放的中心基准, 一般会传入graphView.getLogicalPoint(event)的返回值,即已当前鼠标点为中心进行缩放, 该参数为空时则以当前可见矩形区域的中心进行缩放。

ajustZoom函数默认实现如下,对ht.Default上配置的最大值和最小值进行了限制:

adjustZoom = function(value){
    if(value < ht.Default.zoomMin){
        return ht.Default.zoomMin;
    }
    if(value > ht.Default.zoomMax){
        return ht.Default.zoomMax;
    }
    return value;                
}; 

交互

GraphView默认内置了一些交互器,以实现基本的选择、单双击、缩放、平移和编辑等交互功能,内置的交互器有:

可通过GraphView#setInteractors(list)组合这些交互器,用户也可以基于Interactor扩展自己的交互器, 以下代码为GraphView#setEditable(false/true)的实现,GraphView构造函数会调用setEditable(false), 因此默认只有基本的操作功能不具备编辑功能,需要编辑功能可调用setEditable(true)实现。

setEditable: function (editable) {
    var self = this;
    if (editable) {
        self.setInteractors([
            new ScrollBarInteractor(self),
            new SelectInteractor(self),
            new EditInteractor(self),
            new MoveInteractor(self),
              new DefaultInteractor(self),
            new TouchInteractor(self)
        ]);
    } else {
        self.setInteractors([
            new ScrollBarInteractor(self),
            new SelectInteractor(self),
            new MoveInteractor(self),
            new DefaultInteractor(self),
            new TouchInteractor(self, {editable: false})
        ]);
    }
},

TouchInteractor该类的第二个参数可传入json对象控制部分功能的开启和关闭,默认为开启

内置的Interactor在交互过程会派发出事件,可通过GraphView#addInteractorListener进行监听,简写为mi

graphView.addInteractorListener(function (e) {
    if(e.kind === 'clickData'){
        console.log(e.data + '被单击');
    }
    else if(e.kind === 'doubleClickData'){
        console.log(e.data + '被双击');
    }            
    else if(e.kind === 'clickBackground'){
        console.log('单击背景');
    }  
    else if(e.kind === 'doubleClickBackground'){
        console.log('双击背景');
    }     
    else if(e.kind === 'beginRectSelect'){
        console.log('开始框选图元');
    }              
    else if(e.kind === 'betweenRectSelect'){
        console.log('正在框选图元');
    }             
    else if(e.kind === 'endRectSelect'){
        console.log('结束框选图元');
    }           
    else if(e.kind === 'beginMove'){
        console.log('开始移动图元');
    }              
    else if(e.kind === 'betweenMove'){
        console.log('正在移动图元');
    }             
    else if(e.kind === 'endMove'){
        console.log('结束移动图元');
    } 
    else if(e.kind === 'beginPan'){
        console.log('开始手抓图平移');
    }              
    else if(e.kind === 'betweenPan'){
        console.log('正在手抓图平移');
    }             
    else if(e.kind === 'endPan'){
        console.log('结束手抓图平移');
    }     
    else if(e.kind === 'beginEditRect'){
        console.log('开始编辑图元大小和位置');
    }              
    else if(e.kind === 'betweenEditRect'){
        console.log('正在编辑图元大小和位置');
    }             
    else if(e.kind === 'endEditRect'){
        console.log('结束编辑图元大小和位置');
    } 
    else if(e.kind === 'beginEditPoint'){
        console.log('开始编辑多边形Shape或多点Edge的具体点');
    }              
    else if(e.kind === 'betweenEditPoint'){
        console.log('正在编辑多边形Shape或多点Edge的具体点');
    }             
    else if(e.kind === 'endEditPoint'){
        console.log('结束编辑多边形Shape或多点Edge的具体点');
    } 
    else if(e.kind === 'beginEditRotation'){
        console.log('开始旋转图元');
    }              
    else if(e.kind === 'betweenEditRotation'){
        console.log('正在旋转图元');
    }             
    else if(e.kind === 'endEditRotation'){
        console.log('结束旋转图元');
    }               
    else if(e.kind === 'moveLeft'){
        console.log('左方向键左移图元一个像素');
    }       
    else if(e.kind === 'moveRight'){
        console.log('右方向键右移图元一个像素');
    } 
    else if(e.kind === 'moveUp'){
        console.log('上方向键上移图元一个像素');
    } 
    else if(e.kind === 'moveDown'){
        console.log('下方向键下移图元一个像素');
    } 
    else if(e.kind === 'toggleNote'){
        console.log('切换note标注的展开合并');
    }             
    else if(e.kind === 'toggleNote2'){
        console.log('切换note2标注的展开合并');
    }
    else if(e.kind === 'beginEditPoints'){
        console.log('开始进入曲线的点编辑状态');
    }
    else if(e.kind === 'endEditPoints'){
        console.log('结束曲线的点编辑状态');
    } 
    else if(e.kind === 'hover'){
        console.log('鼠标停留');
    } 
    else if(e.kind === 'onClick'){
        console.log('单击图元');
    } 
    else if(e.kind === 'onDoubleClick'){
        console.log('双击图元');
    } 
    else if(e.kind === 'onContextMenu'){
        console.log('右击图元');
    } 
    else if(e.kind === 'onDown'){
        console.log('在图元处按下');
    } 
    else if(e.kind === 'onUp'){
        console.log('在图元处放开');
    } 
    else if(e.kind === 'onMove'){
        console.log('鼠标在图元上移动');
    } 
    else if(e.kind === 'onEnter'){
        console.log('鼠标进入图元');
    } 
    else if(e.kind === 'onHover'){
        console.log('鼠标在图元上悬停');
    } 
    else if(e.kind === 'onLeave'){
        console.log('鼠标离开图元');
    } 
    else if(e.kind === 'onBeginDrag'){
        console.log('图元开始拖拽');
    } 
    else if(e.kind === 'onDrag'){
        console.log('图元拖拽');
    } 
    else if(e.kind === 'onEndDrag'){
        console.log('图元结束拖拽');
    } 
    else if(e.kind === 'onScroll'){
        console.log('鼠标图元上滚动');
    } 
});

注意:后面的如onClickon开头的事件,必须设置data.s('interactive', true);来开启节点交互功能才会触发,也可以通过重载gv.isInteractive方法来定义节点可交互逻辑。 节点开启交互后,就会阻止图纸/场景上默认的交互行为,比如在交互节点上无法触发平移,可以使用data.s('preventDefaultWhenInteractive', false);来关闭这一机制,也可以通过重载 gv.preventDefaultWhenInteractive方法定义。

关于交互还有个逻辑坐标点(LogicalPoint)概念,可平移和缩放的组件一般都具有getLogicalPoint(event)函数, 根据交互事件返回相应坐标点信息,简单的理解逻辑坐标点和用户设置model的坐标是一致的,而真正显示在屏幕的坐标点, 需要通过zoomtranslate的转换,在GraphView改变zoomtranslate的过程,图元模型的数据并未改变, 也就是逻辑坐标值不变,仅仅是视图组件的呈现效果变化,DataModel中所有图元的逻辑坐标信息如positionpointswidthheight等保持不变,因此当自定义交互事件处理时,常需要调用以下交互相关的屏幕坐标和逻辑坐标转换函数:

GraphView上有一系列on*类型的回调函数,可重载做后续处理或改变默认实现逻辑:

除了调用GraphView封装的函数外,用户也可以直接添加原生的html组件监听事件,如Node章节的例子, 通过graphView.getView().addEventListener直接对底层div添加监听,以下代码有几点需要注意:

在前面提到的HT封装的onDataClickedon**Clicked等事件,都是在mousedowntouchstart时触发, 如果需要监听在mouseuptouchend的放手后事件处理,可通过对html原生的事件进行监听, 应用较多的是在点击图元需要弹开对话框的情况,如果直接在HT封装的on*Clicked事件处理内直接调用弹出对话框之类操作, 会影响HT后续的交互事件处理,所以交互事件内影响界面的功能,可选择监听在mouseuptouchend里面处理, 有些情况下甚至需要再调用ht.Default.callLater(function(){})的方式进行处理:

var eventType = ht.Default.isTouchable ? 'touchend' : 'mouseup';
graphView.getView().addEventListener(eventType, function(e){
    var data = graphView.getDataAt(e);
    if(data && ht.Default.isDoubleClick(e)){
        alert(data.getName() + ' is double clicked.');
    }
});

以下示例自定义了创建NodeEdgeShape的三种继承于ht.graph.Interactor交互器:

过滤

过滤机制贯穿HT框架,而GraphView对过滤机制的运用尤为集中,合理运用这些过滤器可以灵活控制是否允许图元可见、移动、编辑等逻辑。

isMovable: function (data) {
    if(data instanceof ht.Edge){
        return false;                
    }
    return this._movableFunc ? this._movableFunc(data) : true;
},

以上代码是GraphView.isMovable(data)函数的简化版,MoveInteractor类在处理拖动时会调用此函数来决定图元是否可移动, this._movableFunc属性是通过GraphView.setMovableFunc(func)设置的,由代码可知默认情况下Edge是不允许拖动的, 然后判断是否设置过movableFunc函数属性,如果设置了则以该函数的逻辑决定是否可移动,最后才返回true

因此要自定义可否移动逻辑,有两个途径:

* 设置`GraphView.setMovableFunc(func)`函数属性。
* 重载`GraphView.isMovable(data)`,这种方式客户需要考虑原始`isMovable`的实现逻辑。

下面列表是常见的过滤器函数:

不少刚开始使用HT的用户对过滤器设置在视图组件上而不是数据模型上不解,如果将过滤器控制在模型上, 则所有共享同一模型的组件只能具有相同的过滤逻辑,HT这样的设计思想能使得不同的组件具有不同的过滤逻辑。

除在视图组件上设置过滤器外,GraphView的内置过滤机制也参考了以下style属性,用户可直接改变以下style达到对单个图元的控制效果:

同样对于Graph3dView也有类似的控制参数

style

HT的数据Data可分为三种属性类型:

以下代码为HT内部对style相关函数实现,默认图元的_styleMap为空,查询值时会参考ht.Style的全局默认值:

getStyleMap: function(){
    return this._styleMap;
},
getStyle: function (name, checkDefault) {
    if (checkDefault === undefined) {
        checkDefault = true;
    }
    var value = this._styleMap ? this._styleMap[name] : undefined;
    if (value === undefined && checkDefault) {
        return ht.Style[name];
    } else {
        return value;
    }
},
setStyle: function (name, newValue) {        
    if (!this._styleMap) {
        this._styleMap = {};
    }
    var oldValue = this._styleMap[name];
    if(newValue === undefined){
        delete this._styleMap[name];               
    }else{
        this._styleMap[name] = newValue;               
    }        
    if (this.fp('s:' + name, oldValue, newValue)) {
        this.onStyleChanged(name, oldValue, newValue);
    }
},

以下为部分style属性说明,更多属性由后续章节介绍:

select

GraphView的图元被选中时默认会显示一个选中边框,选中边框的效果可以通过style上的select.*相关属性控制。

border

boder样式在图元的边缘绘制边框效果,用于告警或提示的作用,可通过重载GraphView.getBorderColor(data)函数自定义。

shape

GraphView上的Node图元除了通过image属性设置显示为图片外,还可以设置为HT框架内置的各种多边形类型进行矢量绘制。 要显示成矢量格式可通过设置Node类型图元的style属性实现,相关style属性名说明如下:

position

详见位置手册

label

GraphView的图元可以增加文字说明,如对图元setName('hello HT'),则图元下方将显示该hello HT文字。 上章position例子文字信息并非通过setName设置, 而是通过setStyle('label', 'hello HT');的方式设置,最终显示文字由GraphView.getLabel函数决定:

getLabel: function (data) {
    var label = data.getStyle('label');
    return label === undefined ? data.getName() : label;        
},

通过以上代码可知,style上的label属性优先级高于name属性,可重载GraphView.getLabel函数改变文字获取逻辑。

*View.getLabel的设计方式贯穿HT的所有组件,在ht.widget.*包下的ListViewTreeViewTabView等组件都采用类似的方式, 与GraphView有所不同的是,其他组件默认的实现逻辑都是返回data.toLabel()值,以下是Data#toLabel()的默认实现。

toLabel: function(){
    return this._displayName || this._name;
}  

通过以上代码可知,displayName属性的优先级高于name属性,且style属性一般只用于GraphView组件, 其他组件并不会考虑style上的label属性,当TreeGraphView共享同一DataModel数据模型时, 需要在TreeGraphView上分别显示不一样的文字,这种情况下就可以通过设置style上的label属性, 或者设置displayName属性即可达到不一样的效果,也可以直接重载组件的getLabel函数自定义逻辑。

HT默认除了label.*的属性外,还提供了label2.*的属性,用于满足一个图元需要显示双文字的情况, label2label的属性意义是一一对应的:

note

note一般作为图元的标注,有提示和警告的作用,以冒泡的形式呈现,也可缩小合并成一个小标注。 note的参数通过stylenote.*相关属性控制,与label一样, 为了满足一个图元双标注的需求,提供了note2.*第二个标注参数:

icon

iconnote类似,展示在图元周围,有提示和警告的作用,不同的是note展示文字,而icon展示图片或 矢量

以上为ht.Data提供的操作icon的函数,这两个方法实际修改了styleicons属性, 用户也可以通过setStyle('icons', icons)getStyle('icons')设置和获取icons

addStyleIcon方法第二个参数iconsjson格式对象,其属性含义如下:

edge.addStyleIcon("flags", {
    position: 17,
    direction: 'east',
    offsetX: -26,
    gap: 10,
    names: ['china', 'spain', 'usa']
});

edge.addStyleIcon("arrow1", {
    position: 2,
    width: 50,
    height: 25,
    keepOrien: true,
    names: ['arrow']
});

edge.addStyleIcon("arrow2", {
    position: 4,
    width: 50,
    height: 25,
    positionFixed: true,
    names: ['arrow']
}); 

以上示例中edge对象通过addStyleIcon的方式,设置一排由['china', 'spain', 'usa']组合的三个国旗图标, 以及两个arrow的矢量图标,以上代码可用以下代码替代:

edge.setStyle('icons', {
    flags: {
        position: 17,
        direction: 'east',
        offsetX: -26,
        gap: 10,
        names: ['china', 'spain', 'usa']
    },
    arrow1: {
        position: 2,
        width: 50,
        height: 25,
        keepOrien: true,
        names: ['arrow']
    },
    arrow2: {
        position: 4,
        width: 50,
        height: 25,
        positionFixed: true,
        names: ['arrow']
    }
});

示例中node1通过node1.setStyle("icons", ...)注册了三个矢量圆球,通过重载graphView.onDataClicked, 并结合graphView.getIconInfoAt函数能得到点击的具体icon图标信息,进而改变连线箭头颜色

graphView.onDataClicked = function(data, e) {
    if(data === node1){
        var info = this.getIconInfoAt(e, data);
        if(info){
            edge.a('arrow.color', info.name.comps[0].background);
            node1.a('select.index', info.index);
        } 
    }                                       
};

group

Group组类型图元可设置如下样式:

edge

对于edge.typepoints类型的连线,当edge.centerfalse,且edge.offset0时,连线起始和结束线段将根据关联节点的矩形边缘进行裁剪。

推荐

书籍

Object-Oriented JavaScript
开发HTML5应用,特别是企业应用,JavaScript几乎占据绝大部分代码量,所以需要掌握js语法,内置的类和函数, 与其他面向对象语言不同的类继承模式,以及DOM的基本操作,对于这些方面知识此书是不错的选择。

Pro JavaScript Design Patterns
设计模式已经在各种软件开发中被广泛运用,但是很多耳熟能详的设计模式,用js语言的实现方式与JavaC#等传统面对对象语言的实现方式还是有很多差异, 导致很多初学者往往无从下手,或者设计出来的模式不是最佳的js实践,此书对接口、单类、继承、工厂等模式给出了js语言的可选择实现方式。

CSS3: The Missing Manual
HT产品提供了丰富的企业应用组件,做出一个常规的案例仅用js也足够,但更美观的界面效果离不开CSS技术的掌握和运用, 进行HT产品深度定制扩展很多时候也是离不开CSS技术。此书的第一二版就好评如潮,第三版更是针对HTML5进行了改版, 删除了以前针对老版IE浏览器的章节,增加了针对CSS3新特性的章节篇幅,是一本非常值得前端程序员通读的经典读物。

Responsive Web Design with HTML5 and CSS3
Responsive Web Design(RWD)是前端技术的新领域,最早提出“响应式Web设计”概念的鼻祖文章是 Responsive Web Design。 一个优秀的页面不仅要在PC浏览器下良好呈现,也需要考虑用户采用手机或平板进行浏览时的呈现效果, 因此程序员和设计师需要考虑不同屏幕尺寸等复杂情况,保持桌面鼠标以及移动设备手势操作都有较好的用户体验, 此书起了抛砖引玉的作用,可以让大家了解目前常见的RWD的设计技巧,开始对这方面技术进行关注和运用。

订阅

系统的学习一门技术我们还是推荐阅读书籍,但是写出一本优秀经典的书籍一般需要几个月甚至几年时间, 对于紧跟前沿的程序员还需要一定的途径获取最新的技术资讯,在此强烈推荐三处针对前端技术开发的周刊订阅, 每周只需看看这三封周刊邮件,基本不会错过地球上前端开发领域正在发生的有趣事件:


欢迎交流 service@hightopo.com