zoukankan      html  css  js  c++  java
  • 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", {  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

  • 相关阅读:
    网络流 KM dinic
    网络流 增广路 回退
    树链剖分
    线段树区间更新 lazy
    全排列
    各种蕴含算法思想的DP
    各种蕴含算法思想的DP
    Strassen矩阵乘法之思考
    [0,x)的随机数
    hdu1331 按着题目的公式直接写
  • 原文地址:https://www.cnblogs.com/sggx/p/3836432.html
Copyright © 2011-2022 走看看