使用 C# ASP.NET MVC 进行 Javascript 流程图和工作流






4.95/5 (24投票s)
使用 JSPlumb 和 ASP MVC 创建流程图和工作流应用程序
引言
我们一早醒来就开始做决定——是赖在床上(周末,太好了!),还是起床(去上班,必须赶火车……)。一旦我们做了第一个决定,我们可能会采取行动(喝杯咖啡!),或者考虑另一个决定(我需要先去趟洗手间吗?)。随着一天的进展,我们的生活就是一连串的决定。在每个组织中也是如此。从某种意义上说,我们可以说生活以及它的走向,是基于无限可能性的流程状态:)
这篇文章是关于一个非常有用的 Javascript 库,称为“JS plumb”,以及它如何帮助我们在基于 .net 的 Web 应用程序中构建流程图或工作流风格的功能。正如您现在可能期望的那样,它并不局限于 .net,也不局限于 MVC,但在这篇文章中,我选择了专注于此。
背景
我经常遇到这种情况,我希望让用户在程序执行过程中拥有某种形式的决策能力。这可能基于一系列简单的硬编码状态(是/否,如果 X 则从 Y 操作列表中选择),或者可能更复杂,具有底层自动化系统,例如 Windows 工作流基础提供的系统。我一直很喜欢工作流基础的视觉方面,并怀念能够为最终用户提供我们在 Visual Studio 中拥有的那种出色的拖放式工作流构建器体验的能力。
当我遇到 JSPlumb 时,我立刻对它的可能性感到兴奋。它是一个非常灵活的库,可以管理画布上的对象以及它们之间的连接——它的重点是为流程图或工作流风格用户界面的“管道”部分提供解决方案。与许多此类库一样,有一个开源社区版,还有一个更高级的版本。后者被称为 JSPlumb Toolkit,并提供商业支持。JSPlumb 使用 SVG,并在从 IE9 及以上的所有浏览器上运行。
基础知识
在我们开始编写代码之前,让我们先看看流程图以及 JSPlumb 的基本可视组件是如何组合在一起的。
在上面的图中有许多您会熟悉的常见事物
- 工作流或流程图通常有一个起点
- 有连接事物的线条
- 我们有代表必须做出的决定和要执行的操作的形状
- 形状之间的连接方向通常由箭头表示,并带有某种文本注释。
如果我们放大一点来看这张图,JSPlumb 的定义如下:
视觉对象分为两组。有关的对象称为“源”和“目标”,然后是连接这些对象的“连接线”。每个源或目标可以有 0:M 个锚点位置。锚点是对象上连接线可以挂接的点。连接线允许对象相互链接。连接线连接到源或目标上任一端的锚点的端点——因为它们是双向的,所以没有开始或结束的“端点”,只有端点!
入门
首先要了解的是,该库使用一系列您定义的 DIV 对象。与其他 JavaScript 库不同,后者要求您定义一个 DIV 作为边界并将其用作画布进行绘制,JSPlumb 只是说——“告诉我哪些 DIV 是您的流程图形状,我会连接它们”。这很有道理,鉴于该库的名称——它不是 flowchartJS 或 WorkFlowJS,而是关于连接事物,它是字面意义上的“管道”!
为了演示这一点,让我们定义两个 DIV,然后使用 JSPlumb 将它们连接起来。首先是 DIV...
<div class="shape" id="shape1"></div>
<div class="shape" id="shape2"></div>
和一些样式
.shape {
float:left;
margin:10px;
width: 100px;
height: 100px;
border: solid 1px;
}
现在,魔法来了……
jsPlumb.ready(function() {
jsPlumb.connect({
source:"shape1",
target:"shape2"
});
});
就像我们可以指示 Javascript 在 DOM 加载完成后触发一样,在这种情况下,我们告诉 jsPlumb 一旦加载并准备就绪,就为我们的前两个形状执行“connect”方法。渲染后看起来是这样的……像笑脸一样!
我们不必定义连接、锚点或端点形状的位置——它使用了自己的默认值,“bottom”(底部)和“round”(圆形)。
Shapes
我们流程图的构建块是形状——我们将它们连接在一起,但形状本身通常代表某种含义。菱形或三角形表示决策,圆形表示汇聚点等。这完全取决于应用程序(考虑流程图与实体关系图与基于 Web 的工作流)。JSPlumb 不负责对形状进行样式设置,这取决于您和您老的朋友 CSS。无论您如何样式化,JSPlumb 都会进行连接。以下是一些您在流程图应用程序中可能经常看到的示例。
注意:您可以在 CSS Tricks 上找到一些很棒的 CSS 形状示例,要微调颜色,可以参考 HTMLColorCodes。
HTML 声明用于为每个 DIV 设置样式的 CLASS
<div id="shape1" class="shape square"></div>
<div id="shape2" class="shape circle"></div>
<div id="shape3" class="shape triangle-down"></div>
<div id="shape4" class="shape parallelogram"></div>
每个示例的 CSS 样式
.shape {
float:left;
margin:80px;
width: 100px;
height: 100px;
border: solid 1px;
}
.square {
width: 100px;
height: 100px;
background: #76D7C4;
}
.circle {
width: 100px;
height: 100px;
background: red;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
background: #F7DC6F
}
.triangle-down {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-top: 100px solid red;
}
.parallelogram {
width: 150px;
height: 100px;
-webkit-transform: skew(20deg);
-moz-transform: skew(20deg);
-o-transform: skew(20deg);
background: blue;
}
让我们看看我们还能对端点和连接做些什么。首先,让我们看看锚点,也就是连接线挂接在形状上的点。
锚点
通过设置 anchors 属性可以更改连接线的锚定位置
jsPlumb.ready(function() {
jsPlumb.connect({
source:"shape1",
target:"shape2",
anchor:"Top"
});
});
有效选项包括:
Top
(也别名为 `TopCenter`)TopRight
Right
(也别名为 `RightMiddle`)BottomRight
Bottom
(也别名为 `BottomCenter`)BottomLeft
Left
(也别名为 `LeftMiddle`)TopLeft
中心
一些示例
除了上面列出的静态锚点之外,您还可以使用其他选项,包括 动态和外围。后者很有趣,因为它们是根据您定义的源或目标 DIV 的形状类型动态创建的。例如,如果您有一个决策对象,您可能会选择一个菱形——在这种情况下,库会自动将锚点放在最合乎逻辑的位置,即菱形的角。使用例如圆/椭圆也有类似的选择——jsplumb 锚点的文档概述了您需要了解的关于这种灵活性的知识。
终结点
端点是连接线和锚点的交叉点。它们可以使用一些内置形状、图像或自定义 CSS 进行样式设置(是的,老好人 Bob 终于作为端点出现了……成功了!)。
您可以使用的不同端点类型如下:
-
Dot(点)这个端点在屏幕上绘制一个点。它支持三个构造函数参数:
radius
(半径)- 可选;默认为 10 像素。定义点的半径。cssClass
(CSS 类)- 可选。要附加到端点创建的元素的 CSS 类。hoverClass
(悬停类)- 可选。在鼠标悬停在元素或连接的连接线上时,要附加到端点创建的元素的 CSS 类。
-
Rectangle(矩形)绘制一个矩形。支持的构造函数参数为:
width
(宽度)可选;默认为 20 像素。定义矩形的宽度。height
(高度)可选;默认为 20 像素。定义矩形的高度。cssClass
(CSS 类)- 可选。要附加到端点创建的元素的 CSS 类。hoverClass
(悬停类)- 可选。在鼠标悬停在元素或连接的连接线上时,要附加到端点创建的元素的 CSS 类。
-
Image(图像)从给定 URL 绘制图像。此端点支持三个构造函数参数:
src
(源)必需。指定要使用的图像的 URL。cssClass
(CSS 类)- 可选。要附加到端点创建的元素的 CSS 类。hoverClass
(悬停类)- 可选。在鼠标悬停在元素或连接的连接线上时,要附加到端点创建的元素的 CSS 类。
-
Blank(空白)不向用户绘制任何可见内容。如果您希望用户能够拖动现有连接,那么这个端点可能不是您想要的——为此,请使用矩形或点端点,并为其分配一个使它透明的 CSS 类。
连接器
我们用于在形状之间创建链接的线条是连接线。与端点和锚点一样,它们有不同的选项可供我们使用。连接线会携带一些其他概念
- 锚点是连接线可以挂接在形状上的位置
- 端点是连接一端的可视表示,它们大多(但不总是)可见地附着在形状上,指示连接线可以挂接的位置
JSPlumb 有四种标准连接线——贝塞尔曲线(这是默认的)、直线、“流程图”模式和“状态机”模式。
连接线及其选项通常使用 `connector` 属性在调用以下任何方法时指定:
jsPlumb.connect
jsPlumb.addEndpoint(s)
jsPlumb.makeSource
jsPlumb.makeTarget
以下是构建连接并设置渲染的连接类型的示例:
示例 1 - JSPlumb 连接线 - 具有不同曲线半径的贝塞尔曲线
jsPlumb.connect({
source:"shape1",
target:"shape2",
connector:[ "Bezier", { curviness:150 } ]
});
jsPlumb.connect({
source:"shape3",
target:"shape4",
connector:[ "Bezier", { curviness:50 } ]
});
示例 2 - JSPlumb 连接线 - 具有选项的直线和流程图连接线
jsPlumb.connect({
source:"shape1",
target:"shape2",
connector:[ "Straight" ],
anchors:["Top", "Bottom"],
endpoint:[ "Rectangle", { width:20, height:40 }]
});
jsPlumb.connect({
source:"shape3",
target:"shape4",
connector:[ "Flowchart", {cornerRadius:15}]
});
jsPlumb.connect({
source:"shape5",
target:"shape6",
connector:[ "Flowchart", {stub:10, gap:20}]
});
上面使用的选项是“cornerRadius”(圆角),它会在连接线连接时的拐角处形成一个曲线;“gap”(间隙),它在连接线和端点之间引入一个空间;以及“stub”(桩),它为连接线提供一个方形边缘。
除了讨论连接线选项之外,我们还有两个概念需要讨论:
- Overlays(覆盖层)是可用于装饰连接线的 UI 元素——例如,一个指示连接线方向的箭头,或者连接线本身的文本覆盖,描述连接。
- Groups(组)允许您将一组元素“分组”在一个父元素内,该元素可以折叠和展开——这在工作流图中有用,可以允许用户深入了解特定工作流流程的详细信息。
覆盖层
构成连接线的线条也有可用选项。其中有覆盖层。这些元素包括连接线上的显示文本和箭头。在下面的示例代码中,我们将对覆盖层做几件事情:添加一些文本并设置其样式,以及添加几种不同的箭头。
首先,让我们看看最终结果,然后看看它是如何构建的。
由于我希望将“Sample text”(样本文本)覆盖标签的样式设置委托给 CSS 类,因此我们将首先定义它。
.labelClass {
background-color:yellow;
padding:0.4em;
font:16px sans-serif;
color:#444;
z-index:21;
border:1px solid red;
}
现在我们来看 JSPlumb 设置代码。
jsPlumb.connect({
source:"shape1",
target:"shape2",
paintStyle: { strokeStyle: "orange", lineWidth: 5 },
connector:[ "Bezier", { curviness:100 } ],
overlays:[
[ "Arrow", { width:20, length:15, location:[.1], foldback: 1,
paintStyle: { strokeStyle: "red", lineWidth: 2 } }
],
[ "Arrow", { width:20, length:15, location:[.2],
paintStyle: { strokeStyle: "green", lineWidth: 2 } }
],
[ "Arrow", { width:20, length:15, location:[.3], direction:-1,
paintStyle: { strokeStyle: "blue", lineWidth: 2 } }
],
[ "Arrow", { width:50, length:30, location:[.9] } ],
[ "Label", {
label:"Sample text",
location:0.5,
id:"label",
cssClass:"labelClass"
}],
]
});
和以前一样,我们首先声明源和目标形状。
source:"shape1", target:"shape2"
接下来,我们定义主绘图样式。
paintStyle: { strokeStyle: "orange", lineWidth: 5 }
我们的连接线是贝塞尔曲线。
connector:[ "Bezier", { curviness:100 } ]
现在是覆盖层……
第一个是箭头。它的位置距离源 '.1'(10%)处。位置可以是 0 到 1 的比例值(因此在这种情况下,'.1' = 10%),或者是一个绝对值,负值表示到目标的距离,正值(大于 1)表示到源的距离。Foldback(回折)指的是箭头底部的折叠。负数会使其像钻石形状一样向外漂浮,正数则形成一个细长的尖锐箭头。我们稍后会看到一个示例。
"Arrow", { width:20, length:15, location:[.1], foldback: 1,
paintStyle: { strokeStyle: "red", lineWidth: 2 } }
下一个箭头的主要特点是它使用了默认的回折,并且我们更改了笔触颜色。
[ "Arrow", { width:20, length:15, location:[.2],
paintStyle: { strokeStyle: "green", lineWidth: 2 } }
紧随 Mr. Foldback 之后的是 Ms.Reverse!……在这里,我们将“Direction”(方向)属性设置为“-1”。默认值为“1”。
[ "Arrow", { width:20, length:15, location:[.3], direction:-1,
paintStyle: { strokeStyle: "blue", lineWidth: 2 } }
],
此示例中的最后一个箭头是个大头——我们通过调整宽度和长度来更改其尺寸。
[ "Arrow", { width:50, length:30, location:[.9] } ]
插入文本非常简单。您可能还记得我们之前定义了一些 CSS 来设置标签的样式——这里就是我们调用它的地方。
[ "Label", {
label:"Sample text",
location:0.5,
id:"label",
cssClass:"labelClass"
}]
以上代码的关键部分是“label”(标签),它提供您希望显示的文本,以及我们希望用于样式的 cssClass(CSS 类)。
下面通过另一个示例更详细地看看箭头是如何工作的,您可以根据下面的代码了解每个箭头的设计方式。
jsPlumb.connect({
source:"shape3",
target:"shape4",
connector:[ "Straight"],
anchors: ["Right", "Left"],
paintStyle: { strokeStyle: "orange", lineWidth: 1 },
overlays:[
[ "Arrow", { width:30, length:25, location:[.2], foldback: 1,
paintStyle: { strokeStyle: "red", lineWidth: 2 } }
],
[ "Arrow", { width:20, length:15, location:[.4],
paintStyle: { strokeStyle: "green", lineWidth: 1 } }
],
[ "Arrow", { width:20, length:15, location:[.65], foldback: 2,
paintStyle: { strokeStyle: "red", lineWidth: 8 } }
],
[ "Arrow", { width:20, length:15, location:[.8], direction:-1,
paintStyle: { strokeStyle: "blue", lineWidth: 2 } }
]
]
});
组
该库有一个称为“Groups”(组)的概念,其工作方式与您的预期基本相同,但有一个额外的优点。分组允许您将一组形状分组到一个父容器的范围内。这些子形状到您 UI 上其他形状的任何连接都会保留。现在有两个很酷的地方需要注意:
- 当您拖动/移动父/组容器形状时,它内部的所有子形状都会随之移动。
- 您可以折叠和展开“父”组容器,当您这样做时,先前在视觉上连接到其他形状的任何连接线都会“代理”到组的折叠元素上。
这是显示分组的屏幕截图。
其他有用功能
常用选项
很多时候,您可能会发现自己在设置端点样式、连接线等方面重复劳动。在复杂的应用程序中,您可能希望一种元素有一套样式,而另一种元素有另一套样式。为了解决这个问题,我们可以将一个普通的 Javascript 对象作为“常用选项”传递给 Endpoint、Connector、Anchor 和 Overlay 方法。
var commonEndpoint = {
stub:"90",
gap:"15"
};
jsPlumb.connect({
source:"shape5",
target:"shape6",
connector:[ "Flowchart", commonEndpoint]
});
拖放
另一个有用且我认为至关重要的流程图/图表实用工具功能是能够将元素拖放到画布上。通过向元素引入“draggable”(可拖动)方法来启动拖放。
<code data-lang="javascript">jsPlumbInstance.draggable($(".someClass"));</code>
有关拖放的更多文档:https://jsplumbtoolkit.com/community/doc/dragging.html
吸附到网格
为了获得更好、更有条理的布局,如果您需要,能够将画布上的元素“吸附”到一个网格上会很有用。
有一个非常有用的网站展示了一种进行此操作以及其他操作的好技术,位于“Free developer tutorials”。
保存和加载
能够保存用户的工作并将其重新加载回来非常重要,有许多解决方案可用(搜索一下就知道了!)……来自 stack-overflow 的这个解决方案正如它所说的那样有效……
整合——MVC 的流程图
我(附加在本文顶部)准备了一个非常基础的演示(基于上面引用的 stack overflow 代码)。它允许我们通过单击按钮添加不同类型的流程图元素,并将图表保存到本地文本区域以及保存到我们的 MVC 控制器。结合上面对不同功能的介绍,它应该足以让您开始!
步骤
(1)通过单击按钮添加图表元素,或通过单击“从服务器加载”加载简单的示例图表
(2)从服务器加载的结果
祝您制图愉快,别忘了,如果您喜欢这篇文章,请投一票!
历史
2017/11/08 - 版本 1