這是我第一次寫博客,心情仍是有點小小的激動!此次主要分享的是用jsPlumb,作一個能夠給用戶自定義拖拉的流程圖,而且能夠序列化保存在服務器端。javascript
我在此次的實現上面作得比較粗糙,還有分享我在作jsPlumb流程圖遇到的一些問題。css
製做流程圖用到的相關的腳本:html
1 <script src="<%= ResolveUrl("~/resources/jquery/jquery-1.11.1.min.js")%>" type="text/javascript"></script> 2 <script src="<%= ResolveUrl("~/resources/jquery-ui-1.10.4/js/jquery-ui-1.10.4.min.js") %>" type="text/javascript"></script> 3 <script src="<%= ResolveUrl("~/resources/jquery-plugins/jquery.jsPlumb-1.6.2-min.js") %>" type="text/javascript"></script>
jsPlumb-1.6.2-min.js在官網上下載,這裏用得是最新版本。jquery-1.11.1.min.js等腳本百度上都能找到,這裏就很少說了。java
css樣式在官網裏也能夠搜到,這裏我就貼出來。node
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很是費勁,通常都是一邊開着金山翻譯,jquery
一邊看着文檔,英語好的略過這段。web
言歸正傳,如今開始咱們的jsPlumb流程圖製做,下面先附上流程圖。ajax
根據客戶的要求,咱們要完成的功能點有如下幾點:數據庫
1.支持將左邊的div層複製拖拉到右邊中間的層,而且左邊同一個div拖拉沒有次數限制,若是隻能拖拉一次,作這個東西就沒有什麼意義了。json
2.拖拉到中間的div層能夠拖動,拖動不能超過中間div的邊框。
3.拖動到中間的層,四周能有4個endpoint點,可供客戶連線。
4.能支持刪除多餘的div的功能。
5.支持刪除鏈接線。
6.能雙擊修改流程圖的文字。
7.能序列化保存流程圖。
下面咱們根據功能開始製做:
1.拖拉jsPlumb實際上是提供draggable方法,和droppable方法官網裏有介紹, 可是我這裏用得是jquery裏的draggable()和droppable()。
1 <div id="left"> 2 <div class="node radius" id="node1">開始</div> 3 <div class="node" id="node2">流程</div> 4 <div class="node" id="node3">判斷</div> 5 <div class="node radius" id="node4">結束</div> 6 </div> 7 8 <div id="right"> 9 <p>拖拉到此區域</p> 10 </div> 11 <div id="save"> 12 <input type="button" value="保存" onclick="save()" /> 13 </div>
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")根本找不到這個元素,
由於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<JsPlumbConnect> list = new JavaScriptSerializer().Deserialize<List<JsPlumbConnect>>(jsonText.Split('&')[0]); 10 List<JsPlumbBlock> blocks = new JavaScriptSerializer().Deserialize<List<JsPlumbBlock>>(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 += "<div class='node radius' id='" + block.BlockId + "'style='left:"+block.BlockX+"px;top:"+block.BlockY+"px;' >" + block.BlockContent + "</div>"; 19 else 20 htmlText += "<div class='node' id='" + block.BlockId + "'style='left:" + block.BlockX + "px;top:" + block.BlockY + "px;' >" + block.BlockContent + "</div>"; 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 /// <summary> 2 /// 鏈接線信息 3 /// </summary> 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 /// <summary> 13 /// 流程圖的全部div 14 /// </summary> 15 public class JsPlumbBlock 16 { 17 /// <summary> 18 /// div Id 19 /// </summary> 20 public string BlockId { get; set; } 21 /// <summary> 22 /// div裏面的內容 23 /// </summary> 24 public string BlockContent { get; set; } 25 public int BlockX { get; set; } 26 public int BlockY { get; set; } 27 }
轉載請註明出處,謝謝!
附件下載地址:http://pan.baidu.com/s/1jGC8XM2