JS组件系列——JsPlumb制作流程图及相关效果详解

上    篇

前言:之前项目里面用到了Web里面的拖拽流程图的技术JsPlumb,其实真不算难,不过项目里面用HTML做的一些类似flash的效果,感觉还不错,在此分享下。

Jsplumb官网:https://jsplumbtoolkit.com

GitHub:https://github.com/sporritt/jsplumb/

一、效果图展示

 1、从左边拖动元素到中间区域,然后连线

 2、连线类型可以自定义:这里定义为直线、折线、曲线。实际项目中根据业务我们定义为分装线、分装支线、总装线等

 3、鼠标拖动区域选中元素,并且选中元素统一拖动位置。

 4、对选中的元素左对齐。

5、对选中元素居中对齐

6、右对齐

7、上对齐

8、垂直居中对齐

9、下对齐

10、根据第一个选中的元素上下靠拢

11、根据第一个选中的元素左右靠拢

12、根据第一个选中的元素同高

13、根据第一个选中的元素同宽

14、选中元素顺时针旋转,点击一次旋转45度

15、选中元素逆时针旋转。

16、选中统一删除元素以及元素上面的连线

这里很多效果其实在项目中作用并不太大,很多单纯就是为了展示用的。没办法,领导要求,我们就只有做喽。

二、代码详解

 这里涉及的效果比较多,可能要分多篇来介绍。这篇还是来看看构造流程图的核心技术:JsPlumb。

1、概述

关于JsPlumb的内容,园子里也有很多朋友做过分享,也有写得不错的博文。在此就简单说明下吧。jsPlumb是一个强大的JavaScript连线库,它可以将html中的元素用箭头、曲线、直线等连接起来,适用于开发Web上的图表、建模工具等。它同时支持jQuery+jQuery UI、MooTools和YUI3这三个JavaScript框架,十分强大。本项目中还是结合大家最熟悉的JQuery来讲解。并且还要注意的一点就是JsPlumb的浏览器兼容性,JsPlumb支持IE 6以上、火狐、谷歌等各种浏览器

2、使用

(1)引入JS文件

可以直接去官网上面下载最新的js库,由于JsPlumb需要JQuery的支持,按照网上的说法,它只兼容jQuery1.3.x及以上版本,并在jQuery UI 1.7.x、1.8.x及1.9.x上测试通过。所以我们需要下载较高一点版本的JQuery和JQuery UI。关于JsPlumb的内容只需要引用一个Js即可。类似

<script src="~/Content/jquery-1.9.1.min.js"></script>
<script src="~/Content/jquery-ui-1.11.4.custom/jquery-ui.js"></script>
<link href="~/Content/jquery-ui-1.11.4.custom/jquery-ui.min.css" rel="stylesheet" />
<script src="~/Content/jsPlumb-master/dist/js/jquery.jsPlumb-1.7.5.js"></script>

(2)初始化

使用JsPlumb需要注意一点,JsPlumb的连线的样式是由点确定的,也就是说点的样式里面包含了相关的属性来说明当使用此点来连线的时候,连线的样式应该是什么样的。

在我们项目里面,左边的模型区域,中间才是设计区域。那么要将一个元素从模型区域创建出来,就要用到我们JQuery UI里面的draggable和droppable事件。首先我们注册左边模型的draggable和中间区域的droppable事件。

cshtml页面代码,<div id="divContentLeftMenu">这个是左边模型的容器,<div id="divCenter"></div>表示中间区域容器。

复制代码
      <div id="divContentLeftMenu">
            <div class="sidebar-menu" id="divSidebar">
                <a href="#plantmodel" onclick="Resize()" class="nav-header menu-first collapsed" data-toggle="collapse">工厂模型</a>
                <ul id="plantmodel" class="nav nav-list collapse menu-second">
                </ul>
                <a href="#artlinemodel" onclick="Resize()" class="nav-header menu-first collapsed" data-toggle="collapse">工艺段模型</a>
                <ul id="artlinemodel" class="nav nav-list collapse menu-second">
                    <li>
                        <a href="#">
                            <div class="node radius" id="node4" dbtype="DTO_TM_ART_LINE">
                                <label>工段</label>
                            </div>
                        </a>
                    </li>
                    <li>
                        <a href="#">
                            <div class="node" id="node5" dbtype="DTO_TM_ULOC">
                                <label>工位</label>
                            </div>
                        </a>
                    </li>
                </ul>
            </div>
        </div>
        <div id="divCenter"></div>
复制代码

Js代码:

首先我们定义几个点的样式的全局变量

复制代码
//基本连接线样式
var connectorPaintStyle = {
    strokeStyle: "#1e8151",
    fillStyle: "transparent",
    radius: 5,
    lineWidth: 2
};
// 鼠标悬浮在连接线上的样式
var connectorHoverStyle = {
    lineWidth: 3,
    strokeStyle: "#216477",
    outlineWidth: 2,
    outlineColor: "white"
};
var endpointHoverStyle = {
    fillStyle: "#216477",
    strokeStyle: "#216477"
};
//空心圆端点样式设置
var hollowCircle = {
    DragOptions: { cursor: 'pointer', zIndex: 2000 },
    endpoint: ["Dot", { radius: 7 }],  //端点的形状
    connectorStyle: connectorPaintStyle,//连接线的颜色,大小样式
    connectorHoverStyle: connectorHoverStyle,
    paintStyle: {
        strokeStyle: "#1e8151",
        fillStyle: "transparent",
        radius: 5,
        lineWidth: 2
    },        //端点的颜色样式
    //anchor: "AutoDefault",
    isSource: true,    //是否可以拖动(作为连线起点)
    connector: ["Straight", { stub: [0, 0], gap: 10, cornerRadius: 5, alwaysRespectStubs: true }],  //连接线的样式种类有[Bezier],[Flowchart],[StateMachine ],[Straight ]
    isTarget: true,    //是否可以放置(连线终点)
    maxConnections: -1,    // 设置连接点最多可以连接几条线
    connectorOverlays: [["Arrow", { width: 10, length: 10, location: 1 }]]
};
复制代码

然后再页面初始化完成之后注册事件

复制代码
        $(function(){
                //左边区域的draggable事件
                $("#divContentLeftMenu .node").draggable({
                    helper: "clone",
                    scope: "plant"
                });

                //中间拖拽区的drop事件
                $("#divCenter").droppable({
                    scope: "plant",
                    drop: function (event, ui) {
                        // 创建工厂模型到拖拽区
                        CreateModel(ui, $(this));
                    }
                });
        });    
复制代码
复制代码
//1.创建模型(参数依次为:drop事件的ui、当前容器、id、当前样式)
function CreateModel(ui, selector) { //1.1 添加html模型 var modelid = $(ui.draggable).attr("id"); i++; var id = modelid + i; var cur_css = modelid; var type = $(ui.helper).attr("dbtype"); $(selector).append('<div class="node ' + cur_css + '" id="' + id + '" dbtype="' + type + '" parentid="' + $(selector).attr("id") + '" onclick="oInitElement.GetPropertiesByType(\'' + type + '\',this)" ondblclick="InitStation().DbClick(\'' + type + '\',this)" >' + $(ui.helper).html() + '</div>'); var left = parseInt(ui.offset.left - $(selector).offset().left); var top = parseInt(ui.offset.top - $(selector).offset().top); $("#" + id).css("left", left).css("top", top); //jsPlumb.setContainer($("#divCenter")); //1.2 添加连接点 jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle); jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle); jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle); jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle); jsPlumb.draggable(id); //1.3 注册实体可draggable和resizable $("#" + id).draggable({ containment: "parent", start: function () { startMove(); }, drag: function (event, ui) { MoveSelectDiv(event, ui, id); jsPlumb.repaintEverything(); }, stop: function () { jsPlumb.repaintEverything(); } }); $("#" + id).resizable({ resize: function () { jsPlumb.repaintEverything(); }, stop: function () { jsPlumb.repaintEverything(); //oInitElement.SendPropRequest("DTO_TM_PLANT", $(this)); } }); return id; };
复制代码

重点来看看这一句:

jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);

调用了JsPlumb里面的addEndpoint方法,第一个参数表示页面标签的id,第一个表示连线点的位置(RightMiddle、LeftMiddle、TopCenter、BottomCenter四个选项);第三参数表示点的样式以及连线的样式。没调用依次addEndpoint方法,元素上面就会多一个连线的节点。关于hollowCircle里面各个参数的意义,可以查看api。

还有一句多个地方都看到了:

jsPlumb.repaintEverything();

看字面意思大概能知道这句是干什么的,修复所有。当在中间区域拖动元素的时候,如果不带这一句,节点不会跟着元素一起移动。加上之后节点才会跟随标签移动。至此,最基础的JsPlumb连线就完成了。源码在下篇。

 


下    篇

前言:在上篇介绍了下JsPlumb在浏览器里面画流程图的效果展示,以及简单的JsPlumb代码示例。这篇还是接着来看看各个效果的代码说明。

一、设置连线的样式和颜色效果代码示例

大概的效果如图:

这些效果看着很简单,那么,我们如何用代码去实现它呢。上章我们说过,JsPlumb的连线样式是由点的某些属性决定的,既然如此,我们就通过设置点的样式来动态改变连线的样式即可。来看代码:

首先来看看连线类型的那个select

复制代码
 <div id="btn_linetype" class="divMenuBtn btn-default btn">
    连线类型:
  <select id="sel_linetype" style="width:80px;height:20px">
    <
option value="2">直线</option>
    <
option value="1">折线</option>
    <
option value="3">曲线</option>
  </
select>
</div>
复制代码

在页面初始化的时候注册select的change事件

复制代码
//全局的空心圆端点样式设置
var hollowCircle = {
    DragOptions: { cursor: 'pointer', zIndex: 2000 },
    endpoint: ["Dot", { radius: 7 }],  //端点的形状
    connectorStyle: connectorPaintStyle,//连接线的颜色,大小样式
    connectorHoverStyle: connectorHoverStyle,
    paintStyle: {
        strokeStyle: "#1e8151",
        fillStyle: "transparent",
        radius: 5,
        lineWidth: 2
    },        //端点的颜色样式
    //anchor: "AutoDefault",
    isSource: true,    //是否可以拖动(作为连线起点)
    connector: ["Straight", { stub: [0, 0], gap: 10, cornerRadius: 5, alwaysRespectStubs: true }],  //连接线的样式种类有[Bezier],[Flowchart],[StateMachine ],[Straight ]
    isTarget: true,    //是否可以放置(连线终点)
    maxConnections: -1,    // 设置连接点最多可以连接几条线
    connectorOverlays: [["Arrow", { width: 10, length: 10, location: 1 }]]
};
复制代码
复制代码
//页面初始化完成之后
$(function () {
    //连线样式下拉框的change事件
    $("#sel_linetype").change(function () {
        var strlinetype = "";
        var strlinecolor = "";
        //设置新添加元素的节点的连线样式
        //直线的样式和样色
        if ($(this).val() == "1") {
            strlinetype = "Flowchart";
            strlinecolor = "red";
            hollowCircle.connector = ["Flowchart", { stub: [0, 0], gap: 10, cornerRadius: 5, alwaysRespectStubs: true }];
        }
        //折线的样式和颜色
        else if ($(this).val() == "2") {
            strlinetype = "Straight";
            strlinecolor = "green";
            hollowCircle.connector = ["Straight", { stub: [0, 0], gap: 10, cornerRadius: 5, alwaysRespectStubs: true }];
        }
        //曲线的样式和颜色
        else if ($(this).val() == "3") {
            strlinetype = "Bezier";
            strlinecolor = "orange";
            hollowCircle.connector = ["Bezier", { stub: [0, 0], gap: 10, cornerRadius: 5, alwaysRespectStubs: true }];
        }
        //设置已经存在的所有的连接点的连线样式
        var arrnode = $("#divCenter").find(".node");
        for (var i = 0; i < arrnode.length; i++) {
            var arrendpoints = jsPlumb.getEndpoints($(arrnode[i]).attr("id"));
            if (arrendpoints == undefined || arrendpoints == null) {
                return;
            }
            var oconnector = arrendpoints[0].connector;
            if (oconnector == null || oconnector == undefined) {
                return;
            }
            oconnector[0] = strlinetype;
            var oconnectstyle = arrendpoints[0].connectorStyle;
            if (oconnectstyle == null || oconnectstyle == undefined) {
                return;
            }
            oconnectstyle.strokeStyle = strlinecolor;
        }
    });
});
复制代码

其实也就几行代码,设置已经存在和将要拖动到界面上面的端点的连线样式。

二、全选、全选拖动效果代码示例

 可以选中元素,批量拖动元素和连线,大概效果:

看看实现代码:

1、初始化的时候注册可选中

复制代码
$(function () {
    var oRegionSelect = new RegionSelect({
        region: '#divCenter div.node',
        selectedClass: 'seled',
        parentId: "divCenter"
    });
    oRegionSelect.select();
});
复制代码

 2、选中相关方法

var _selectedRegions = [];
//var selProp;

function RegionSelect(selRegionProp) {
    //selProp = selRegionProp;
    this.regions = [];
    this.selProp = selRegionProp;
    this.InitRegions(selRegionProp);
    this.selectedClass = selRegionProp["selectedClass"];
    this.selectedRegion = [];
    this.selectDiv = null;
    this.startX = null;
    this.startY = null;
    this.parentId = selRegionProp["parentId"];
}

RegionSelect.prototype.InitRegions = function () {
    var _self = this;
    _self.regions = [];
    var _regions = document.getElementsBySelector(_self.selProp["region"]);//$("#divCenter > .node");//

    var bSelect = true;
    if (_regions && _regions.length > 0) {
        for (var i = 0; i < _regions.length; i++) {
            _regions[i].onmousedown = function () {
                bSelect = false;
                var evt = window.event || arguments[0];
                if (!evt.shiftKey && !evt.ctrlKey) {
                    if ($.inArray(this, _selectedRegions) === -1) {
                        // 清空所有select样式
                        _self.clearSelections(_regions);
                        this.className += " " + _self.selectedClass;
                        // 清空selected数组,并加入当前select中的元素
                        _selectedRegions = [];
                        _selectedRegions.push(this);
                    }
                } else {
                    if (this.className.indexOf(_self.selectedClass) == -1) {
                        this.className += " " + _self.selectedClass;
                        _selectedRegions.push(this);
                    } else {
                        this.className = this.className.replaceAll(_self.selectedClass, "");
                        _selectedRegions.remove(this);
                    }
                }
                clearEventBubble(evt);
            }
            this.regions.push(_regions[i]);
        }
    }

    if (bSelect) {
        // 清空所有select样式
        _self.clearSelections(_regions);
        // 清空selected数组,并加入当前select中的元素
        _selectedRegions = [];
    }
}

RegionSelect.prototype.select = function () {
    var _self = this;
    var sDivId = _self.parentId;
    var intMousePosition = [0, 0];
    var intOriginalPosition = [0, 0];
    var parentWidth = parseInt(document.getElementById(sDivId).parentElement.offsetWidth);
    var parentHeight = parseInt(document.getElementById(sDivId).parentElement.offsetHeight);
    addEvent("mousedown", function () {
        var evt = window.event || arguments[0];
        var buttonType = evt.buttons || evt.button;
        if (evt.target != undefined) {
            if (evt.target.id !== sDivId) return;
        }
        if (evt.srcElement != undefined) {
            if (evt.srcElement.id !== sDivId) return;
        }
        if (evt.buttons == undefined && buttonType == 0){
            _self.onBeforeSelect(evt, sDivId);
        }
        if (buttonType === 1) {
            _self.onBeforeSelect(evt, sDivId);
        }
        if (buttonType === 2) {
            intMousePosition = [evt.clientX, evt.clientY];
            var movX = parseInt(GetStyle(document.getElementById(sDivId), "left"));
            var movY = parseInt(GetStyle(document.getElementById(sDivId), "top"));
            intOriginalPosition = [movX, movY];
            document.getElementById(sDivId).style.cursor = "move";
        }
        clearEventBubble(evt);
    }, document);

    addEvent("mousemove", function () {
        var evt = window.event || arguments[0];
        //if (evt.target.id !== sDivId) return;
        var buttonType = evt.buttons || evt.button;
        if (evt.buttons == undefined && buttonType == 0) {
            _self.onSelect(evt, sDivId);
        }
        if (buttonType === 1) {
            _self.onSelect(evt, sDivId);
        }
        if (buttonType === 2) {
            var newX = intOriginalPosition[0] + evt.clientX - intMousePosition[0];
            var newY = intOriginalPosition[1] + evt.clientY - intMousePosition[1];
            if (newX >= 0) {
                newX = 0;
            }
            if (newY >= 0) {
                newY = 0;
            }
            $("#" + sDivId).css("left", newX + "px");
            $("#" + sDivId).css("top", newY + "px");
            $("#" + sDivId).css("width", (parentWidth-newX) + "px");
            $("#" + sDivId).css("height", (parentHeight-newY) + "px");

        }
        clearEventBubble(evt);
    }, document);

    addEvent("mouseup", function () {
        var evt = window.event || arguments[0];
        var buttonType = evt.buttons || evt.button;
        if (evt.buttons == undefined && buttonType == 0) {
        }
        if (buttonType === 1) {
        }
            document.getElementById(sDivId).style.cursor = "default";
            _self.onEnd();
    }, document);
}

RegionSelect.prototype.onBeforeSelect = function (evt, sDivId) {
    // 创建模拟 选择框
    var _self = this;
    _self.InitRegions(_self.selProp);
    if (!document.getElementById("selContainer")) {
        this.selectDiv = document.createElement("div");
        this.selectDiv.style.cssText = "position:absolute;width:0px;height:0px;font-size:0px;margin:0px;padding:0px;border:1px dashed #0099FF;background-color:#C3D5ED;z-index:1000;filter:alpha(opacity:60);opacity:0.6;display:none;";
        this.selectDiv.id = "selContainer";
        document.getElementById(sDivId).appendChild(this.selectDiv);
    } else {
        this.selectDiv = document.getElementById("selContainer");
    }

    this.startX = posXY(evt, sDivId).x;
    this.startY = posXY(evt, sDivId).y;
    this.isSelect = true;

}

RegionSelect.prototype.onSelect = function (evt, sDivId) {
    var self = this;
    if (self.isSelect) {
        if (self.selectDiv.style.display == "none") self.selectDiv.style.display = "";

        var posX = posXY(evt, sDivId).x;
        var poxY = posXY(evt, sDivId).y;
        self.selectDiv.style.left = Math.min(posX, this.startX) + "px";
        self.selectDiv.style.top = Math.min(poxY, this.startY) + "px";
        self.selectDiv.style.width = Math.abs(posX - this.startX) + "px";
        self.selectDiv.style.height = Math.abs(poxY - this.startY) + "px";

        var regionList = self.regions;
        for (var i = 0; i < regionList.length; i++) {
            if (self.selectDiv.parentNode.id !== regionList[i].parentNode.id) continue;
            var r = regionList[i], sr = self.innerRegion(self.selectDiv, r);
            if (sr && r.className.indexOf(self.selectedClass) == -1) {
                r.className = r.className + " " + self.selectedClass;
                _selectedRegions.push(r);
            } else if (!sr && r.className.indexOf(self.selectedClass) != -1) {
                r.className = r.className.replaceAll(self.selectedClass, "");
                _selectedRegions.remove(r);
            }

        }
    }
}

RegionSelect.prototype.onEnd = function () {
    var self = this;
    if (self.selectDiv) {
        self.selectDiv.style.display = "none";
    }
    this.isSelect = false;
    //_selectedRegions = this.selectedRegion;
}

// 判断一个区域是否在选择区内
RegionSelect.prototype.innerRegion = function (selDiv, region) {
    var s_top = parseInt(selDiv.style.top);
    var s_left = parseInt(selDiv.style.left);
    var s_right = s_left + parseInt(selDiv.offsetWidth);
    var s_bottom = s_top + parseInt(selDiv.offsetHeight);

    var r_top = parseInt(region.offsetTop);
    var r_left = parseInt(region.offsetLeft);
    var r_right = r_left + parseInt(region.offsetWidth);
    var r_bottom = r_top + parseInt(region.offsetHeight);

    var t = Math.max(s_top, r_top);
    var r = Math.min(s_right, r_right);
    var b = Math.min(s_bottom, r_bottom);
    var l = Math.max(s_left, r_left);

    if (b > t + 5 && r > l + 5) {
        return region;
    } else {
        return null;
    }

}

RegionSelect.prototype.clearSelections = function (regions) {
    for (var i = 0; i < regions.length; i++) {
        regions[i].className = regions[i].className.replaceAll(this.selectedClass, "");
    }
}

function getSelectedRegions() {
    return _selectedRegions;
}

/*-------------------------------------- 区域选择方法结束 --------------------------------------------*/

function showSelDiv() {
    var selInfo = "";
    var arr = getSelectedRegions();
    for (var i = 0; i < arr.length; i++) {
        selInfo += arr[i].innerHTML + "\n";
    }

    alert("共选择 " + arr.length + " 个文件,分别是:\n" + selInfo);

}

function MoveSelectDiv(event, ui,id) {
    var arr = getSelectedRegions();
    var iMoveLeft = ui.position.left - ui.originalPosition.left;
    var iMoveTop = ui.position.top - ui.originalPosition.top;

    for (var i = 0; i < arr.length; i++) {
        //if (arr[i].id === id) continue;

        if (arr[i].parentNode.id !== document.getElementById(id).parentNode.id) continue;
        var iLeft = parseInt($(arr[i]).attr("bLeft"));
        var iTop = parseInt($(arr[i]).attr("bTop"));
        $(arr[i]).css("left", (iLeft + iMoveLeft) + "px");
        $(arr[i]).css("top", (iTop + iMoveTop) + "px");
    }
}

function startMove() {
    var arr = getSelectedRegions();
    for (var i = 0; i < arr.length; i++) {
        $(arr[i]).attr("bLeft", $(arr[i]).position().left);
        $(arr[i]).attr("bTop", $(arr[i]).position().top);
    }
}
选中相关方法

三、对齐、旋转代码示例

//左对齐
function SelectAlignLeft() {
    var arr = getSelectedRegions();
    var iLeft = 0;
    var id = "";

    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if ($(arr[i]).position().left<iLeft||iLeft===0) {
            iLeft = $(arr[i]).position().left;
        }
    }

    for (var j = 0; j < arr.length; j++) {
        if (id !== arr[j].parentNode.id) continue;
        $(arr[j]).css("left", iLeft + "px");
    }

    jsPlumb.repaintEverything();
}

//居中对齐
function SelectAlignCenter() {
    var arr = getSelectedRegions();
    var iLeft = 0;
    var id = "";

    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if ($(arr[i]).position().left < iLeft || iLeft === 0) {
            iLeft = $(arr[i]).position().left + parseInt(GetStyle(arr[i],"width")) / 2;
        }
    }

    for (var j = 0; j < arr.length; j++) {
        if (id !== arr[j].parentNode.id) continue;
        $(arr[j]).css("left", (iLeft - parseInt(GetStyle(arr[j],"width")) / 2) + "px");
    }

    jsPlumb.repaintEverything();
}
//右对齐
function SelectAlignRight() {
    var arr = getSelectedRegions();
    var iLeft = 0;
    var id = "";

    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if ($(arr[i]).position().left + parseInt(GetStyle(arr[i], "width")) > iLeft || iLeft === 0) {
            iLeft = $(arr[i]).position().left + parseInt(GetStyle(arr[i], "width"));
        }
    }

    for (var j = 0; j < arr.length; j++) {
        if (id !== arr[j].parentNode.id) continue;
        $(arr[j]).css("left", (iLeft - parseInt(GetStyle(arr[j], "width"))) + "px");
    }

    jsPlumb.repaintEverything();
}

//上对齐
function SelectAlignTop() {
    var arr = getSelectedRegions();
    var iTop = 0;
    var id = "";

    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if ($(arr[i]).position().top < iTop || iTop === 0) {
            iTop = $(arr[i]).position().top;
        }
    }

    for (var j = 0; j < arr.length; j++) {
        if (id !== arr[j].parentNode.id) continue;
        $(arr[j]).css("top", iTop + "px");
    }

    jsPlumb.repaintEverything();
}

//垂直居中
function SelectAlignMiddle() {
    var arr = getSelectedRegions();
    var iTop = 0;
    var id = "";

    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if ($(arr[i]).position().top + parseInt(GetStyle(arr[i], "height")) / 2 < iTop || iTop === 0) {
            iTop = $(arr[i]).position().top + parseInt(GetStyle(arr[i], "height")) / 2;
        }
    }

    for (var j = 0; j < arr.length; j++) {
        if (id !== arr[j].parentNode.id) continue;
        $(arr[j]).css("top", (iTop - parseInt(GetStyle(arr[j], "height")) / 2) + "px");
    }

    jsPlumb.repaintEverything();
}

//下对齐
function SelectAlignBottom() {
    var arr = getSelectedRegions();
    var iTop = 0;
    var id = "";

    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if ($(arr[i]).position().top + parseInt(GetStyle(arr[i], "height")) > iTop || iTop === 0) {
            iTop = $(arr[i]).position().top + parseInt(GetStyle(arr[i], "height"));
        }
    }

    for (var j = 0; j < arr.length; j++) {
        if (id !== arr[j].parentNode.id) continue;
        $(arr[j]).css("top", (iTop - parseInt(GetStyle(arr[j], "height"))) + "px");
    }

    jsPlumb.repaintEverything();
}

//上下靠拢
function SelectUpColse() {
    var arr = getSelectedRegions();
    var iTop = 0;
    var id = "";
    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if (iTop === 0) iTop = $(arr[i]).position().top;
        $(arr[i]).css("top", iTop + "px");
        iTop += parseInt(GetStyle(arr[i], "height"));
    }

    jsPlumb.repaintEverything();
}

//左右靠拢
function SelectLeftColse() {
    var arr = getSelectedRegions();
    var iLeft = 0;
    var id = "";
    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if (iLeft === 0) iLeft = $(arr[i]).position().left;
        $(arr[i]).css("left", iLeft + "px");
        iLeft += parseInt(GetStyle(arr[i], "width"));
    }

    jsPlumb.repaintEverything();

}


//同高
function SelectSameHeight() {
    var arr = getSelectedRegions();
    var iHeigth = 0;
    var id = "";
    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if (iHeigth === 0) iHeigth = parseInt(GetStyle(arr[i], "height"));
        $(arr[i]).css("height", iHeigth+"px");
    }

    jsPlumb.repaintEverything();
}


//同宽
function SelectSameWidth() {
    var arr = getSelectedRegions();
    var iWidth = 0;
    var id = "";
    for (var i = 0; i < arr.length; i++) {
        if (id === "") id = arr[i].parentNode.id;
        if (id !== arr[i].parentNode.id) continue;
        if (iWidth === 0) iWidth = parseInt(GetStyle(arr[i], "width"));
        $(arr[i]).css("width", iWidth + "px");
    }

    jsPlumb.repaintEverything();

}


//旋转
function SelectClockwise(index) {
    var arr = getSelectedRegions();
    //var iWidth = 0;
    //var id = "";
    for (var i = 0; i < arr.length; i++) {
        //if (id === "") id = arr[i].parentNode.id;
        //if (id !== arr[i].parentNode.id) continue;
        var sIndex= arr[i].style.transform.replace("rotate(", "").replace("deg)", "");
        var iNum = 0;
        if (sIndex) iNum = parseInt(sIndex);
        $(arr[i]).css("transform", "rotate(" + (iNum + index)%360 + "deg)");

        var points = jsPlumb.getEndpoints(arr[i]);
    }

    jsPlumb.repaintEverything();

}
//删除选中
function DeleteSelect() {
    var arr = getSelectedRegions();
    for (var i = 0; i < arr.length; i++) {
        jsPlumb.remove(arr[i],true);
        //var points = jsPlumb.getEndpoints(arr[i]);
        //for (var j = 0; j < points.length; j++) {
        //    jsPlumb.deleteEndpoint(points[j]);
        //}
        //arr[i].parentNode.removeChild(arr[i]);
        }

    jsPlumb.repaintEverything();

}

function GetStyle(obj, attr) {
    if (obj.currentStyle) {
        return obj.currentStyle[attr];  //只适用于IE
    }
    else {
        return getComputedStyle(obj, false)[attr];   //只适用于FF,Chrome,Safa
    }
    return obj.style[attr]; //本人测试在IE和FF下没有用,chrome有用
}
对齐、旋转代码示例

 

代码可能有点乱,待整理。上章有博友就找我要过源码,这次出来一个初级的版本,还是把源码贴出来。有兴趣可以看看。源码下载

 

出处:

http://www.cnblogs.com/landeanfen/p/4959584.html

http://www.cnblogs.com/landeanfen/p/4971211.html

=======================================================================================

jsPlumb开发流程设计器

前言

jsPlumb是一款开源软件,但jsPlumb toolkit是收费的。

本文主要使用jsPlumb实现一些简单的流程设计功能。

基础学习

首先引入jsplumb.min.js。

1
<script src="https://cdn.jsdelivr.net/npm/jsplumb@2.8.0/dist/js/jsplumb.min.js"></script>

然后编写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    #diagramContainer {
      padding: 20px;
      width: 80%;
      height: 200px;
      border: 1px solid gray;
    }
    .item {
      height: 80px;
      width: 80px;
      border: 1px solid blue;
      float: left;
    }
  </style>
</head>
<body>
  <div id="diagramContainer">
    <div id="item_left" class="item"></div>
    <div id="item_right" class="item" style="margin-left:50px;"></div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/jsplumb@2.8.0/dist/js/jsplumb.min.js"></script>
  <script>
    /* global jsPlumb */
    jsPlumb.ready(function () {
      jsPlumb.connect({
        source: 'item_left',
        target: 'item_right',
        endpoint: 'Dot'
      })
    })
  </script>
</body>
</html>

效果图如下:

 

可以看到,我们定义了一个容器diagramContainer,和两个div块元素,然后通过jsPlumb的connect连接函数,将两个正方形,连接到了一起。

基础学习参考网站:https://github.com/wangduanduan/jsplumb-chinese-tutorial

流程设计器开发

首先设计Html元素,设计一个左侧功能列表区域,一个右侧流程设计区域。

然后再设计三个节点拖进设计区域后释放时的样式。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<div id="app">
        <div class="container-fluid">
            <div class="row">
                <div id="side-buttons" class="col-md-1 bg-info min-height ">
                    <div style="text-align:center;">
                        <h2 class="mt20 ">节点列表</h2>
                        <hr />
                        <a class="btn btn-success btn-controler btnw" href="#" data-template="tpl-Normal" role="button">
                            <i class="fa fa-square" aria-hidden="true"></i>
                            节点
                        </a>
                        <hr />
                        <a id="export" class="btn btn-success mt10 btnw" href="#" role="button">
                            <i class="fa fa-file-text-o" aria-hidden="true"></i>
                            导出
                        </a>
                    </div>
                </div>
                <div class="min-height">
                    <div class="title"><p>提示:双击连接线可删除连接。</p></div>
                    <div id="drop-bg" class="col-md-11 bg-success min-height">
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script id="tpl-Normal" type="text/html">
        <div class='pa' id='{{id}}' style='top:{{top}}px;left:{{left}}px; opacity: 0.6;'>
            <a class='btn btn-default' href='#' role='button'>
                <div>
                    <input type="text" value="{{comment}}" tag="{{id}}" class="nodeText" />
                    <span class="delete-node pull-right" data-type="deleteNode" data-id="{{id}}" style="font-size:10px;margin:0 -10px 0 0">X</span>
                    @*<span class="add-node pull-right" data-type="addDragNode" data-id="{{id}}" style="font-size:10px;margin:0 5px 0 0">+</span>*@
                </div>
            </a>
        </div>
    </script>
    <script id="tpl-Root" type="text/html">
        <div class='pa' id='{{id}}' style='top:{{top}}px;left:{{left}}px;opacity: 0.6;'>
            <a class='btn btn-success' href='#' role='button'>
                <div style="width:80px;height:30px;line-height:35px">
                    {{comment}}
                    @*<span class="delete-node pull-right" data-type="deleteNode" data-id="{{id}}">X</span>*@
                </div>
            </a>
        </div>
    </script>
    <script id="tpl-Exit" type="text/html">
        <div class='pa' id='{{id}}' style='top:{{top}}px;left:{{left}}px'>
            <a class='btn btn-danger' href='#' role='button'>
                <div style="width:80px;height:30px;line-height:35px">
                    {{comment}}
                    @*<span class="delete-node pull-right" data-type="deleteNode" data-id="{{id}}">X</span>*@
                </div>
            </a>
        </div>
    </script>

页面效果如下:

然后编写代码实现拖拽和释放的功能。

核心代码如下:

  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
jsPlumb.ready(
        function () {
            console.log("main-start")
            jsPlumb.setContainer('diagramContainer')
            
            $('.btn-controler').draggable({
                helper: 'clone',
                scope: 'ss'
            })
            $(areaId).droppable({
                scope: 'ss',
                drop: function (event, ui) {
                    dropNode(ui.draggable[0].dataset.template, ui.position)
                }
            })
            $('#app').on('click', function (event) {
                 
                event.stopPropagation()
                event.preventDefault()
                var item = event.target.dataset
                 
                if (item.type === 'deleteNode') {
                    var index = -1;
                    data.nodeList.forEach(function (node, i) {
                        if (node.id == item.id) {
                            index = i;
                        }
                    })
                    data.nodeList.splice(index, 1);
                    console.log(data.nodeList)
                    jsPlumb.remove(item.id)
                }
               
            })
            
            // 单点击了连接线上的X号
            jsPlumb.bind('dblclick', function (conn, originalEvent) {
                DataDraw.deleteLine(conn)
            })
           
            // 当链接建立
            jsPlumb.bind('beforeDrop', function (info) {
                console.log("beforeDrop") 
                console.log(info)
                var isSame = false;
                data.nodeList.forEach(function (node) {
                    if (info.sourceId == node.id) {
                        if (!node.data) {
                            node.data = []
                            var nextNode = {
                                "nextNode": info.targetId
                            }
                            node.data.push(nextNode)
                        }
                        else {
                              
                            node.data.forEach(function (dItem){
                                if (dItem.nextNode == info.targetId) {
                                    isSame = true;
                                    return;
                                }
                            })
                            if (!isSame) {
                                var nextNode = {
                                    "nextNode": info.targetId
                                }
                                node.data.push(nextNode)
                            }
                          
                        }
                        
                    }
                })
                if (!isSame) {
                    console.log(data.nodeList)
                    return connectionBeforeDropCheck(info)
                }
                else {
                    console.log("节点相同")
                    return
                }
            })
            console.log("main-DataDraw.draw")
            DataDraw.draw(data.nodeList)
            console.log("初始化节点文本事件")
            initNodeTextEvent();
   
        })

jsPlumb函数:

setContainer:设置容器。

droppable:指定该区域支持拖拽的控件。

draggable:指定该按钮可以被拖拽。

自定义函数:

DataDraw.draw初始化节点。

initNodeTextEvent设计图中的节点中的节点名称变化,同步到节点列表数组对象中,实现数据同步。

页面初始化时读取了data.js文件中的起始配置节点的数据。

data.js文件如下:

1
2
3
var data = {
    'nodeList': [{ "id": "Start", "type": "Root", "comment": "开始", "top": 50, "left": 150, "data": [{ "nextNode": "81422cf0-00ae-11ec-b359-c13e24702355" }, { "nextNode": "779c8300-00b1-11ec-923c-fbdaa48876a6" }] }, { "id": "e1a3de30-0096-11ec-b888-ddd94967488d", "comment": "22", "top": 198, "left": 566, "type": "Normal", "data": [{ "nextNode": "Exit" }] }, { "id": "81422cf0-00ae-11ec-b359-c13e24702355", "comment": "1", "top": 634, "left": 432, "type": "Normal", "data": [{ "nextNode": "Exit" }] }, { "id": "84689a40-00ae-11ec-b359-c13e24702355", "comment": "2", "top": 628, "left": 198, "type": "Normal", "data": [{ "nextNode": "Exit" }] }, { "id": "779c8300-00b1-11ec-923c-fbdaa48876a6", "comment": "", "top": 891, "left": 617, "type": "Normal" }, { "id": "Exit", "type": "Exit", "comment": "结束", "top": 818, "left": 929 }, { "id": "a57fe0d0-00b3-11ec-99d4-39fb5d424f70", "comment": "", "top": 316, "left": 1130, "type": "Normal" }]
}

这样我们就实现了基础的流程设计器了,下面我们看一下功能。

删除

点击链接线可以删除链接,如下图:

拖拽

拖拽节点按钮到设计器区域,如下图:

导出

点击导出按钮将当前流程的节点信息导出成json字符串,如下图

可以看到,设计器是支持一个节点发射出多个链接线的。

在导出时,我们再设计器中修改的节点名,也被同步的导出到json字符串中了。

----------------------------------------------------------------------------------------------------

到此,jsPlumb开发流程设计器就已经介绍完了。

代码已经传到Github上了,欢迎大家下载。

Github地址: https://github.com/kiba518/KibaWorkFlowDesigner_JS

----------------------------------------------------------------------------------------------------

注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的推荐】,非常感谢!

 

 

出处:https://www.cnblogs.com/kiba/p/15293054.html

posted on 2017-12-01 14:28  jack_Meng  阅读(65480)  评论(144编辑  收藏  举报

导航