制作流程图用到的相关的脚本:
1
2
3
jsPlumb-1.6.2-min.js在官网上下载,这里用得是最新版本。jquery-1.11.1.min.js等脚本百度上都能找到,这里就不多说了。
css样式在官网里也可以搜到,这里我就贴出来。
1 .node {
2 box-shadow: 2px 2px 19px #aaa;
3 -o-box-shadow: 2px 2px 19px #aaa;
4 -webkit-box-shadow: 2px 2px 19px #aaa;
5 -moz-box-shadow: 2px 2px 19px #aaa;
6 -moz-border-radius: 0.5em;
7 border-radius: 0.5em;
8 opacity: 0.8;
9 filter: alpha(opacity=80);
10 border: 1px solid #346789;
11 width: 150px;
12 /*line-height: 40px;*/
13 text-align: center;
14 z-index: 20;
15 position: absolute;
16 background-color: #eeeeef;
17 color: black;
18 padding: 10px;
19 font-size: 9pt;
20 cursor: pointer;
21 height: 50px;
22 line-height: 50px;
23 }
24 .radius {
25 border-radius: 25em;
26 }
27 .node:hover {
28 box-shadow: 2px 2px 19px #444;
29 -o-box-shadow: 2px 2px 19px #444;
30 -webkit-box-shadow: 2px 2px 19px #444;
31 -moz-box-shadow: 2px 2px 19px #444;
32 opacity: 0.8;
33 filter: alpha(opacity=80);
34 }
这里还有提到一点,jsPlumb官网上的api全是英文的,博主我从小英文就不好,所以看里面的doc非常费劲,一般都是一边开着金山翻译,
一边看着文档,英语好的略过这段。
言归正传,现在开始我们的jsPlumb流程图制作,下面先附上流程图。
根据客户的要求,我们要完成的功能点有以下几点:
1.支持将左边的div层复制拖拉到右边中间的层,并且左边同一个div拖拉没有次数限制,如果只能拖拉一次,做这个东西就没有什么意义了。
2.拖拉到中间的div层可以拖动,拖动不能超过中间div的边框。
3.拖动到中间的层,四周能有4个endpoint点,可供客户连线。
4.能支持删除多余的div的功能。
5.支持删除连接线。
6.能双击修改流程图的文字。
7.能序列化保存流程图。
下面我们根据功能开始制作:
1.拖拉jsPlumb其实是提供draggable方法,和droppable方法官网里有介绍, 但是我这里用得是jquery里的draggable()和droppable()。
1 "left">
2 class="node radius" id="node1">开始
3 class="node" id="node2">流程
4 class="node" id="node3">判断
5 class="node radius" id="node4">结束
6
7
8 "right">
9 拖拉到此区域
10
11 "save">
12 "button" value="保存" οnclick="save()" />
13
1 $("#left").children().draggable({
2 helper: "clone",
3 scope: "ss",
4 });
helper:"clone"表示复制,scope:"ss"是一个标识为了判断是否可以放置,主要用于droppable方法里面也设置这个标识来判断拖放到的地方,
除非两个都不写scope,可以随便拖放,但是会有一个问题,每次我从左边拖东西到右边,我再拖到的时候就会有div拖到不了,所以最好设置
scope:"//里面的值随便,只是一个标识"。
下面是完整的拖放:
1 $("#left").children().draggable({
2 helper: "clone",
3 scope: "ss",
4 });
5 $("#right").droppable({
6 scope: "ss",
7 drop: function (event, ui) {
8 var left = parseInt(ui.offset.left - $(this).offset().left);
9 var top = parseInt(ui.offset.top - $(this).offset().top);
10 var name = ui.draggable[0].id;
11 switch (name) {
12 case "node1":
13 i++;
14 var id = "state_start" + i;
15 $(this).append('<div class="node" style="border-radius: 25em" id="' + id + '" >' + $(ui.helper).html() + 'div>');
16 $("#" + id).css("left", left).css("top", top);
17 jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
18 jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
19 jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
20 jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
21 jsPlumb.draggable(id);
22 $("#" + id).draggable({ containment: "parent" });
23 doubleclick("#" + id);
24 break;
25 case "node2":
26 i++;
27 id = "state_flow" + i;
28 $(this).append("<div class='node' id='" + id + "'>" + $(ui.helper).html() + "div>");
29 $("#" + id).css("left", left).css("top", top);
30 jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
31 jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
32 jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
33 jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
34 jsPlumb.addEndpoint(id, hollowCircle);
35 jsPlumb.draggable(id);
36 $("#" + id).draggable({ containment: "parent" });
37 doubleclick("#" + id);
38 break;
39 case "node3":
40 i++;
41 id = "state_decide" + i;
42 $(this).append("<div class='node' id='" + id + "'>" + $(ui.helper).html() + "div>");
43 $("#" + id).css("left", left).css("top", top);
44 jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
45 jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
46 jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
47 jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
48 jsPlumb.addEndpoint(id, hollowCircle);
49 jsPlumb.draggable(id);
50 $("#" + id).draggable({ containment: "parent" });
51 doubleclick("#" + id);
52 break;
53 case "node4":
54 i++;
55 id = "state_end" + i;
56 $(this).append('<div class="node" style="border-radius: 25em" id="' + id + '" >' + $(ui.helper).html() + 'div>');
57 $("#" + id).css("left", left).css("top", top);
58 jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
59 jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
60 jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
61 jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
62 jsPlumb.draggable(id);
63 $("#" + id).draggable({ containment: "parent" });
64 doubleclick("#" + id);
65 break;
66 }
67 }
68 });
怎么样把左边的层复制到右边的层,我的做法是这样的:
1 $(this).append('<div class="node" style="border-radius: 25em" id="' + id + '" >' + $(ui.helper).html() + 'div>');
做到这里会有人奇怪,怎么做到左边能拉无数次append到右边,id这样不会冲突吗?我就在外面var i=0; 当有元素拖放到右边的div时,i++;
然后var id="state_start"+i;拼接起来,这样你的id就不会一样了。
然后再设置div的left和top:
drop: function (event, ui) {
var left = parseInt(ui.offset.left - $(this).offset().left);
var top = parseInt(ui.offset.top - $(this).offset().top);
$("#" + id).css("left", left).css("top", top);
2.拖拉到中间的div层可以拖动,拖动不能超过中间div的边框:
jsPlumb.draggable(id);
$("#" + id).draggable({ containment: "parent" });
3.拖动到中间的层,四周能有4个endpoint点,可供客户连线:
这个功能是本文的重点,如何通过jsPlumb初始化端点和构造端点(endpoint)。
3.1 初始化端点样式设置:主要设置一些基本的端点,连接线的样式,里面的属性不设置,默认使用默认值
1 //基本连接线样式
2 var connectorPaintStyle = {
3 lineWidth: 4,
4 strokeStyle: "#61B7CF",
5 joinstyle: "round",
6 outlineColor: "white",
7 outlineWidth: 2
8 };
9 // 鼠标悬浮在连接线上的样式
10 var connectorHoverStyle = {
11 lineWidth: 4,
12 strokeStyle: "#216477",
13 outlineWidth: 2,
14 outlineColor: "white"
15 };
16 var hollowCircle = {
17 endpoint: ["Dot", { radius: 8 }], //端点的形状
18 connectorStyle: connectorPaintStyle,//连接线的颜色,大小样式
19 connectorHoverStyle: connectorHoverStyle,
20 paintStyle: {
21 strokeStyle: "#1e8151",
22 fillStyle: "transparent",
23 radius: 2,
24 lineWidth: 2
25 }, //端点的颜色样式
26 //anchor: "AutoDefault",
27 isSource: true, //是否可以拖动(作为连线起点)
28 connector: ["Flowchart", { stub: [40, 60], gap: 10, cornerRadius: 5, alwaysRespectStubs: true }], //连接线的样式种类有[Bezier],[Flowchart],[StateMachine ],[Straight ]
29 isTarget: true, //是否可以放置(连线终点)
30 maxConnections: -1, // 设置连接点最多可以连接几条线
31 connectorOverlays: [["Arrow", { width: 10, length: 10, location: 1 }]]
32 };
3.2 构造端点(endpoint):怎样将端点添加到div的四周?
1 jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
2 jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
3 jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
4 jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
通过jsPlumb.addEndpoint(a,b,c)里面有三个参数,a:要添加端点的div的id;b:设置端点放置的位置("TopCenter","RightMiddle","BottomCenter","LeftMiddle")
四个初始位置;c:端点和连接线的样式。b,c(可选).
添加多个端点:jsPlumb.addEndpoints(a,b,c)三个参数 c(可选),a:要添加端点的div的id;b:含端点的构造函数参数的对象列表;
举个例子:
4.支持删除多余的div的功能:
有时候拖拉div经常会发生拖多了等问题,所有需要删除功能。我要做的删除效果是:鼠标放到div上面,div的右上角会出现一个红色的删除图标,鼠标移走就消失。如下图:
我是通过以下代码实现的:
1 $("#right").on("mouseenter", ".node", function () {
2 $(this).append('<img src="../../resources/images/close2.png" style="position: absolute;" />');
3 if ($(this).text() == "开始" || $(this).text() == "结束") {
4 $("img").css("left", 158).css("top", 0);
5 } else {
6 $("img").css("left", 158).css("top", -10);
7 }
8 });
9 $("#right").on("mouseleave", ".node", function () {
10 $("img").remove();
11 });
我想在这里大家都有疑问吧,为什么用on()事件委托。因为是后添加进来的元素,前面页面已经完成了初始化,所以你用$("img")根本找不到这个元素,
因为img是在页面初始化后,才添加的元素。这里就提到了live()为什么不用这个,jquery1.7.2才有这个方法,这里用的是jquery1.11.1 已经没有live()方法了,
取而代之的是on()方法。(live()有许多缺点,所以在新的版本被摒弃了)
后面删除比较简单:
1 $("#right").on("click", "img",function () {
2 if (confirm("确定要删除吗?")) {
3 jsPlumb.removeAllEndpoints($(this).parent().attr("id"));
4 $(this).parent().remove();
5
6 }
7 });
注明:这里我遇到一个问题,你删除了那个div,你还得把它周围的4个端点(endpoint)删除,这个问题刚开始我想了很多,一直没做出来,后来去jsPlumb官网查看相关的资料,
发现jsPlumb提供一个方法能删除div四周的端点。方法如下:
jsPlumb.removeAllEndpoints($(this).parent().attr("id"));//删除指定id的所有端点
5.支持删除连接线:
1 jsPlumb.bind("click", function (conn, originalEvent) {
2 if (confirm("确定删除吗? "))
3 jsPlumb.detach(conn);
4 });
6. 能双击修改流程图的文字:
1 function doubleclick(id) {
2 $(id).dblclick(function () {
3 var text = $(this).text();
4 $(this).html("");
5 $(this).append("<input type='text' value='" + text + "' />");
6 $(this).mouseleave(function () {
7 $(this).html($("input[type='text']").val());
8 });
9 });
10 }
7.能序列化保存流程图:
我的思路是这样的,将中间div里所有的"流程图div信息和连接线两端的信息"保存到数组里,然后序列化成json数据,通过ajax传到asp.net 后台,将json写入到txt文档里保存到服务器端。
(其实保存到数据库里是最好的,后面会考虑保存到数据库),下次展示页面的时候,只要读取txt文档里的json,然后再转成泛型集合。
将页面上的div信息,和连线信息转成json跳转到ajax.aspx页面:
1 function save() {
2 var connects = [];
3 $.each(jsPlumb.getAllConnections(), function (idx, connection) {
4 connects.push({
5 ConnectionId: connection.id,
6 PageSourceId: connection.sourceId,
7 PageTargetId: connection.targetId,
8 SourceText: connection.source.innerText,
9 TargetText: connection.target.innerText,
10 });
11 });
12 var blocks = [];
13 $("#right .node").each(function (idx, elem) {
14 var $elem = $(elem);
15 blocks.push({
16 BlockId: $elem.attr('id'),
17 BlockContent: $elem.html(),
18 BlockX: parseInt($elem.css("left"), 10),
19 BlockY: parseInt($elem.css("top"), 10)
20 });
21 });
22
23 var serliza = JSON.stringify(connects) + "&" + JSON.stringify(blocks);
24 $.ajax({
25 type: "post",
26 url: "ajax.aspx",
27 data: { id: serliza },
28 success: function (filePath) {
29 window.open("show-flowChart.aspx?path=" + filePath);
30 }
31 });
32 }
ajax.aspx页面将前台传过来的json保存到服务器端,并跳转至 show-flowChart.aspx:
1 protected void Page_Load(object sender, EventArgs e)
2 {
3 if (!IsPostBack)
4 {
5 string str = Request["id"];
6 string filePath = Server.MapPath("~/prototype/project-reply")+"\\json"+DateTime.Now.ToString("yyyyMMddhhmmss")+".txt";
7 WriteToFile(filePath,str,false);
8 //Response.Redirect("show-flowChart.aspx?path="+filePath);
9 Response.Write(filePath);
10 }
11 }
12 public static void WriteToFile(string name, string content, bool isCover)
13 {
14 FileStream fs = null;
15 try
16 {
17 if (!isCover && File.Exists(name))
18 {
19 fs = new FileStream(name, FileMode.Append, FileAccess.Write);
20 StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
21 sw.WriteLine(content);
22 sw.Flush();
23 sw.Close();
24 }
25 else
26 {
27 File.WriteAllText(name, content, Encoding.UTF8);
28 }
29 }
30 finally
31 {
32 if (fs != null)
33 {
34 fs.Close();
35 }
36 }
37
38 }
show-flowChart.aspx页面:
1 protected void Page_Load(object sender, EventArgs e)
2 {
3 if (!IsPostBack)
4 {
5 string str = Request["path"];
6 StreamReader sr = new StreamReader(str);
7 string jsonText = sr.ReadToEnd();
8
9 List list = new JavaScriptSerializer().Deserialize>(jsonText.Split('&')[0]);
10 List blocks = new JavaScriptSerializer().Deserialize>(jsonText.Split('&')[1]);
11 string htmlText = "";
12 string conn = "";
13 if (blocks.Count > 0)
14 {
15 foreach (JsPlumbBlock block in blocks)
16 {
17 if(block.BlockContent=="开始"||block.BlockContent=="结束")
18 htmlText += "" + block.BlockContent + "";
19 else
20 htmlText += "" + block.BlockContent + "";
21 }
22 foreach (JsPlumbConnect jsplum in list)
23 conn += "jsPlumb.connect({ source: \"" + jsplum.PageSourceId + "\", target: \"" + jsplum.PageTargetId + "\" }, flowConnector);";
24 Literal1.Text = htmlText;
25 string script = "jsPlumb.ready(function () {" + conn + "});";
26 ClientScript.RegisterStartupScript(this.GetType(), "myscript", script, true);
27 }
28 }
29 }
以及两个用到的类JsPlumbConnect类和JsPlumbBlock类:
1 ///
2 /// 连接线信息
3 ///
4 public class JsPlumbConnect
5 {
6 public string ConnectionId { get; set; }
7 public string PageSourceId { get; set; }
8 public string PageTargetId { get; set; }
9 public string SourceText { get; set; }
10 public string TargetText { get; set; }
11 }
12 ///
13 /// 流程图的所有div
14 ///
15 public class JsPlumbBlock
16 {
17 ///
18 /// div Id
19 ///
20 public string BlockId { get; set; }
21 ///
22 /// div里面的内容
23 ///
24 public string BlockContent { get; set; }
25 public int BlockX { get; set; }
26 public int BlockY { get; set; }
27 }