前言

这是我第一次写博客,心情还是有点小小的激动!这次主要分享的是用jsPlumb,做一个可以给用户自定义拖拉的流程图,并且可以序列化保存在服务器端。

我在这次的实现上面做得比较粗糙,还有分享我在做jsPlumb流程图遇到的一些问题。

准备工作

制作流程图用到的相关的脚本:

 <script src="<%= ResolveUrl("~/resources/jquery/jquery-1.11..min.js")%>" type="text/javascript"></script>
<script src="<%= ResolveUrl("~/resources/jquery-ui-1.10./js/jquery-ui-1.10..min.js") %>" type="text/javascript"></script>
<script src="<%= ResolveUrl("~/resources/jquery-plugins/jquery.jsPlumb-1.6.-min.js") %>" type="text/javascript"></script>

jsPlumb-1.6.2-min.js在官网上下载,这里用得是最新版本。jquery-1.11.1.min.js等脚本百度上都能找到,这里就不多说了。

css样式在官网里也可以搜到,这里我就贴出来。

     .node {
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);
border: 1px solid #346789;
width: 150px;
/*line-height: 40px;*/
text-align: center;
z-index:;
position: absolute;
background-color: #eeeeef;
color: black;
padding: 10px;
font-size: 9pt;
cursor: pointer;
height: 50px;
line-height: 50px;
}
.radius {
border-radius: 25em;
}
.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官网上的api全是英文的,博主我从小英文就不好,所以看里面的doc非常费劲,一般都是一边开着金山翻译,

一边看着文档,英语好的略过这段。

正文

言归正传,现在开始我们的jsPlumb流程图制作,下面先附上流程图。

功能

根据客户的要求,我们要完成的功能点有以下几点:

1.支持将左边的div层复制拖拉到右边中间的层,并且左边同一个div拖拉没有次数限制,如果只能拖拉一次,做这个东西就没有什么意义了。

2.拖拉到中间的div层可以拖动,拖动不能超过中间div的边框。

3.拖动到中间的层,四周能有4个endpoint点,可供客户连线。

4.能支持删除多余的div的功能。

5.支持删除连接线。

6.能双击修改流程图的文字。

7.能序列化保存流程图。

操作

下面我们根据功能开始制作:

1.拖拉jsPlumb其实是提供draggable方法,和droppable方法官网里有介绍, 但是我这里用得是jquery里的draggable()和droppable()。

 <div id="left">
<div class="node radius" id="node1">开始</div>
<div class="node" id="node2">流程</div>
<div class="node" id="node3">判断</div>
<div class="node radius" id="node4">结束</div>
</div> <div id="right">
<p>拖拉到此区域</p>
</div>
<div id="save">
<input type="button" value="保存" onclick="save()" />
</div>
     $("#left").children().draggable({
helper: "clone",
scope: "ss",
});

helper:"clone"表示复制,scope:"ss"是一个标识为了判断是否可以放置,主要用于droppable方法里面也设置这个标识来判断拖放到的地方,

除非两个都不写scope,可以随便拖放,但是会有一个问题,每次我从左边拖东西到右边,我再拖到的时候就会有div拖到不了,所以最好设置

scope:"//里面的值随便,只是一个标识"。

下面是完整的拖放:

 $("#left").children().draggable({
helper: "clone",
scope: "ss",
});
$("#right").droppable({
scope: "ss",
drop: function (event, ui) {
var left = parseInt(ui.offset.left - $(this).offset().left);
var top = parseInt(ui.offset.top - $(this).offset().top);
var name = ui.draggable[0].id;
switch (name) {
case "node1":
i++;
var id = "state_start" + i;
$(this).append('<div class="node" style="border-radius: 25em" id="' + id + '" >' + $(ui.helper).html() + '</div>');
$("#" + id).css("left", left).css("top", top);
jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
jsPlumb.draggable(id);
$("#" + id).draggable({ containment: "parent" });
doubleclick("#" + id);
break;
case "node2":
i++;
id = "state_flow" + i;
$(this).append("<div class='node' id='" + id + "'>" + $(ui.helper).html() + "</div>");
$("#" + id).css("left", left).css("top", top);
jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
jsPlumb.addEndpoint(id, hollowCircle);
jsPlumb.draggable(id);
$("#" + id).draggable({ containment: "parent" });
doubleclick("#" + id);
break;
case "node3":
i++;
id = "state_decide" + i;
$(this).append("<div class='node' id='" + id + "'>" + $(ui.helper).html() + "</div>");
$("#" + id).css("left", left).css("top", top);
jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
jsPlumb.addEndpoint(id, hollowCircle);
jsPlumb.draggable(id);
$("#" + id).draggable({ containment: "parent" });
doubleclick("#" + id);
break;
case "node4":
i++;
id = "state_end" + i;
$(this).append('<div class="node" style="border-radius: 25em" id="' + id + '" >' + $(ui.helper).html() + '</div>');
$("#" + id).css("left", left).css("top", top);
jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "LeftMiddle" }, hollowCircle);
jsPlumb.draggable(id);
$("#" + id).draggable({ containment: "parent" });
doubleclick("#" + id);
break;
}
}
});

怎么样把左边的层复制到右边的层,我的做法是这样的:

 $(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 初始化端点样式设置:主要设置一些基本的端点,连接线的样式,里面的属性不设置,默认使用默认值

     //基本连接线样式
var connectorPaintStyle = {
lineWidth: 4,
strokeStyle: "#61B7CF",
joinstyle: "round",
outlineColor: "white",
outlineWidth: 2
};
// 鼠标悬浮在连接线上的样式
var connectorHoverStyle = {
lineWidth: 4,
strokeStyle: "#216477",
outlineWidth: 2,
outlineColor: "white"
};
var hollowCircle = {
endpoint: ["Dot", { radius: 8 }], //端点的形状
connectorStyle: connectorPaintStyle,//连接线的颜色,大小样式
connectorHoverStyle: connectorHoverStyle,
paintStyle: {
strokeStyle: "#1e8151",
fillStyle: "transparent",
radius: 2,
lineWidth: 2
}, //端点的颜色样式
//anchor: "AutoDefault",
isSource: true, //是否可以拖动(作为连线起点)
connector: ["Flowchart", { stub: [40, 60], gap: 10, cornerRadius: 5, alwaysRespectStubs: true }], //连接线的样式种类有[Bezier],[Flowchart],[StateMachine ],[Straight ]
isTarget: true, //是否可以放置(连线终点)
maxConnections: -1, // 设置连接点最多可以连接几条线
connectorOverlays: [["Arrow", { width: 10, length: 10, location: 1 }]]
};

3.2 构造端点(endpoint):怎样将端点添加到div的四周?

     jsPlumb.addEndpoint(id, { anchors: "TopCenter" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "RightMiddle" }, hollowCircle);
jsPlumb.addEndpoint(id, { anchors: "BottomCenter" }, hollowCircle);
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的右上角会出现一个红色的删除图标,鼠标移走就消失。如下图:

我是通过以下代码实现的:

         $("#right").on("mouseenter", ".node", function () {
$(this).append('<img src="../../resources/images/close2.png" style="position: absolute;" />');
if ($(this).text() == "开始" || $(this).text() == "结束") {
$("img").css("left", 158).css("top", 0);
} else {
$("img").css("left", 158).css("top", -10);
}
});
$("#right").on("mouseleave", ".node", function () {
$("img").remove();
});

我想在这里大家都有疑问吧,为什么用on()事件委托。因为<img />是后添加进来的元素,前面页面已经完成了初始化,所以你用$("img")根本找不到这个元素,

因为img是在页面初始化后,才添加的元素。这里就提到了live()为什么不用这个,jquery1.7.2才有这个方法,这里用的是jquery1.11.1 已经没有live()方法了,

取而代之的是on()方法。(live()有许多缺点,所以在新的版本被摒弃了)

后面删除比较简单:

     $("#right").on("click", "img",function () {
if (confirm("确定要删除吗?")) {
jsPlumb.removeAllEndpoints($(this).parent().attr("id"));
$(this).parent().remove(); }
});

注明:这里我遇到一个问题,你删除了那个div,你还得把它周围的4个端点(endpoint)删除,这个问题刚开始我想了很多,一直没做出来,后来去jsPlumb官网查看相关的资料,

发现jsPlumb提供一个方法能删除div四周的端点。方法如下:

 jsPlumb.removeAllEndpoints($(this).parent().attr("id"));//删除指定id的所有端点

5.支持删除连接线:

     jsPlumb.bind("click", function (conn, originalEvent) {
if (confirm("确定删除吗? "))
jsPlumb.detach(conn);
});

6. 能双击修改流程图的文字:

     function doubleclick(id) {
$(id).dblclick(function () {
var text = $(this).text();
$(this).html("");
$(this).append("<input type='text' value='" + text + "' />");
$(this).mouseleave(function () {
$(this).html($("input[type='text']").val());
});
});
}

7.能序列化保存流程图:

我的思路是这样的,将中间div里所有的"流程图div信息和连接线两端的信息"保存到数组里,然后序列化成json数据,通过ajax传到asp.net 后台,将json写入到txt文档里保存到服务器端。

(其实保存到数据库里是最好的,后面会考虑保存到数据库),下次展示页面的时候,只要读取txt文档里的json,然后再转成泛型集合。

将页面上的div信息,和连线信息转成json跳转到ajax.aspx页面:

     function save() {
var connects = [];
$.each(jsPlumb.getAllConnections(), function (idx, connection) {
connects.push({
ConnectionId: connection.id,
PageSourceId: connection.sourceId,
PageTargetId: connection.targetId,
SourceText: connection.source.innerText,
TargetText: connection.target.innerText,
});
});
var blocks = [];
$("#right .node").each(function (idx, elem) {
var $elem = $(elem);
blocks.push({
BlockId: $elem.attr('id'),
BlockContent: $elem.html(),
BlockX: parseInt($elem.css("left"), 10),
BlockY: parseInt($elem.css("top"), 10)
});
}); var serliza = JSON.stringify(connects) + "&" + JSON.stringify(blocks);
$.ajax({
type: "post",
url: "ajax.aspx",
data: { id: serliza },
success: function (filePath) {
window.open("show-flowChart.aspx?path=" + filePath);
}
});
}

ajax.aspx页面将前台传过来的json保存到服务器端,并跳转至 show-flowChart.aspx:

   protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
string str = Request["id"];
string filePath = Server.MapPath("~/prototype/project-reply")+"\\json"+DateTime.Now.ToString("yyyyMMddhhmmss")+".txt";
WriteToFile(filePath,str,false);
//Response.Redirect("show-flowChart.aspx?path="+filePath);
Response.Write(filePath);
}
}
public static void WriteToFile(string name, string content, bool isCover)
{
FileStream fs = null;
try
{
if (!isCover && File.Exists(name))
{
fs = new FileStream(name, FileMode.Append, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
sw.WriteLine(content);
sw.Flush();
sw.Close();
}
else
{
File.WriteAllText(name, content, Encoding.UTF8);
}
}
finally
{
if (fs != null)
{
fs.Close();
}
} }

show-flowChart.aspx页面:

 protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
string str = Request["path"];
StreamReader sr = new StreamReader(str);
string jsonText = sr.ReadToEnd(); List<JsPlumbConnect> list = new JavaScriptSerializer().Deserialize<List<JsPlumbConnect>>(jsonText.Split('&')[]);
List<JsPlumbBlock> blocks = new JavaScriptSerializer().Deserialize<List<JsPlumbBlock>>(jsonText.Split('&')[]);
string htmlText = "";
string conn = "";
if (blocks.Count > )
{
foreach (JsPlumbBlock block in blocks)
{
if(block.BlockContent=="开始"||block.BlockContent=="结束")
htmlText += "<div class='node radius' id='" + block.BlockId + "'style='left:"+block.BlockX+"px;top:"+block.BlockY+"px;' >" + block.BlockContent + "</div>";
else
htmlText += "<div class='node' id='" + block.BlockId + "'style='left:" + block.BlockX + "px;top:" + block.BlockY + "px;' >" + block.BlockContent + "</div>";
}
foreach (JsPlumbConnect jsplum in list)
conn += "jsPlumb.connect({ source: \"" + jsplum.PageSourceId + "\", target: \"" + jsplum.PageTargetId + "\" }, flowConnector);";
Literal1.Text = htmlText;
string script = "jsPlumb.ready(function () {" + conn + "});";
ClientScript.RegisterStartupScript(this.GetType(), "myscript", script, true);
}
}
}

以及两个用到的类JsPlumbConnect类和JsPlumbBlock类:

 /// <summary>
/// 连接线信息
/// </summary>
public class JsPlumbConnect
{
public string ConnectionId { get; set; }
public string PageSourceId { get; set; }
public string PageTargetId { get; set; }
public string SourceText { get; set; }
public string TargetText { get; set; }
}
/// <summary>
/// 流程图的所有div
/// </summary>
public class JsPlumbBlock
{
/// <summary>
/// div Id
/// </summary>
public string BlockId { get; set; }
/// <summary>
/// div里面的内容
/// </summary>
public string BlockContent { get; set; }
public int BlockX { get; set; }
public int BlockY { get; set; }
}

结尾

转载请注明出处,谢谢!

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

jsPlumb插件做一个模仿viso的可拖拉流程图的更多相关文章

  1. [转]jsPlumb插件做一个模仿viso的可拖拉流程图

    原贴:https://www.cnblogs.com/sggx/p/3836432.html 前言 这是我第一次写博客,心情还是有点小小的激动!这次主要分享的是用jsPlumb,做一个可以给用户自定义 ...

  2. 用bootstrap的tab插件做一个图层切换效果(感觉会误导淫们,大家当乐子看吧)

    小伙伴们啊,我JS真的是个渣渣,所以总想要偷懒,于是为了实现效果就把tab插件给改了(各位大神轻拍啊,我是小白,很纯洁滴,小心脏也很脆弱)…… 最近做的项目为了考虑以后的移动设备兼容性,所以用了Boo ...

  3. 扩展一个boot的插件—tooltip&做一个基于boot的表达验证

    在线演示 本地下载 (代码太多请查看原文) 加班,加班加班,我爱加班··· 我已经疯了,哦也. 这次发一个刚接触boot的时候用boot做的表单验证,我们扩展一下tooltip的插件,让他可以换颜色. ...

  4. [Js插件]使用JqueryUI的弹出框做一个“炫”的登录页面

    引言 查看项目代码的时候,发现项目中用到JqueryUi的弹出框,可拖拽,可设置模式对话框,就想着使用它弄一个登录页面. 弹出框 在Jquery Ui官网可定制下载弹出框,下载和弹出框下载相关的js文 ...

  5. 使用node.js做一个自用的天气插件

    var request = require('request') var url = 'http://www.baidu.com/home/xman/data/superload' var cooki ...

  6. 使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能。并且在界面上有radio 的选择内容也要上传

    使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能.并且在界面上有radio 的选择内容也要上传 uploadify 插件的 下载和文档地址  ...

  7. jquery做一个小的轮播插件---有BUG,后续修改

    //首页无缝轮播 ; (function($, window, document, undefined) { $.fn.slider = function(options) { var default ...

  8. 使用jsPlumb插件实现动态连线功能

    这周去看了两天的羽毛球亚锦赛,工作有提前晚上加班做一些,但是技术文章却拉下了. 这段时间一直在寻找可以实现前端元素动态连线的功能,找了好几个库,考虑过用d3或者原生svg和canvas来实现,最后和同 ...

  9. 使用DB4o做一个.Net版的website(一)环境

    一个机缘巧合之下,知道了DB4o这个数据库引擎,下载查看之后,被其方便.高效.以及便捷的管理方式锁折服. 故决定使用其做一个.NET版本的web站点,来巩固学到的知识,以及为后来人做一点点贡献. 首先 ...

随机推荐

  1. Windows Server 2008 R2安装WAMPSERVER无法启动的解决方法

    其实根本不算什么解决方法,会者不难的事.Windows Server 2008 R2(也包括其他版本的Windows)默认状态下安装WAMPSERVER经常是无法顺利启动WAMPSERVER的,尤其是 ...

  2. Ajax详解及其案例分析------如何获得Ajax对象,使用Ajax对象发送GET和POST请求,校验用户名,POST和GET请求时的乱码处理,实现级联的下拉列表

    本节主要内容预览: 1 获得Ajax对象 2 使用Ajax对象发送GET请求 3 使用Ajax对象发送POST请求 4 使用Ajax校验用户名 5 POST请求时的乱码处理 6 GET请求时的乱码处理 ...

  3. Linux traceroute

    一.简介 traceroute 通过发送 TCP 数据包向目标端口进行探测,以检测源到目标服务器的整个链路上相应端口的连通性情况.   二.语法 -n 直接使用IP地址而非主机名称(禁用 DNS 反查 ...

  4. NOIP2008 普及组T3 传球游戏 解题报告-S.B.S.

    题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同 ...

  5. 八皇后,回溯与递归(Python实现)

    八皇后问题是十九世纪著名的数学家高斯1850年提出 .以下为python语句的八皇后代码,摘自<Python基础教程>,代码相对于其他语言,来得短小且一次性可以打印出92种结果.同时可以扩 ...

  6. Spring 一二事(4) - 单例

    spring bean配置后再默认情况下是单例的,如果需要配置可以选择 prototype, request, session和global session 在配置spring mvc的action时 ...

  7. 第22章 DLL注入和API拦截(2)

    22.4 使用远程线程来注入DLL 22.4.1 概述 (1)远程线程注入是指一个进程在另一个进程中创建线程,然后载入我们编写的DLL,并执行该DLL代码的技术.其基本思路是通过CreateRemot ...

  8. Ember模板中的操作指向

    模板中的链接操作指向有三个地方,该模板对应的控制器和路由以及视图,默认是先跳转到控制器,如果控制器里没有定义模板中动作的方法,就去该模板对应的路由里找,如果还没找到,就去父级路由找,直到顶级路由,如果 ...

  9. linux下安装php的imagick扩展模块(附php升级脚本)

    imagick是一个PHP的扩展,是一套软件系列,用ImageMagick提供的API来进行图片的创建与修改,不过这些操作已经包装到扩展imagick中去了,最终调用的是ImageMagick提供的A ...

  10. 使用CSS3制作72个webapp图标

    前言 移动网络带宽的快慢直接影响webapp应用体验效果的优差,其中加载图片是很耗流量的,所以对这一方面的性能优化是很需要的.一般对于那些小而多的图片(图标)都会采用sprite合并成一张图片来减少h ...