jsPlumb插件做一個模仿viso的可拖拉流程圖


前言

這是我第一次寫博客,心情還是有點小小的激動!這次主要分享的是用jsPlumb,做一個可以給用戶自定義拖拉的流程圖,並且可以序列化保存在服務器端。

我在這次的實現上面做得比較粗糙,還有分享我在做jsPlumb流程圖遇到的一些問題。

准備工作

制作流程圖用到的相關的腳本:

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等腳本百度上都能找到,這里就不多說了。

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         }
View Code

 

 這里還有提到一點,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 <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>
View Code
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             });
View Code

怎么樣把左邊的層復制到右邊的層,我的做法是這樣的:

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             };
View Code

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);
View Code

通過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             });
View Code

我想在這里大家都有疑問吧,為什么用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         }
View Code

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     }
View Code

 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     }
View Code

以及兩個用到的類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     }
View Code

 

結尾

轉載請注明出處,謝謝!

附件下載地址:http://pan.baidu.com/s/1jGC8XM2

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM