zoukankan      html  css  js  c++  java
  • 使用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);  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;  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组件:

  • 相关阅读:
    LeetCode-079-单词搜索
    awk学习笔记
    Python实现排列组合算法
    python模拟登录人人
    Python的SQLite数据库使用方法
    C语言排序算法——插入排序算法
    C语言排序算法——简单选择排序算法
    C语言排序算法——冒泡排序算法
    Python学习——python的函数参数传递
    Python学习——实现secure copy功能
  • 原文地址:https://www.cnblogs.com/lwme/p/use-jsplumb-make-flowChart-designer.html
Copyright © 2011-2022 走看看