使用jsPlumb制作流程图设计器
jsPlumb是一个比较强大的绘图组件,它提供了一种方法,主要用于连接网页上的元素。在现代浏览器中,它使用SVG或者Canvas技术,而对于IE8以下(含IE8)的古董浏览器,则使用VML技术。
项目主页:http://jsplumbtoolkit.com/
GitHub:https://github.com/sporritt/jsPlumb
作为插件,主要支持jQuery/MooTools/YUI3三种js库,目前最新版本为1.4.1。其中作为jQuery的插件需要用到jQuery、jQuery UI,建议使用最新版本的库避免一些bug。
本文主要使用jQuery 1.9.0、jQuery UI 1.9.2、jsPlumb 1.4.1来绘制流程图。
资源准备
下载jsPlumb,用到以下几个文件:
- build/js/jquery.jsPlumb-1.4.1-all.min.js
- build/lib/jquery-1.9.0-min.js
- build/lib/jquery-ui-1.9.2-min.js
- build/lib/jquery.ui.touch-punch.min.js (可选) 用于触摸支持
以及build/demo/js/demo-helper-jquery.js,主要用于绘图模式的切换,调整为如下代码:
jsPlumb.bind("ready", function() {
// chrome fix.
document.onselectstart = function() { return false; };
// render mode
var resetRenderMode = function(desiredMode) {
var newMode = jsPlumb.setRenderMode(desiredMode);
$(".rmode").removeClass("selected");
$(".rmode[mode='" + newMode + "']").addClass("selected");
$(".rmode[mode='canvas']").attr("disabled", !jsPlumb.isCanvasAvailable());
$(".rmode[mode='svg']").attr("disabled", !jsPlumb.isSVGAvailable());
$(".rmode[mode='vml']").attr("disabled", !jsPlumb.isVMLAvailable());
nodeFlow.init();
};
$(".rmode").bind("click", function() {
var desiredMode = $(this).attr("mode");
if (jsPlumbDemo.reset) jsPlumbDemo.reset();
jsPlumb.reset();
resetRenderMode(desiredMode);
});
resetRenderMode(jsPlumb.SVG);
});
再准备css样式(从flowchartDemo.css调整而来):
.node { border: 1px solid #346789; box-shadow: 2px 2px 19px #aaa; -o-box-shadow: 2px 2px 19px #aaa; -webkit-box-shadow: 2px 2px 19px #aaa; -moz-box-shadow: 2px 2px 19px #aaa; -moz-border-radius: 0.5em; border-radius: 0.5em; opacity: 0.8; filter: alpha(opacity=80); width: 7em; height: 5em; line-height: 5em; text-align: center; z-index: 20; position: absolute; background-color: #eeeeef; color: black; font-family: helvetica; padding: 0.5em; font-size: 1em; }
.node:hover { box-shadow: 2px 2px 19px #444; -o-box-shadow: 2px 2px 19px #444; -webkit-box-shadow: 2px 2px 19px #444; -moz-box-shadow: 2px 2px 19px #444; opacity: 0.8; filter: alpha(opacity=80); } ._jsPlumb_connector { z-index: 4; }
._jsPlumb_endpoint { z-index: 21; cursor: pointer; }
._jsPlumb_dragging { z-index: 4000; } .dragHover { border: 1px dotted red; } .aLabel { background-color: white; padding: 0.4em; font: 12px sans-serif; color: #444; z-index: 21; border: 1px dotted gray; opacity: 0.8; filter: alpha(opacity=80); } .ep { position: absolute; right: 5px; top: 5px; width: 1em; height: 1em; background-color: #994466; cursor: pointer; }
最终引入的资源如下:
<script src='js/jquery-1.9.0.min.js'></script>
<script src='js/jquery-ui-1.9.2.min.js'>
<link href="css/demo.css" rel="stylesheet" />
<script src="js/jquery.jsPlumb-1.4.1-all-min.js"></script>
<script src="js/jquery.ui.touch-punch.min.js"></script>
<script src="js/demo.init.js"></script>
<script src="js/demo-helper-jquery.js"></script>
主要实现
参照例子中的Flowchart以及State Machine,实现如下:
; (function() {
window.nodeFlow = {
init: function() {
// 设置点、线的默认样式
jsPlumb.importDefaults({
DragOptions: { cursor: 'pointer', zIndex: 2000 },
Endpoint: ["Dot", { radius: 1 }],
HoverPaintStyle: { strokeStyle: "#42a62c", lineWidth: 2 },
ConnectionOverlays: [
["Arrow", { location: -7, id: "arrow", length: 14, foldback: 0.8 }],
["Label", { location: 0.1, id: "label" }]
]
});
// 连接事件
jsPlumb.bind("jsPlumbConnection", function(conn, originalEvent) {
if (conn.connection.sourceId == conn.connection.targetId) {
jsPlumb.detach(conn);
alert("不能连接自己!");
}
$.each(jsPlumb.getEndpoints(conn.source), function(i, el) {
if (conn.connection != el.connections[0] &&
(el.connections[0].targetId == conn.targetId || (el.connections[0].sourceId == conn.targetId && el.connections[0].targetId == conn.sourceId))) {
jsPlumb.detach(conn);
alert("不能重复连接!");
return false;
}
}); nodeFlow.onConnectionChange && nodeFlow.onConnectionChange(conn);
conn.connection.bind("editCompleted", function(o) {
if (typeof console != "undefined")
console.log("connection edited. path is now ", o.path);
});
});
// 取消连接事件
jsPlumb.bind("jsPlumbConnectionDetached", function(conn) {
nodeFlow.onConnectionChange && nodeFlow.onConnectionChange(conn);
});
// 双击取消连接
jsPlumb.bind("dblclick", function(conn, originalEvent) {
jsPlumb.detach(conn);
});
// 连接的元素
// 本例中.node既是源头又是目标
var nodeList = $(".node");
nodeList.each(function(i, e) {
// 设置连接的源元素
jsPlumb.makeSource($(e), {
filter: ".ep", // .ep元素用于拖动连接
anchor: "Continuous",
connector: ["Flowchart", { curviness: 20 }], // 连接的方式为流程图
connectorStyle: { strokeStyle: "#014ae1", lineWidth: 2 },
maxConnections: -1 // 最大连接数不限
});
});
// 设置连接目标
jsPlumb.makeTarget(nodeList, {
dropOptions: { hoverClass: "dragHover" },
anchor: "Continuous"
});
// 初始化所有连接元素为可拖动
jsPlumb.draggable(nodeList);
}
};
})();
保存及载入状态
创建如下html结构作为测试:
<asp:HiddenField runat="server" ID="connections" /><!--保存连接-->
<asp:HiddenField runat="server" ID="locations" /><!--保存元素位置-->
<div class="nodeWrapper" style="height:100%;">
<div class="node" id='node1' data-id="1">
<div class="ep"></div>
<strong>节点1</strong>
</div>
<div class="node" id='node1' data-id="1">
<div class="ep"></div>
<strong>节点1</strong>
</div>
<div class="node" id='node2' data-id="2">
<div class="ep"></div>
<strong>节点2</strong>
</div>
<div class="node" id='node3' data-id="3">
<div class="ep"></div>
<strong>节点3</strong>
</div>
</div>
在连接状态改变、表单提交时保存连接数据:
// 连接改变时把所有的节点位置、连接以JSON格式存入到隐藏域中
nodeFlow.onConnectionChange = function() {
var connections = [], locations = [], conns = jsPlumb.getAllConnections();
$.each(conns, function(scopeName, scopeConnections) {
$.each(scopeConnections, function(i, el) {
locations.push($.extend(el.source.offset(), { nodeId: el.source.data("id") }));
locations.push($.extend(el.target.offset(), { nodeId: el.target.data("id") }));
connections.push({ source: el.source.data("id"), target: el.target.data("id") });
});
});
$("input[id$=connections]").val(JSON.stringify(connections));
$("input[id$=locations]").val(JSON.stringify(locations));
};
// 提交表单时更新连接数据
$(":submit").click(nodeFlow.onConnectionChange);
通过以上代码,即可以在表单提交时把流程图的状态保存到数据库。
载入数据
调整html代码如下:
<div class="nodeWrapper" style="height:100%;">
<asp:Repeater runat="server" ID="nodeList">
<ItemTemplate>
<div class="node" id='node<%#Eval("nodeId") %>' data-id="<%#Eval("nodeId") %>" style="<%#GetLocation((int)Eval("nodeId"))%>">
<div class="ep"></div>
<strong><%#Eval("nodeName") %></strong>
</div>
</ItemTemplate>
</asp:Repeater>
</div>
从数据库获取节点、位置、连接数据:
/// <summary>
/// 节点信息
/// </summary>
public class NodeItem
{
public int NodeId { get; set; }
public string NodeName { get; set; }
}
/// <summary>
/// 节点位置信息
/// </summary>
public class NodeLocation
{
public int NodeId { get; set; }
public double Left { get; set; }
public double Top { get; set; }
}
/// <summary>
/// 节点连接信息
/// </summary>
public class NodeConnection
{
public int Source { get; set; }
public int Target { get; set; }
} List<NodeLocation> locationData; protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var nodeData = new List<NodeItem>
{
new NodeItem{NodeId=1, NodeName="节点1"},
new NodeItem{NodeId=2, NodeName="节点2"},
new NodeItem{NodeId=3, NodeName="节点3"}
};
nodeList.DataSource = nodeData;
nodeList.DataBind(); // 从数据库获取位置以及连接
locationData = JsonConvert.DeserializeObject<List<NodeLocation>>(locationString);
var connectionData = JsonConvert.DeserializeObject<List<NodeConnection>>(connectionString); // 连接所有节点
var builder = new StringBuilder();
builder.Append("jsPlumb.bind(\"ready\", function() {");
connectionData.ForEach(c =>
{
builder.AppendFormat("jsPlumb.connect({{source: 'node{0}', target: 'node{1}'}});", c.Source.ToString(), c.Target.ToString());
});
builder.Append("});");
}
} /// <summary>
/// 获取位置
/// </summary>
protected string GetLocation(int nodeId)
{
var ll = locationData.FirstOrDefault(l => l.NodeId == nodeId);
if (ll != null)
return "left:" + ll.Left.ToString() + "px;top:" + ll.Top.ToString() + "px;";
return string.Empty;
}
其中,每次载入时,都需要获取所有连接的数据,并通过脚本把所有节点连接起来。
结尾
以上功能只用到jsPlumb少量API,实现起来都比较简单。更多的功能参考官方文档及API文档进行扩展。
在使用jsPlumb之前也看过一些其他的js组件:
- Raphaël:绘图功能非常强大,但是没有提供更直接的例子,需要花费较多的开发时间
- D3:理由同上
- JointJS:对IE支持不好
- JavaScript InfoVis Toolkit:操作起来比较复杂,表现形式较少
- jQuery OrgChart:只能用来呈现组织结构图,不能进行编辑
- jssvggraph:只有呈现不具备编辑功能
- JS Flowchart:操作复杂,界面不够现代
- ternlight:缺乏API文档
- Strawberry:国人开发的,运行效果似乎不太良好(ie10)
- Diagramo:功能强大、操作不简单,而且使用php
- WireIt:demo太过简单,而且在ie下表现不佳
- jGraphUI:操作不够简单$10
- creately:商业软件
- mxGraph:商业软件,价格高昂$5000+
- MindFusion:商业软件$300+
- Draw2D touch:商业软件499 €
- yworks:商业软件$5000+
- GoJS HTML5 Canvas:商业软件$2795+
使用jsPlumb制作流程图设计器的更多相关文章
- C#如何实现一个简单的流程图设计器
以前看过不少Window Form开发的流程图设计器,支持节点拖放,非常方便即可设计出很美观的流程图,作为一个程序员,对其内部实现原理一直很好奇,感叹有朝一日自己如果可以开发一款类似的软件那是多么让人 ...
- jsPlumb开发流程设计器
前言 jsPlumb是一款开源软件,但jsPlumb toolkit是收费的. 本文主要使用jsPlumb实现一些简单的流程设计功能. 基础学习 首先引入jsplumb.min.js. <scr ...
- JS组件系列——JsPlumb制作流程图及相关效果详解
上 篇 前言:之前项目里面用到了Web里面的拖拽流程图的技术JsPlumb,其实真不算难,不过项目里面用HTML做的一些类似flash的效果,感觉还不错,在此分享下. Jsplumb官网:htt ...
- 基于jQuery的web在线流程图设计器GooFlow
简易的流程图设计控件,效果图: JavaScript源文件在GooFlow.js中,样式文件是GooFlow2.css.可以自定义样式. GooFlow_item类是每个项的样式属性. 但估计实现任务 ...
- C# 报表设计器 (winform 设计端)开发与实现生成网页的HTML报表
记得2010年之前,公司的项目基本上都要用到报表,以前我们常用的方法就是针对客户的需求来定制化开发(基本上是死写代码)来实现,经常导致项目经常性的延期,因为客户的需求经常会变化,随着用户的使用认知度的 ...
- 【转】Silverlight全开源工作流设计器
声明 此工作流是作者自行构思和设计的被动式数据触发模式的工作流.没有遵循各种现有的工作流设计标准(如WFMC或WSFL),也没有与其他工作流通用性的接口规范.这里体现更多的是作者对工作流的使用思想,及 ...
- .NET 开源工作流: Slickflow流程引擎高级开发(十) -- BpmnJS流程设计器集成
前言: 在Slickflow产品开发过程中,前端流程设计器经历了几个不同的版本(jsPlumb, mxGraph等),目的是为了在设计流程时的用户体验更加良好,得到客户的好评和认可.BpmnJS流程设 ...
- 解析大型.NET ERP系统核心组件 查询设计器 报表设计器 窗体设计器 工作流设计器 任务计划设计器
企业管理软件包含一些公共的组件,这些基础的组件在每个新项目立项阶段就必须考虑.核心的稳定不变功能,方便系统开发与维护,也为系统二次开发提供了诸多便利.比如通用权限管理系统,通用附件管理,通用查询等组件 ...
- BIRT使用1:简介、概念、元素、报表设计器组成
前一篇博客对birt进行了一个初探,相信通过上篇博客大家对birt有个初步认识,接下来我们随着下面这张思维导图的展示,进入birt的使用学习. 这一篇博客是第一部分,主要介绍一下birt的简介.概念. ...
随机推荐
- Oracle命令:授权-收回权限-角色
Oracle命令:授权-收回权限-角色 oracle grant 不论授予何种权限,每条授权(grant)语句总是由三部分组成: 1) 接受者部分是准备获得权限的一个或多个用户的列表. 2)关键字权限 ...
- Docker私有仓库 Registry中的镜像管理
这里主要介绍Registry v2的版本 查看Registry仓库中现有的镜像: # curl -XGET http://10.0.30.6:5000/v2/_catalog# curl -XGET ...
- Node.js 的初步理解
Node.js 是一个采用C++语言编写的后端的 Javascript 的运行环境, 它使用了 google 的 V8虚拟机来解释和执行代码.Node.js 的有许多有用的内置的模块,比如 http, ...
- node.js入门及express.js框架
node.js介绍 javascript原本只是用来处理前端,Node使得javascript编写服务端程序成为可能.于是前端开发者也可以借此轻松进入后端开发领域.Node是基于Google的V8引擎 ...
- servletconfig和servletContext的区别
1.servletConfig: 在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数.(配置在某个servlet标签或者整个we ...
- globals()
[globals() ] globals() Return a dictionary representing the current global symbol table. This is alw ...
- oracle中rownum和rowid的区别
rownum和rowid的区别总括: rownum和rowid都是伪列,但是两者的根本是不同的. rownum是根据sql查询出的结果给每行分配一个逻辑编号,所以你的sql不同也就会导致最终rownu ...
- ArcGIS删除部分数据后全图范围不正确
我有一个全国地图的图层,现在删除图层中其他省份,只保留山东省的图形,但是点击全图后,全图范围仍然是全国地图时候的全图范围,使用的版本是ArcGIS9.3,数据存放在9.3的个人数据库中(Perso ...
- 第一章 zookeeper基础概念
1.ZooKeeper是什么 ZooKeeper为分布式应用提供了高效且可靠的分布式协调服务,提供了统一命名服务. 配置管理和分布式锁等分布式的基础服务.在解决分布式数据一致性方面, ZooKeepe ...
- Odoo10 变化
官方在 https://www.odoo.com/forum/help-1/question/fyi-what-has-odoo-r-d-been-working-on-lately-106945 发 ...