├── MIT-LICENSE.txt
├── README.md
├── demo.html
├── flowchart.jquery.json
├── jquery.flowchart.css
├── jquery.flowchart.js
├── jquery.flowchart.min.css
├── jquery.flowchart.min.js
└── package.json
/MIT-LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2016 Sebastien Drouyer
2 | https://github.com/sdrdis/jquery.flowchart
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | jquery.flowchart
2 | ================
3 |
4 | Javascript jQuery plugin that allows you to draw a flow chart. Take a look at the demo:
5 | http://sebastien.drouyer.com/jquery.flowchart-demo/
6 |
7 | Description
8 | -----------
9 |
10 | jquery.flowchart.js is an open source javascript jQuery ui plugin that allows you to draw and edit a flow chart.
11 |
12 | Here are the main functionalities provided so far:
13 | * Draw boxes (called operators) and connections between them.
14 | * Methods are provided so that the end-user can edit the flow chart by adding / moving / removing operators, creating / removing connections between them.
15 | * The developper can save / load the flowchart.
16 | * Operators and links can be customized using CSS and the plugin parameters.
17 | * Some methods allow you to add advanced functionalities, such as a panzoom view or adding operators using drag and drop. Take a look at the [advanced demo](http://sebastien.drouyer.com/jquery.flowchart-demo/#advanced).
18 |
19 | Context
20 | -------
21 |
22 | This project is part of a bigger one, [UltIDE](https://github.com/ultide/ultide) which allows you to have a complete interface managing a flowchart and custom properties. Though it is still WIP, you are welcome to give it a try and contribute. A screenshot is shown below.
23 |
24 | 
25 |
26 | License
27 | -------
28 | jquery.flowchart.js is under [MIT license](https://github.com/sdrdis/jquery.flowchart/blob/master/MIT-LICENSE.txt).
29 |
30 | Authors
31 | -------
32 | * [Sebastien Drouyer](http://sebastien.drouyer.com) - alias [@sdrdis](https://twitter.com/sdrdis) - for this jquery ui plugin
33 |
34 | Contributors
35 | ------------
36 | * Simone Gasparini - alias [@simmyg89](https://github.com/simmyg89) - for bug fixes and code formatting.
37 | * Guijin Ding - alias [@dingguijin](https://github.com/dingguijin) - for bug fixes.
38 | * M. Fatih Marabaoğlu - alias [@MFatihMAR](https://github.com/MFatihMAR) - for adding the uncontained parameter and improving the grid system.
39 | * Peter Vavro - alias [@petervavro](https://github.com/petervavro) - for adding mouse events.
40 | * Mike Branham - alias [@Mike-Branham](https://github.com/Mike-Branham) - for bug fixes in the demo page.
41 | * [@zhangbg](https://github.com/zhangbg) - for compatibility with IE9, IE10 and IE11.
42 | * [@elvaron](https://github.com/elvaron) - for bug fixes and adding the getLinksFrom and getLinksTo methods.
43 | * Ziyi Wang - alias [@ziyiwang](https://github.com/ziyiwang) - for bug fixes.
44 | * [@dogbull](https://github.com/dogbull) - for adding the getDataRef method.
45 | * Yaroslav Zenin - alias [@yaroslav-zenin](https://github.com/yaroslav-zenin) - for big fixes.
46 | * [@lflfm](https://github.com/lflfm) - for bug fixes and new demo page.
47 | * [@neoera](https://github.com/neoera) - for adding multiple sub connector with array support.
48 | * Dima Shemendiuk - alias [@dshemendiuk](https://github.com/dshemendiuk) - for adding vertical flowchart support and access to operators's body.
49 | * Ernani Azevedo - alias [@ernaniaz](https://github.com/ernaniaz) - for adding the possibility to decide for each connector if there can be a single link and multiple links and for making the integration of features from the community much easier.
50 |
51 | Documentation
52 | -------------
53 |
54 | ### Demo:
55 |
56 | http://sebastien.drouyer.com/jquery.flowchart-demo/
57 |
58 | ### Terminology:
59 |
60 | 
61 |
62 | ### Options:
63 |
64 | * __canUserEditLinks (default: true):__ Can the user add links by clicking on connectors. Note that links can be removed during the process if `multipleLinksOnInput`of `multipleLinksOnOutput`are set to false.
65 |
66 | * __canUserMoveOperators (default: true):__ Can the user move operators using drag and drop.
67 |
68 | * __data (default: `{}`):__ Initialization data defining the flow chart operators and links between them.
69 |
70 | * __operators:__ Hash defining the operators in your flow chart. The keys define the operators ID and the value define each operator's information as follow:
71 | * __top (in px)__
72 | * __left (in px)__
73 | * __type__: (optional) The type of the operator. See `data.operatorTypes`.
74 | * __properties:__
75 | * __title__
76 | * __body__
77 | * __uncontained:__ (optional, default: `false`) If `true`, the operator can be moved outside the container.
78 | * __class:__ css classes added to the operator DOM object. If undefined, default value is the same as `defaultOperatorClass`.
79 | * __inputs:__ Hash defining the box's input connectors. The keys define the connectors ID and the values define each connector's information as follow:
80 | * __label__: Label of the connector. If the connector is __multiple__, '(:i)' is replaced by the subconnector ID.
81 | * __multipleLinks__: (optional) If `true`, allow multiple links to this connector.
82 | * __multiple__: (optional) If `true`, whenever a link is created on the connector, another connector (called subconnector) is created. See the [multiple connectors demo](http://sebastien.drouyer.com/jquery.flowchart-demo/#multiple).
83 | * __outputs:__ Hash defining the box's output connectors. Same structure as `inputs`.
84 |
85 | * __links:__ Hash defining the links between your operators in your flow chart. The keys define the link ID and the value define each link's information as follow:
86 | * __fromOperator:__ ID of the operator the link comes from.
87 | * __fromConnector:__ ID of the connector the link comes from.
88 | * __fromSubConnector:__ (optional) If it is a multiple connector, which subconnector is it.
89 | * __toOperator:__ ID of the operator the link goes to.
90 | * __toConnector:__ ID of the connector the link goes to.
91 | * __toSubConnector:__ (optional) If it is a multiple connector, which subconnector is it.
92 | * __color:__ Color of the link. If undefined, default value is the same as `defaultLinkColor`.
93 |
94 | * __operatorTypes:__ (optional) Hash allowing you to define common operator types in order to not repeat the properties key. Key define the operator's type ID and the value define the properties (same structure as `data.operators.properties`).
95 |
96 | * __verticalConnection (default: false):__ Allows to build vertical-connected flowcharts. __WARNING:__ When using vertical connectors, avoid using multiple connectors, because it will break layout.
97 |
98 | * __distanceFromArrow (default: 3):__ Distance between the output connector and the link.
99 |
100 | * __defaultLinkColor (default: '#3366ff'):__ Default color of links.
101 |
102 | * __defaultSelectedLinkColor (default: 'black'):__ Default color of links when selected.
103 |
104 | * __defaultOperatorClass (default: 'flowchart-default-operator'):__ Default class of the operator DOM element.
105 |
106 | * __linkWidth (default: 11):__ Width of the links.
107 |
108 | * __grid (default: 20):__ Grid of the operators when moved. If its value is set to 0, the grid is disabled.
109 |
110 | * __multipleLinksOnInput (default: false):__ Allows multiple links on the same input connector.
111 |
112 | * __multipleLinksOnOutput (default: false):__ Allows multiple links on the same output connector.
113 |
114 | * __linkVerticalDecal (default: 0):__ Allows to vertical decal the links (in case you override the CSS and links are not aligned with their connectors anymore).
115 |
116 | * __onOperatorSelect (default: function returning true):__ Callback method. Called when an operator is selected. It should return a boolean. Returning `false` cancels the selection. Parameters are:
117 | * __operatorId:__ ID of the operator.
118 |
119 | * __onOperatorUnselect (default: function returning true):__ Callback method. Called when an operator is unselected. It should return a boolean. Returning `false` cancels the unselection.
120 |
121 | * __onOperatorMouseOver (default: function returning true):__ Callback method. Called when the mouse pointer enters an operator. It should return a boolean. Returning `false` cancels the selection. Parameters are:
122 | * __operatorId:__ ID of the operator.
123 |
124 | * __onOperatorMouseOut (default: function returning true):__ Callback method. Called when the mouse pointer leaves an operator. It should return a boolean. Returning `false` cancels the unselection.
125 |
126 | * __onLinkSelect (default: function returning true):__ Callback method. Called when a link is selected. It should return a boolean. Returning `false` cancels the selection. Parameters are:
127 | * __linkId:__ ID of the link.
128 |
129 | * __onLinkUnselect (default: function returning true):__ Callback method. Called when a link is unselected. It should return a boolean. Returning `false` cancels the unselection.
130 |
131 | * __onOperatorCreate (default: function returning true):__ Callback method. Called when an operator is created. It should return a boolean. Returning `false` cancels the creation. Parameters are:
132 | * __operatorId:__ ID of the operator.
133 | * __operatorData:__ Data of the operator.
134 | * __fullElement:__ Hash containing DOM elements of the operator. It contains:
135 | * __operator:__ the DOM element of the whole operator.
136 | * __title:__ the DOM element of the operator's title.
137 | * __connectorArrows:__ the DOM element of the connectors' arrows.
138 | * __connectorSmallArrows:__ the DOM element of the connectors' small arrows.
139 |
140 | * __onOperatorDelete (default: function returning true):__ Callback method. Called when an operator is deleted. It should return a boolean. Returning `false` cancels the deletion. Parameters are:
141 | * __operatorId:__ ID of the operator.
142 |
143 | * __onLinkCreate (default: function returning true):__ Callback method. Called when a link is created. It should return a boolean. Returning `false` cancels the creation. Parameters are:
144 | * __linkId:__ ID of the link.
145 | * __linkData:__ Data of the link.
146 |
147 | * __onLinkDelete (default: function returning true):__ Callback method. Called when a link is deleted. It should return a boolean. Returning `false` cancels the deletion. Parameters are:
148 | * __linkId:__ ID of the link.
149 | * __forced:__ The link deletion can not be cancelled since it happens during an operator deletion.
150 |
151 | * __onOperatorMoved (default: function):__ Callback method. Called when an operator is moved. Parameters are:
152 | * __operatorId:__ ID of the operator.
153 | * __position:__ New position of the operator.
154 |
155 | * __onAfterChange (default: function):__ Callback method. Called after changes have been done (operator creation for instance). Parameters are:
156 | * __changeType:__ What change did occur. Can be one of these strings:
157 | * operator_create
158 | * link_create
159 | * operator_delete
160 | * link_delete
161 | * operator_moved
162 | * operator_title_change
163 | * operator_body_change
164 | * operator_data_change
165 | * link_change_main_color
166 |
167 | ### Events
168 |
169 | All callbacks (options with a name that begins with "on") have their event counterpart. For instance, the callback
170 | `onOperatorSelect(operatorId)` has an equivalent event that can be handled using
171 | `$(flowchartEl).on('operatorSelect', function(el, operatorId, returnHash) { /* your code here */ })`, where
172 | `flowchartEl` is the DOM element of the flowchart.
173 |
174 | If `onOperatorSelect` doesn't return `false`, the event `operatorSelect` is triggered, and the final return value
175 | will be `returnHash['result']`. The behaviour is similar for all callbacks.
176 |
177 | ### Functions
178 | #### Operators:
179 | * __createOperator(operatorId, operatorData):__
180 | * __Parameters:__
181 | * __operatorId__
182 | * __operatorData:__ Same as in `data.operators`.
183 |
184 | * __addOperator(operatorData):__
185 | * __Description:__ Same as `createOperator`, but automatically sets the operator's ID.
186 | * __Parameters:__
187 | * __operatorData:__ Same as in `data.operators`.
188 | * __Return:__ The operator's ID.
189 |
190 | * __deleteOperator(operatorId):__
191 | * __Parameters:__
192 | * __operatorId__
193 |
194 | * __getSelectedOperatorId():__
195 | * __Return:__ The operator ID if one is selected, `null` otherwise.
196 |
197 | * __selectOperator(operatorId):__
198 | * __Parameters:__
199 | * __operatorId__
200 |
201 | * __unselectOperator():__
202 |
203 | * __addClassOperator(operatorId, className):__
204 | * __Parameters:__
205 | * __operatorId__
206 | * __className__: Class name to add.
207 |
208 | * __removeClassOperator(operatorId, className):__
209 | * __Parameters:__
210 | * __operatorId__
211 | * __className__: Class name to remove.
212 |
213 | * __removeClassOperators(className):__
214 | * __Parameters:__
215 | * __className__: Remove class name from all operators.
216 |
217 | * __setOperatorTitle(operatorId, title):__
218 | * __Parameters:__
219 | * __operatorId__
220 | * __title:__ The operator's new title to be set.
221 |
222 | * __setOperatorBody(operatorId, body):__
223 | * __Parameters:__
224 | * __operatorId__
225 | * __body:__ The operator's new body html to be set.
226 |
227 | * __getOperatorTitle(operatorId):__
228 | * __Parameters:__
229 | * __operatorId__
230 | * __Return:__ The operator's title.
231 |
232 | * __getOperatorBody(operatorId):__
233 | * __Parameters:__
234 | * __operatorId__
235 | * __Return:__ The operator's body.
236 |
237 | * __doesOperatorExists(operatorId):__
238 | * __Description:__ This method checks whether or not an operator with id equal to `operatorId` exists.
239 | * __Parameters:__
240 | * __operatorId__
241 |
242 | * __setOperatorData(operatorId, operatorData):__
243 | * __Description:__ This method replaces the operator's data. Note that if new connectors are renamed / removed, the flowchart can remove links.
244 | * __Parameters:__
245 | * __operatorId__
246 | * __operatorData__: Same as in `data.operators`.
247 |
248 | * __getOperatorData(operatorId):__
249 | * __Parameters:__
250 | * __operatorId__
251 | * __Return:__ The operator's data. Same as in `data.operators`.
252 |
253 | * __getConnectorPosition(operatorId, connectorId):__
254 | * __Parameters:__
255 | * __operatorId__
256 | * __connectorId__
257 | * __Return:__ The connector's position relative to the container.
258 |
259 | * __getOperatorCompleteData(operatorData):__
260 | * __Parameters:__
261 | * __operatorData:__ The operator's data. Same as in `data.operators`.
262 | * __Return:__ Completes the operator's data with default values if some keys are not defined (like `class` for instance).
263 |
264 | * __getOperatorElement(operatorData):__
265 | * __Parameters:__
266 | * __operatorData:__ The operator's data. Same as in `data.operators`.
267 | * __Return:__ The operator's DOM element (jquery). The element is not added in the container. It can be used to preview the operator or showing it during a drag and drop operation.
268 |
269 | * __getLinksFrom(operatorId):__
270 | * __Parameters:__
271 | * __operatorId:__ The operator's Id.
272 | * __Return:__ Array with all links from the provided operator.
273 |
274 | * __getLinksTo(operatorId):__
275 | * __Parameters:__
276 | * __operatorId:__ The operator's Id.
277 | * __Return:__ Array with all links to the provided operator.
278 |
279 | * __getOperatorFullProperties(operatorData):__
280 | * __Parameters:__
281 | * __operatorData:__ The operator's data. Same as in `data.operators`.
282 | * __Return:__ If not operator type is defined, the `property` key. Otherwise, the `property` key extended with the operator's type properties.
283 |
284 |
285 | #### Links:
286 | * __createLink(linkId, linkData):__
287 | * __Parameters:__
288 | * __linkId__
289 | * __linkData:__ Same as in `data.links`.
290 |
291 | * __addLink(linkData):__
292 | * __Description:__ Same as `createLinks`, but automatically sets the link's ID.
293 | * __Parameters:__
294 | * __linkData:__ Same as in `data.links`.
295 | * __Return:__ The link's ID.
296 |
297 | * __deleteLink(linkId):__
298 | * __Parameters:__
299 | * __linkId__
300 |
301 | * __getSelectedLinkId():__
302 | * __Return:__ The link ID if one is selected, `null` otherwise.
303 |
304 | * __selectLink(linkId):__
305 | * __Parameters:__
306 | * __linkId__
307 |
308 | * __unselectLink()__
309 |
310 | * __setLinkMainColor(linkId, color):__
311 | * __Parameters:__
312 | * __linkId__
313 | * __color__
314 |
315 | * __getLinkMainColor(linkId):__
316 | * __Parameters:__
317 | * __linkId__
318 | * __Returns:__ The link's color.
319 |
320 | * __colorizeLink(linkId, color):__
321 | * __Description:__ Sets the link a temporary color contrary to `setLinkMainColor`. It can be used to temporarly highlight a link for instance.
322 | * __Parameters:__
323 | * __linkId__
324 | * __color__
325 |
326 | * __uncolorizeLink(linkId):__
327 | * __Description:__ Sets the link color back to its main color.
328 | * __Parameters:__
329 | * __linkId__
330 |
331 | * __redrawLinksLayer():__
332 | * __Description:__ Redraws all the links.
333 |
334 |
335 |
336 | #### Misc:
337 | * __getData():__
338 | * __Return:__ The flow chart's data. Same structure as the `data` option.
339 |
340 | * __setData(data):__
341 | * __Parameters:__
342 | * __data:__ Same structure as the `data` option.
343 |
344 | * __getDataRef():__
345 | * __Return:__ The internal system flow chart's data.
346 |
347 | * __setPositionRatio(positionRatio):__
348 | * __Parameters:__
349 | * __positionRatio:__ The ratio between the mouse position and the position of the DOM elements. Used when drag and dropping the operators. You should use it if you zoom the container. See the [advanced demo](http://sebastien.drouyer.com/jquery.flowchart-demo/#advanced).
350 |
351 | * __getPositionRatio():__
352 | * __Return:__ The position ratio.
353 |
354 | * __deleteSelected():__
355 | * __Description:__ Deletes the link or operator selected.
356 |
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Home
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
27 |
28 |
29 |
30 | Semi-minimalist usage sample
31 | Sample created with basis on some from Sebastien Drouyer, original author's website .
32 | Flowchart
33 |
36 |
37 |
38 | Operators (drag and drop them in the flowchart):
39 |
40 |
41 |
1 input
42 |
1 output
43 |
1 input & 1 output
44 |
1 in & 2 out
45 |
2 in & 1 out
46 |
2 in & 2 out
47 |
48 |
49 | Create operator
50 | Delete selected operator / link
51 |
52 | Operator's title:
53 |
54 |
55 | Link's color:
56 |
57 | Get data
58 | Set data
59 | Save to local storage
60 | Load from local storage
61 |
62 |
63 |
64 |
345 |
346 |
347 |
348 |
--------------------------------------------------------------------------------
/flowchart.jquery.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery.flowchart",
3 | "description": "JQuery UI plugin that allows you to draw a flow chart.",
4 | "version": "1.0.0",
5 | "licenses": [
6 | {
7 | "type": "MIT",
8 | "url": "https://github.com/sdrdis/jquery.flowchart/blob/master/MIT-LICENSE.txt"
9 | }
10 | ],
11 | "title": "Flowchart",
12 | "keywords": [
13 | "flowchart",
14 | "flow",
15 | "chart",
16 | "graph",
17 | "ui"
18 | ],
19 | "author": {
20 | "url": "http://sebastien.drouyer.com",
21 | "name": "Sébastien Drouyer"
22 | },
23 | "maintainers": [
24 | {
25 | "name": "Sébastien Drouyer",
26 | "url": "http://sebastien.drouyer.com"
27 | }
28 | ],
29 | "download": "https://github.com/sdrdis/jquery.flowchart/releases",
30 | "homepage": "http://sebastien.drouyer.com/jquery.flowchart-demo/",
31 | "demo": "http://sebastien.drouyer.com/jquery.flowchart-demo/",
32 | "docs": "https://github.com/sdrdis/jquery.flowchart/",
33 | "bugs": "https://github.com/sdrdis/jquery.flowchart/issues",
34 | "dependencies": {
35 | "jquery": ">=1.10",
36 | "jquery-ui": ">=1.11"
37 | }
38 | }
--------------------------------------------------------------------------------
/jquery.flowchart.css:
--------------------------------------------------------------------------------
1 | /*
2 | * jquery.flowchart - CSS definitions
3 | */
4 |
5 | .flowchart-container {
6 | position: relative;
7 | overflow: hidden;
8 | }
9 |
10 | .flowchart-links-layer, .flowchart-operators-layer, .flowchart-temporary-link-layer {
11 | position: absolute;
12 | top: 0px;
13 | left: 0px;
14 | width: 100%;
15 | height: 100%;
16 | }
17 |
18 | .flowchart-operators-layer, .flowchart-temporary-link-layer {
19 | pointer-events: none;
20 | }
21 |
22 | .flowchart-temporary-link-layer {
23 | display: none;
24 | }
25 |
26 | .flowchart-link, .flowchart-operator {
27 | cursor: default;
28 | }
29 |
30 | .flowchart-operator-connector {
31 | position: relative;
32 | padding-top: 5px;
33 | padding-bottom: 5px;
34 | }
35 |
36 | .flowchart-operator-connector-label {
37 | font-size: small;
38 | }
39 |
40 | .flowchart-operator-inputs .flowchart-operator-connector-label {
41 | margin-left: 14px;
42 | }
43 |
44 | .flowchart-operator-outputs .flowchart-operator-connector-label {
45 | text-align: right;
46 | margin-right: 5px;
47 | }
48 |
49 | .flowchart-operator-connector-arrow {
50 | width: 0px;
51 | height: 0px;
52 | border-top: 10px solid transparent;
53 | border-bottom: 10px solid transparent;
54 | border-left: 10px solid rgb(204, 204, 204);
55 | position: absolute;
56 | top: 0px;
57 | }
58 |
59 | .flowchart-operator-connector-small-arrow {
60 | width: 0px;
61 | height: 0px;
62 | border-top: 5px solid transparent;
63 | border-bottom: 5px solid transparent;
64 | border-left: 5px solid transparent; /*rgb(100, 100, 100);*/
65 | position: absolute;
66 | top: 5px;
67 | pointer-events: none;
68 | }
69 |
70 | .flowchart-operator-connector:hover .flowchart-operator-connector-arrow {
71 | border-left: 10px solid rgb(153, 153, 153);
72 | }
73 |
74 | .flowchart-operator-inputs .flowchart-operator-connector-arrow {
75 | left: -1px;
76 | }
77 |
78 | .flowchart-operator-outputs .flowchart-operator-connector-arrow {
79 | right: -10px;
80 | }
81 |
82 | .flowchart-operator-inputs .flowchart-operator-connector-small-arrow {
83 | left: -1px;
84 | }
85 |
86 | .flowchart-operator-outputs .flowchart-operator-connector-small-arrow {
87 | right: -7px;
88 | }
89 |
90 | .unselectable {
91 | -moz-user-select: none;
92 | -khtml-user-select: none;
93 | -webkit-user-select: none;
94 |
95 | /*
96 | Introduced in IE 10.
97 | See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/
98 | */
99 | -ms-user-select: none;
100 | user-select: none;
101 | }
102 |
103 |
104 | /* Default Operator */
105 |
106 | .flowchart-operator {
107 | position: absolute;
108 | width: 140px;
109 | border: 1px solid #CCCCCC;
110 | background: #FAFAFA;
111 | pointer-events: auto;
112 | }
113 |
114 | .flowchart-operator.hover {
115 | border-color: #999;
116 | }
117 |
118 | .flowchart-operator.selected {
119 | border-color: #555;
120 | }
121 |
122 |
123 |
124 | .flowchart-operator .flowchart-operator-title {
125 | width: 100%;
126 | padding: 5px;
127 | font-weight: bold;
128 | box-sizing: border-box;
129 | border-bottom: 1px solid #DDDDDD;
130 | background: #F0F0F0;
131 | height: auto;
132 | overflow: hidden;
133 | text-overflow: ellipsis;
134 | white-space: nowrap;
135 | cursor: move;
136 | }
137 |
138 | .flowchart-operator .flowchart-operator-inputs-outputs {
139 | display: table;
140 | width: 100%;
141 | margin-top: 5px;
142 | margin-bottom: 5px;
143 | }
144 |
145 | .flowchart-operator .flowchart-operator-inputs, .flowchart-default-operator .flowchart-operator-outputs {
146 | display: table-cell;
147 | width: 50%;
148 | }
149 |
150 | /*
151 | * flowchart-vertical
152 | */
153 |
154 | .flowchart-vertical .flowchart-operator-inputs,
155 | .flowchart-vertical .flowchart-operator-outputs {
156 | position: relative;
157 | text-align: center;
158 | display: table;
159 | width: 100%;
160 | }
161 | .flowchart-vertical .flowchart-operator-connector-set {
162 | display: table-cell;
163 | }
164 | .flowchart-vertical .flowchart-operator-connector {
165 | position: relative;
166 | }
167 | .flowchart-vertical .flowchart-operator-connector-label {
168 | position: relative;
169 | text-align: center;
170 | width: 100%;
171 | }
172 | .flowchart-vertical .flowchart-operator-inputs .flowchart-operator-connector-label {
173 | margin-left: auto;
174 | }
175 | .flowchart-vertical .flowchart-operator-connector-arrow {
176 | border-left: 10px solid transparent;
177 | border-right: 10px solid transparent;
178 | border-top: 10px solid #ccc;
179 | left: calc(50% - 10px);
180 | }
181 | .flowchart-vertical .flowchart-operator-connector:hover .flowchart-operator-connector-arrow {
182 | border-left-color: transparent;
183 | border-top-color: #999;
184 | }
185 | .flowchart-vertical .flowchart-operator-connector-small-arrow {
186 | border-right: 5px solid transparent;
187 | top: 2px;
188 | left: calc(50% - 5px);
189 | }
190 | .flowchart-vertical .flowchart-operator-connector-arrow {
191 | top: 0px;
192 | }
193 | .flowchart-vertical .flowchart-operator-outputs .flowchart-operator-connector-arrow {
194 | bottom: -20px;
195 | top: auto;
196 | }
197 | .flowchart-vertical .flowchart-operator-outputs .flowchart-operator-connector-small-arrow {
198 | left: calc(50% - 5px);
199 | bottom: -12px;
200 | top: auto;
201 | }
202 | .flowchart-vertical .flowchart-link rect {
203 | display: none;
204 | }
205 |
206 | /*
207 | * flowchart-operator-body
208 | */
209 | .flowchart-operator-body {
210 | padding: 5px;
211 | cursor: move;
212 | }
213 |
--------------------------------------------------------------------------------
/jquery.flowchart.js:
--------------------------------------------------------------------------------
1 | if (!('remove' in Element.prototype)) {
2 | Element.prototype.remove = function() {
3 | if (this.parentNode) {
4 | alert(this.innerHTML);
5 | this.parentNode.removeChild(this);
6 |
7 | }
8 | };
9 | }
10 |
11 | jQuery(function ($) {
12 | // the widget definition, where "custom" is the namespace,
13 | // "colorize" the widget name
14 | $.widget("flowchart.flowchart", {
15 | // default options
16 | options: {
17 | canUserEditLinks: true,
18 | canUserMoveOperators: true,
19 | data: {},
20 | distanceFromArrow: 3,
21 | defaultOperatorClass: 'flowchart-default-operator',
22 | defaultLinkColor: '#3366ff',
23 | defaultSelectedLinkColor: 'black',
24 | linkWidth: 10,
25 | grid: 20,
26 | multipleLinksOnOutput: false,
27 | multipleLinksOnInput: false,
28 | linkVerticalDecal: 0,
29 | verticalConnection: false,
30 | onOperatorSelect: function (operatorId) {
31 | return true;
32 | },
33 | onOperatorUnselect: function () {
34 | return true;
35 | },
36 | onOperatorMouseOver: function (operatorId) {
37 | return true;
38 | },
39 | onOperatorMouseOut: function (operatorId) {
40 | return true;
41 | },
42 | onLinkSelect: function (linkId) {
43 | return true;
44 | },
45 | onLinkUnselect: function () {
46 | return true;
47 | },
48 | onOperatorCreate: function (operatorId, operatorData, fullElement) {
49 | return true;
50 | },
51 | onLinkCreate: function (linkId, linkData) {
52 | return true;
53 | },
54 | onOperatorDelete: function (operatorId) {
55 | return true;
56 | },
57 | onLinkDelete: function (linkId, forced) {
58 | return true;
59 | },
60 | onOperatorMoved: function (operatorId, position) {
61 |
62 | },
63 | onAfterChange: function (changeType) {
64 |
65 | }
66 | },
67 | data: null,
68 | objs: null,
69 | maskNum: 0,
70 | linkNum: 0,
71 | operatorNum: 0,
72 | lastOutputConnectorClicked: null,
73 | selectedOperatorId: null,
74 | selectedLinkId: null,
75 | positionRatio: 1,
76 | globalId: null,
77 |
78 | // the constructor
79 | _create: function () {
80 | if (typeof document.__flowchartNumber == 'undefined') {
81 | document.__flowchartNumber = 0;
82 | } else {
83 | document.__flowchartNumber++;
84 | }
85 | this.globalId = document.__flowchartNumber;
86 | this._unitVariables();
87 |
88 | this.element.addClass('flowchart-container');
89 |
90 | if (this.options.verticalConnection) {
91 | this.element.addClass('flowchart-vertical');
92 | }
93 |
94 | this.objs.layers.links = $(' ');
95 | this.objs.layers.links.appendTo(this.element);
96 |
97 | this.objs.layers.operators = $('
');
98 | this.objs.layers.operators.appendTo(this.element);
99 |
100 | this.objs.layers.temporaryLink = $(' ');
101 | this.objs.layers.temporaryLink.appendTo(this.element);
102 |
103 | var shape = document.createElementNS("http://www.w3.org/2000/svg", "line");
104 | shape.setAttribute("x1", "0");
105 | shape.setAttribute("y1", "0");
106 | shape.setAttribute("x2", "0");
107 | shape.setAttribute("y2", "0");
108 | shape.setAttribute("stroke-dasharray", "6,6");
109 | shape.setAttribute("stroke-width", "4");
110 | shape.setAttribute("stroke", "black");
111 | shape.setAttribute("fill", "none");
112 | this.objs.layers.temporaryLink[0].appendChild(shape);
113 | this.objs.temporaryLink = shape;
114 |
115 | this._initEvents();
116 |
117 | if (typeof this.options.data != 'undefined') {
118 | this.setData(this.options.data);
119 | }
120 | },
121 |
122 | _unitVariables: function () {
123 | this.data = {
124 | operators: {},
125 | links: {}
126 | };
127 | this.objs = {
128 | layers: {
129 | operators: null,
130 | temporaryLink: null,
131 | links: null
132 | },
133 | linksContext: null,
134 | temporaryLink: null
135 | };
136 | },
137 |
138 | _initEvents: function () {
139 |
140 | var self = this;
141 |
142 | this.element.mousemove(function (e) {
143 | var $this = $(this);
144 | var offset = $this.offset();
145 | self._mousemove((e.pageX - offset.left) / self.positionRatio, (e.pageY - offset.top) / self.positionRatio, e);
146 | });
147 |
148 | this.element.click(function (e) {
149 | var $this = $(this);
150 | var offset = $this.offset();
151 | self._click((e.pageX - offset.left) / self.positionRatio, (e.pageY - offset.top) / self.positionRatio, e);
152 | });
153 |
154 |
155 | this.objs.layers.operators.on('pointerdown mousedown touchstart', '.flowchart-operator', function (e) {
156 | e.stopImmediatePropagation();
157 | });
158 |
159 | this.objs.layers.operators.on('click', '.flowchart-operator', function (e) {
160 | if ($(e.target).closest('.flowchart-operator-connector').length == 0) {
161 | self.selectOperator($(this).data('operator_id'));
162 | }
163 | });
164 |
165 | this.objs.layers.operators.on('click', '.flowchart-operator-connector', function () {
166 | var $this = $(this);
167 | if (self.options.canUserEditLinks) {
168 | self._connectorClicked($this.closest('.flowchart-operator').data('operator_id'), $this.data('connector'), $this.data('sub_connector'), $this.closest('.flowchart-operator-connector-set').data('connector_type'));
169 | }
170 | });
171 |
172 | this.objs.layers.links.on('mousedown touchstart', '.flowchart-link', function (e) {
173 | e.stopImmediatePropagation();
174 | });
175 |
176 | this.objs.layers.links.on('mouseover', '.flowchart-link', function () {
177 | self._connecterMouseOver($(this).data('link_id'));
178 | });
179 |
180 | this.objs.layers.links.on('mouseout', '.flowchart-link', function () {
181 | self._connecterMouseOut($(this).data('link_id'));
182 | });
183 |
184 | this.objs.layers.links.on('click', '.flowchart-link', function () {
185 | self.selectLink($(this).data('link_id'));
186 | });
187 |
188 | this.objs.layers.operators.on('mouseover', '.flowchart-operator', function (e) {
189 | self._operatorMouseOver($(this).data('operator_id'));
190 | });
191 |
192 | this.objs.layers.operators.on('mouseout', '.flowchart-operator', function (e) {
193 | self._operatorMouseOut($(this).data('operator_id'));
194 | });
195 |
196 | },
197 |
198 | setData: function (data) {
199 | this._clearOperatorsLayer();
200 | this.data.operatorTypes = {};
201 | if (typeof data.operatorTypes != 'undefined') {
202 | this.data.operatorTypes = data.operatorTypes;
203 | }
204 |
205 | this.data.operators = {};
206 | for (var operatorId in data.operators) {
207 | if (data.operators.hasOwnProperty(operatorId)) {
208 | this.createOperator(operatorId, data.operators[operatorId]);
209 | }
210 | }
211 | this.data.links = {};
212 | for (var linkId in data.links) {
213 | if (data.links.hasOwnProperty(linkId)) {
214 | this.createLink(linkId, data.links[linkId]);
215 | }
216 | }
217 | this.redrawLinksLayer();
218 | },
219 |
220 | addLink: function (linkData) {
221 | while (typeof this.data.links[this.linkNum] != 'undefined') {
222 | this.linkNum++;
223 | }
224 |
225 | this.createLink(this.linkNum, linkData);
226 | return this.linkNum;
227 | },
228 |
229 | createLink: function (linkId, linkDataOriginal) {
230 | var linkData = $.extend(true, {}, linkDataOriginal);
231 | if (!this.callbackEvent('linkCreate', [linkId, linkData])) {
232 | return;
233 | }
234 |
235 | var subConnectors = this._getSubConnectors(linkData);
236 | var fromSubConnector = subConnectors[0];
237 | var toSubConnector = subConnectors[1];
238 |
239 | var multipleLinksOnOutput = this.options.multipleLinksOnOutput;
240 | var multipleLinksOnInput = this.options.multipleLinksOnInput;
241 | if (!multipleLinksOnOutput || !multipleLinksOnInput) {
242 | for (var linkId2 in this.data.links) {
243 | if (this.data.links.hasOwnProperty(linkId2)) {
244 | var currentLink = this.data.links[linkId2];
245 |
246 | var currentSubConnectors = this._getSubConnectors(currentLink);
247 | var currentFromSubConnector = currentSubConnectors[0];
248 | var currentToSubConnector = currentSubConnectors[1];
249 |
250 | if (!multipleLinksOnOutput && !this.data.operators[linkData.fromOperator].properties.outputs[linkData.fromConnector].multipleLinks && currentLink.fromOperator == linkData.fromOperator && currentLink.fromConnector == linkData.fromConnector && currentFromSubConnector == fromSubConnector) {
251 | this.deleteLink(linkId2);
252 | continue;
253 | }
254 | if (!multipleLinksOnInput && !this.data.operators[linkData.toOperator].properties.inputs[linkData.toConnector].multipleLinks && currentLink.toOperator == linkData.toOperator && currentLink.toConnector == linkData.toConnector && currentToSubConnector == toSubConnector) {
255 | this.deleteLink(linkId2);
256 | }
257 | }
258 | }
259 | }
260 |
261 | this._autoCreateSubConnector(linkData.fromOperator, linkData.fromConnector, 'outputs', fromSubConnector);
262 | this._autoCreateSubConnector(linkData.toOperator, linkData.toConnector, 'inputs', toSubConnector);
263 |
264 | this.data.links[linkId] = linkData;
265 | this._drawLink(linkId);
266 |
267 | this.callbackEvent('afterChange', ['link_create']);
268 | },
269 |
270 | _autoCreateSubConnector: function (operator, connector, connectorType, subConnector) {
271 | var connectorInfos = this.data.operators[operator].internal.properties[connectorType][connector];
272 | if (connectorInfos.multiple) {
273 | var fromFullElement = this.data.operators[operator].internal.els;
274 | var nbFromConnectors = this.data.operators[operator].internal.els.connectors[connector].length;
275 | for (var i = nbFromConnectors; i < subConnector + 2; i++) {
276 | this._createSubConnector(connector, connectorInfos, fromFullElement);
277 | }
278 | }
279 | },
280 |
281 | _refreshOperatorConnectors: function (operatorId) {
282 | for (var linkId in this.data.links) {
283 | if (this.data.links.hasOwnProperty(linkId)) {
284 | var linkData = this.data.links[linkId];
285 | if (linkData.fromOperator == operatorId || linkData.toOperator == operatorId)
286 | {
287 | var subConnectors = this._getSubConnectors(linkData);
288 | var fromSubConnector = subConnectors[0];
289 | var toSubConnector = subConnectors[1];
290 |
291 | this._autoCreateSubConnector(linkData.fromOperator, linkData.fromConnector, 'outputs', fromSubConnector);
292 | this._autoCreateSubConnector(linkData.toOperator, linkData.toConnector, 'inputs', toSubConnector);
293 | }
294 | }
295 | }
296 | },
297 |
298 | redrawLinksLayer: function () {
299 | this._clearLinksLayer();
300 | for (var linkId in this.data.links) {
301 | if (this.data.links.hasOwnProperty(linkId)) {
302 | this._drawLink(linkId);
303 | }
304 | }
305 | },
306 |
307 | _clearLinksLayer: function () {
308 | this.objs.layers.links.empty();
309 | if (this.options.verticalConnection) {
310 | this.objs.layers.operators.find('.flowchart-operator-connector-small-arrow').css('border-top-color', 'transparent');
311 | } else {
312 | this.objs.layers.operators.find('.flowchart-operator-connector-small-arrow').css('border-left-color', 'transparent');
313 | }
314 | },
315 |
316 | _clearOperatorsLayer: function () {
317 | this.objs.layers.operators.empty();
318 | },
319 |
320 | getConnectorPosition: function (operatorId, connectorId, subConnector) {
321 | var operatorData = this.data.operators[operatorId];
322 | var $connector = operatorData.internal.els.connectorArrows[connectorId][subConnector];
323 |
324 | var connectorOffset = $connector.offset();
325 | var elementOffset = this.element.offset();
326 |
327 | var x = (connectorOffset.left - elementOffset.left) / this.positionRatio;
328 | var width = parseInt($connector.css('border-top-width'), 10);
329 | var y = (connectorOffset.top - elementOffset.top - 1) / this.positionRatio + parseInt($connector.css('border-left-width'), 10);
330 |
331 | return {x: x, width: width, y: y};
332 | },
333 |
334 | getLinkMainColor: function (linkId) {
335 | var color = this.options.defaultLinkColor;
336 | var linkData = this.data.links[linkId];
337 | if (typeof linkData.color != 'undefined') {
338 | color = linkData.color;
339 | }
340 | return color;
341 | },
342 |
343 | setLinkMainColor: function (linkId, color) {
344 | this.data.links[linkId].color = color;
345 | this.callbackEvent('afterChange', ['link_change_main_color']);
346 | },
347 |
348 | _drawLink: function (linkId) {
349 | var linkData = this.data.links[linkId];
350 |
351 | if (typeof linkData.internal == 'undefined') {
352 | linkData.internal = {};
353 | }
354 | linkData.internal.els = {};
355 |
356 | var fromOperatorId = linkData.fromOperator;
357 | var fromConnectorId = linkData.fromConnector;
358 | var toOperatorId = linkData.toOperator;
359 | var toConnectorId = linkData.toConnector;
360 |
361 | var subConnectors = this._getSubConnectors(linkData);
362 | var fromSubConnector = subConnectors[0];
363 | var toSubConnector = subConnectors[1];
364 |
365 | var color = this.getLinkMainColor(linkId);
366 |
367 | var fromOperator = this.data.operators[fromOperatorId];
368 | var toOperator = this.data.operators[toOperatorId];
369 |
370 | var fromSmallConnector = fromOperator.internal.els.connectorSmallArrows[fromConnectorId][fromSubConnector];
371 | var toSmallConnector = toOperator.internal.els.connectorSmallArrows[toConnectorId][toSubConnector];
372 |
373 | linkData.internal.els.fromSmallConnector = fromSmallConnector;
374 | linkData.internal.els.toSmallConnector = toSmallConnector;
375 |
376 | var overallGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
377 | this.objs.layers.links[0].appendChild(overallGroup);
378 | linkData.internal.els.overallGroup = overallGroup;
379 |
380 | var mask = document.createElementNS("http://www.w3.org/2000/svg", "mask");
381 | var maskId = "fc_mask_" + this.globalId + "_" + this.maskNum;
382 | this.maskNum++;
383 | mask.setAttribute("id", maskId);
384 |
385 | overallGroup.appendChild(mask);
386 |
387 | var shape = document.createElementNS("http://www.w3.org/2000/svg", "rect");
388 | shape.setAttribute("x", "0");
389 | shape.setAttribute("y", "0");
390 | shape.setAttribute("width", "100%");
391 | shape.setAttribute("height", "100%");
392 | shape.setAttribute("stroke", "none");
393 | shape.setAttribute("fill", "white");
394 | mask.appendChild(shape);
395 |
396 | var shape_polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
397 | shape_polygon.setAttribute("stroke", "none");
398 | shape_polygon.setAttribute("fill", "black");
399 | mask.appendChild(shape_polygon);
400 | linkData.internal.els.mask = shape_polygon;
401 |
402 | var group = document.createElementNS("http://www.w3.org/2000/svg", "g");
403 | group.setAttribute('class', 'flowchart-link');
404 | group.setAttribute('data-link_id', linkId);
405 | overallGroup.appendChild(group);
406 |
407 | var shape_path = document.createElementNS("http://www.w3.org/2000/svg", "path");
408 | shape_path.setAttribute("stroke-width", this.options.linkWidth.toString());
409 | shape_path.setAttribute("fill", "none");
410 | group.appendChild(shape_path);
411 | linkData.internal.els.path = shape_path;
412 |
413 | var shape_rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
414 | shape_rect.setAttribute("stroke", "none");
415 | shape_rect.setAttribute("mask", "url(#" + maskId + ")");
416 | group.appendChild(shape_rect);
417 | linkData.internal.els.rect = shape_rect;
418 |
419 | this._refreshLinkPositions(linkId);
420 | this.uncolorizeLink(linkId);
421 | },
422 |
423 | _getSubConnectors: function (linkData) {
424 | var fromSubConnector = 0;
425 | if (typeof linkData.fromSubConnector != 'undefined') {
426 | fromSubConnector = linkData.fromSubConnector;
427 | }
428 |
429 | var toSubConnector = 0;
430 | if (typeof linkData.toSubConnector != 'undefined') {
431 | toSubConnector = linkData.toSubConnector;
432 | }
433 |
434 | return [fromSubConnector, toSubConnector];
435 | },
436 |
437 | _refreshLinkPositions: function (linkId) {
438 | var linkData = this.data.links[linkId];
439 |
440 | var subConnectors = this._getSubConnectors(linkData);
441 | var fromSubConnector = subConnectors[0];
442 | var toSubConnector = subConnectors[1];
443 |
444 | var fromPosition = this.getConnectorPosition(linkData.fromOperator, linkData.fromConnector, fromSubConnector);
445 | var toPosition = this.getConnectorPosition(linkData.toOperator, linkData.toConnector, toSubConnector);
446 |
447 | var fromX = fromPosition.x;
448 | var offsetFromX = fromPosition.width;
449 | var fromY = fromPosition.y;
450 |
451 | var toX = toPosition.x;
452 | var toY = toPosition.y;
453 |
454 | fromY += this.options.linkVerticalDecal;
455 | toY += this.options.linkVerticalDecal;
456 |
457 | var distanceFromArrow = this.options.distanceFromArrow;
458 |
459 | linkData.internal.els.mask.setAttribute("points", fromX + ',' + (fromY - offsetFromX - distanceFromArrow) + ' ' + (fromX + offsetFromX + distanceFromArrow) + ',' + fromY + ' ' + fromX + ',' + (fromY + offsetFromX + distanceFromArrow));
460 |
461 | var bezierFromX, bezierToX, bezierIntensity;
462 |
463 | if (this.options.verticalConnection) {
464 | fromY = fromY - 10;
465 | toY = toY - 10;
466 | bezierFromX = (fromX + offsetFromX + distanceFromArrow - 3);
467 | bezierToX = (toX + offsetFromX + distanceFromArrow - 3);
468 |
469 | bezierIntensity = Math.min(100, Math.max(Math.abs(bezierFromX - bezierToX) / 2, Math.abs(fromY - toY)));
470 | linkData.internal.els.path.setAttribute("d", 'M' + bezierFromX + ',' + (fromY) + ' C' + bezierFromX + ',' + (fromY + bezierIntensity) + ' ' + bezierToX + ',' + (toY - bezierIntensity) + ' ' + bezierToX + ',' + toY);
471 | linkData.internal.els.rect.setAttribute("x", fromX - 1 + this.options.linkWidth / 2);
472 | } else {
473 | bezierFromX = (fromX + offsetFromX + distanceFromArrow);
474 | bezierToX = toX + 1;
475 | bezierIntensity = Math.min(100, Math.max(Math.abs(bezierFromX - bezierToX) / 2, Math.abs(fromY - toY)));
476 | linkData.internal.els.path.setAttribute("d", 'M' + bezierFromX + ',' + (fromY) + ' C' + (fromX + offsetFromX + distanceFromArrow + bezierIntensity) + ',' + fromY + ' ' + (toX - bezierIntensity) + ',' + toY + ' ' + bezierToX + ',' + toY);
477 | linkData.internal.els.rect.setAttribute("x", fromX);
478 | }
479 |
480 | linkData.internal.els.rect.setAttribute("y", fromY - this.options.linkWidth / 2);
481 | linkData.internal.els.rect.setAttribute("width", offsetFromX + distanceFromArrow + 1);
482 | linkData.internal.els.rect.setAttribute("height", this.options.linkWidth);
483 |
484 | },
485 |
486 | getOperatorCompleteData: function (operatorData) {
487 | if (typeof operatorData.internal == 'undefined') {
488 | operatorData.internal = {};
489 | }
490 | this._refreshInternalProperties(operatorData);
491 | var infos = $.extend(true, {}, operatorData.internal.properties);
492 |
493 | for (var connectorId_i in infos.inputs) {
494 | if (infos.inputs.hasOwnProperty(connectorId_i)) {
495 | if (infos.inputs[connectorId_i] == null) {
496 | delete infos.inputs[connectorId_i];
497 | }
498 | }
499 | }
500 |
501 | for (var connectorId_o in infos.outputs) {
502 | if (infos.outputs.hasOwnProperty(connectorId_o)) {
503 | if (infos.outputs[connectorId_o] == null) {
504 | delete infos.outputs[connectorId_o];
505 | }
506 | }
507 | }
508 |
509 | if (typeof infos.class == 'undefined') {
510 | infos.class = this.options.defaultOperatorClass;
511 | }
512 | return infos;
513 | },
514 |
515 | _getOperatorFullElement: function (operatorData) {
516 | var infos = this.getOperatorCompleteData(operatorData);
517 |
518 | var $operator = $('
');
519 | $operator.addClass(infos.class);
520 |
521 | var $operator_title = $('
');
522 | $operator_title.html(infos.title);
523 | $operator_title.appendTo($operator);
524 |
525 | var $operator_body = $('
');
526 | $operator_body.html(infos.body);
527 | if (infos.body) {
528 | $operator_body.appendTo($operator);
529 | }
530 |
531 | var $operator_inputs_outputs = $('
');
532 |
533 | var $operator_inputs = $('
');
534 |
535 | var $operator_outputs = $('
');
536 |
537 | if (this.options.verticalConnection) {
538 | $operator_inputs.prependTo($operator);
539 | $operator_outputs.appendTo($operator);
540 | } else {
541 | $operator_inputs_outputs.appendTo($operator);
542 | $operator_inputs.appendTo($operator_inputs_outputs);
543 | $operator_outputs.appendTo($operator_inputs_outputs);
544 | }
545 |
546 | var self = this;
547 |
548 | var connectorArrows = {};
549 | var connectorSmallArrows = {};
550 | var connectorSets = {};
551 | var connectors = {};
552 |
553 | var fullElement = {
554 | operator: $operator,
555 | title: $operator_title,
556 | body: $operator_body,
557 | connectorSets: connectorSets,
558 | connectors: connectors,
559 | connectorArrows: connectorArrows,
560 | connectorSmallArrows: connectorSmallArrows
561 | };
562 |
563 | function addConnector(connectorKey, connectorInfos, $operator_container, connectorType) {
564 | var $operator_connector_set = $('
');
565 | $operator_connector_set.data('connector_type', connectorType);
566 | $operator_connector_set.appendTo($operator_container);
567 |
568 | connectorArrows[connectorKey] = [];
569 | connectorSmallArrows[connectorKey] = [];
570 | connectors[connectorKey] = [];
571 | connectorSets[connectorKey] = $operator_connector_set;
572 |
573 | if ($.isArray(connectorInfos.label)) {
574 | for (var i = 0; i < connectorInfos.label.length; i++) {
575 | self._createSubConnector(connectorKey, connectorInfos.label[i], fullElement);
576 | }
577 | } else {
578 | self._createSubConnector(connectorKey, connectorInfos, fullElement);
579 | }
580 | }
581 |
582 | for (var key_i in infos.inputs) {
583 | if (infos.inputs.hasOwnProperty(key_i)) {
584 | addConnector(key_i, infos.inputs[key_i], $operator_inputs, 'inputs');
585 | }
586 | }
587 |
588 | for (var key_o in infos.outputs) {
589 | if (infos.outputs.hasOwnProperty(key_o)) {
590 | addConnector(key_o, infos.outputs[key_o], $operator_outputs, 'outputs');
591 | }
592 | }
593 |
594 | return fullElement;
595 | },
596 |
597 | _createSubConnector: function (connectorKey, connectorInfos, fullElement) {
598 | var $operator_connector_set = fullElement.connectorSets[connectorKey];
599 |
600 | var subConnector = fullElement.connectors[connectorKey].length;
601 |
602 | var $operator_connector = $('
');
603 | $operator_connector.appendTo($operator_connector_set);
604 | $operator_connector.data('connector', connectorKey);
605 | $operator_connector.data('sub_connector', subConnector);
606 |
607 | var $operator_connector_label = $('
');
608 | $operator_connector_label.text(connectorInfos.label.replace('(:i)', subConnector + 1));
609 | $operator_connector_label.appendTo($operator_connector);
610 |
611 | var $operator_connector_arrow = $('
');
612 |
613 | $operator_connector_arrow.appendTo($operator_connector);
614 |
615 | var $operator_connector_small_arrow = $('
');
616 | $operator_connector_small_arrow.appendTo($operator_connector);
617 |
618 | fullElement.connectors[connectorKey].push($operator_connector);
619 | fullElement.connectorArrows[connectorKey].push($operator_connector_arrow);
620 | fullElement.connectorSmallArrows[connectorKey].push($operator_connector_small_arrow);
621 | },
622 |
623 | getOperatorElement: function (operatorData) {
624 | var fullElement = this._getOperatorFullElement(operatorData);
625 | return fullElement.operator;
626 | },
627 |
628 | addOperator: function (operatorData) {
629 | while (typeof this.data.operators[this.operatorNum] != 'undefined') {
630 | this.operatorNum++;
631 | }
632 |
633 | this.createOperator(this.operatorNum, operatorData);
634 | return this.operatorNum;
635 | },
636 |
637 | createOperator: function (operatorId, operatorData) {
638 | operatorData.internal = {};
639 | this._refreshInternalProperties(operatorData);
640 |
641 | var fullElement = this._getOperatorFullElement(operatorData);
642 | if (!this.callbackEvent('operatorCreate', [operatorId, operatorData, fullElement])) {
643 | return false;
644 | }
645 |
646 | var grid = this.options.grid;
647 |
648 | if (grid) {
649 | operatorData.top = Math.round(operatorData.top / grid) * grid;
650 | operatorData.left = Math.round(operatorData.left / grid) * grid;
651 | }
652 |
653 | fullElement.operator.appendTo(this.objs.layers.operators);
654 | fullElement.operator.css({top: operatorData.top, left: operatorData.left});
655 | fullElement.operator.data('operator_id', operatorId);
656 |
657 | this.data.operators[operatorId] = operatorData;
658 | this.data.operators[operatorId].internal.els = fullElement;
659 |
660 | if (operatorId == this.selectedOperatorId) {
661 | this._addSelectedClass(operatorId);
662 | }
663 |
664 | var self = this;
665 |
666 | function operatorChangedPosition(operator_id, pos) {
667 | operatorData.top = pos.top;
668 | operatorData.left = pos.left;
669 |
670 | for (var linkId in self.data.links) {
671 | if (self.data.links.hasOwnProperty(linkId)) {
672 | var linkData = self.data.links[linkId];
673 | if (linkData.fromOperator == operator_id || linkData.toOperator == operator_id) {
674 | self._refreshLinkPositions(linkId);
675 | }
676 | }
677 | }
678 | }
679 |
680 | // Small fix has been added in order to manage eventual zoom
681 | // http://stackoverflow.com/questions/2930092/jquery-draggable-with-zoom-problem
682 | if (this.options.canUserMoveOperators) {
683 | var pointerX;
684 | var pointerY;
685 | fullElement.operator.draggable({
686 | containment: operatorData.internal.properties.uncontained ? false : this.element,
687 | handle: '.flowchart-operator-title, .flowchart-operator-body',
688 | start: function (e, ui) {
689 | if (self.lastOutputConnectorClicked != null) {
690 | e.preventDefault();
691 | return;
692 | }
693 | var elementOffset = self.element.offset();
694 | pointerX = (e.pageX - elementOffset.left) / self.positionRatio - parseInt($(e.target).css('left'), 10);
695 | pointerY = (e.pageY - elementOffset.top) / self.positionRatio - parseInt($(e.target).css('top'), 10);
696 | },
697 | drag: function (e, ui) {
698 | if (self.options.grid) {
699 | var grid = self.options.grid;
700 | var elementOffset = self.element.offset();
701 | ui.position.left = Math.round(((e.pageX - elementOffset.left) / self.positionRatio - pointerX) / grid) * grid;
702 | ui.position.top = Math.round(((e.pageY - elementOffset.top) / self.positionRatio - pointerY) / grid) * grid;
703 |
704 | if (!operatorData.internal.properties.uncontained) {
705 | var $this = $(this);
706 | ui.position.left = Math.min(Math.max(ui.position.left, 0), self.element.width() - $this.outerWidth());
707 | ui.position.top = Math.min(Math.max(ui.position.top, 0), self.element.height() - $this.outerHeight());
708 | }
709 |
710 | ui.offset.left = Math.round(ui.position.left + elementOffset.left);
711 | ui.offset.top = Math.round(ui.position.top + elementOffset.top);
712 | fullElement.operator.css({left: ui.position.left, top: ui.position.top});
713 | }
714 | operatorChangedPosition($(this).data('operator_id'), ui.position);
715 | },
716 | stop: function (e, ui) {
717 | self._unsetTemporaryLink();
718 | var operatorId = $(this).data('operator_id');
719 | operatorChangedPosition(operatorId, ui.position);
720 | fullElement.operator.css({
721 | height: 'auto'
722 | });
723 |
724 | self.callbackEvent('operatorMoved', [operatorId, ui.position]);
725 | self.callbackEvent('afterChange', ['operator_moved']);
726 | }
727 | });
728 | }
729 |
730 | this.callbackEvent('afterChange', ['operator_create']);
731 | },
732 |
733 | _connectorClicked: function (operator, connector, subConnector, connectorCategory) {
734 | if (connectorCategory == 'outputs') {
735 | var d = new Date();
736 | // var currentTime = d.getTime();
737 | this.lastOutputConnectorClicked = {
738 | operator: operator,
739 | connector: connector,
740 | subConnector: subConnector
741 | };
742 | this.objs.layers.temporaryLink.show();
743 | var position = this.getConnectorPosition(operator, connector, subConnector);
744 | var x = position.x + position.width;
745 | var y = position.y;
746 | this.objs.temporaryLink.setAttribute('x1', x.toString());
747 | this.objs.temporaryLink.setAttribute('y1', y.toString());
748 | this._mousemove(x, y);
749 | }
750 | if (connectorCategory == 'inputs' && this.lastOutputConnectorClicked != null) {
751 | var linkData = {
752 | fromOperator: this.lastOutputConnectorClicked.operator,
753 | fromConnector: this.lastOutputConnectorClicked.connector,
754 | fromSubConnector: this.lastOutputConnectorClicked.subConnector,
755 | toOperator: operator,
756 | toConnector: connector,
757 | toSubConnector: subConnector
758 | };
759 |
760 | this.addLink(linkData);
761 | this._unsetTemporaryLink();
762 | }
763 | },
764 |
765 | _unsetTemporaryLink: function () {
766 | this.lastOutputConnectorClicked = null;
767 | this.objs.layers.temporaryLink.hide();
768 | },
769 |
770 | _mousemove: function (x, y, e) {
771 | if (this.lastOutputConnectorClicked != null) {
772 | this.objs.temporaryLink.setAttribute('x2', x);
773 | this.objs.temporaryLink.setAttribute('y2', y);
774 | }
775 | },
776 |
777 | _click: function (x, y, e) {
778 | var $target = $(e.target);
779 | if ($target.closest('.flowchart-operator-connector').length == 0) {
780 | this._unsetTemporaryLink();
781 | }
782 |
783 | if ($target.closest('.flowchart-operator').length == 0) {
784 | this.unselectOperator();
785 | }
786 |
787 | if ($target.closest('.flowchart-link').length == 0) {
788 | this.unselectLink();
789 | }
790 | },
791 |
792 | _removeSelectedClassOperators: function () {
793 | this.objs.layers.operators.find('.flowchart-operator').removeClass('selected');
794 | },
795 |
796 | unselectOperator: function () {
797 | if (this.selectedOperatorId != null) {
798 | if (!this.callbackEvent('operatorUnselect', [])) {
799 | return;
800 | }
801 | this._removeSelectedClassOperators();
802 | this.selectedOperatorId = null;
803 | }
804 | },
805 |
806 | _addSelectedClass: function (operatorId) {
807 | this.data.operators[operatorId].internal.els.operator.addClass('selected');
808 | },
809 |
810 | callbackEvent: function(name, params) {
811 | var cbName = 'on' + name.charAt(0).toUpperCase() + name.slice(1);
812 | var ret = this.options[cbName].apply(this, params);
813 | if (ret !== false) {
814 | var returnHash = {'result': ret};
815 | this.element.trigger(name, params.concat([returnHash]));
816 | ret = returnHash['result'];
817 | }
818 | return ret;
819 | },
820 |
821 | selectOperator: function (operatorId) {
822 | if (!this.callbackEvent('operatorSelect', [operatorId])) {
823 | return;
824 | }
825 | this.unselectLink();
826 | this._removeSelectedClassOperators();
827 | this._addSelectedClass(operatorId);
828 | this.selectedOperatorId = operatorId;
829 | },
830 |
831 | addClassOperator: function (operatorId, className) {
832 | this.data.operators[operatorId].internal.els.operator.addClass(className);
833 | },
834 |
835 | removeClassOperator: function (operatorId, className) {
836 | this.data.operators[operatorId].internal.els.operator.removeClass(className);
837 | },
838 |
839 | removeClassOperators: function (className) {
840 | this.objs.layers.operators.find('.flowchart-operator').removeClass(className);
841 | },
842 |
843 | _addHoverClassOperator: function (operatorId) {
844 | this.data.operators[operatorId].internal.els.operator.addClass('hover');
845 | },
846 |
847 | _removeHoverClassOperators: function () {
848 | this.objs.layers.operators.find('.flowchart-operator').removeClass('hover');
849 | },
850 |
851 | _operatorMouseOver: function (operatorId) {
852 | if (!this.callbackEvent('operatorMouseOver', [operatorId])) {
853 | return;
854 | }
855 | this._addHoverClassOperator(operatorId);
856 | },
857 |
858 | _operatorMouseOut: function (operatorId) {
859 | if (!this.callbackEvent('operatorMouseOut', [operatorId])) {
860 | return;
861 | }
862 | this._removeHoverClassOperators();
863 | },
864 |
865 | getSelectedOperatorId: function () {
866 | return this.selectedOperatorId;
867 | },
868 |
869 | getSelectedLinkId: function () {
870 | return this.selectedLinkId;
871 | },
872 |
873 | // Found here : http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
874 | _shadeColor: function (color, percent) {
875 | var f = parseInt(color.slice(1), 16), t = percent < 0 ? 0 : 255, p = percent < 0 ? percent * -1 : percent, R = f >> 16, G = f >> 8 & 0x00FF, B = f & 0x0000FF;
876 | return "#" + (0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G) * 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1);
877 | },
878 |
879 | colorizeLink: function (linkId, color) {
880 | var linkData = this.data.links[linkId];
881 | linkData.internal.els.path.setAttribute('stroke', color);
882 | linkData.internal.els.rect.setAttribute('fill', color);
883 | if (this.options.verticalConnection) {
884 | linkData.internal.els.fromSmallConnector.css('border-top-color', color);
885 | linkData.internal.els.toSmallConnector.css('border-top-color', color);
886 | } else {
887 | linkData.internal.els.fromSmallConnector.css('border-left-color', color);
888 | linkData.internal.els.toSmallConnector.css('border-left-color', color);
889 | }
890 | },
891 |
892 | uncolorizeLink: function (linkId) {
893 | this.colorizeLink(linkId, this.getLinkMainColor(linkId));
894 | },
895 |
896 | _connecterMouseOver: function (linkId) {
897 | if (this.selectedLinkId != linkId) {
898 | this.colorizeLink(linkId, this._shadeColor(this.getLinkMainColor(linkId), -0.4));
899 | }
900 | },
901 |
902 | _connecterMouseOut: function (linkId) {
903 | if (this.selectedLinkId != linkId) {
904 | this.uncolorizeLink(linkId);
905 | }
906 | },
907 |
908 | unselectLink: function () {
909 | if (this.selectedLinkId != null) {
910 | if (!this.callbackEvent('linkUnselect', [])) {
911 | return;
912 | }
913 | this.uncolorizeLink(this.selectedLinkId, this.options.defaultSelectedLinkColor);
914 | this.selectedLinkId = null;
915 | }
916 | },
917 |
918 | selectLink: function (linkId) {
919 | this.unselectLink();
920 | if (!this.callbackEvent('linkSelect', [linkId])) {
921 | return;
922 | }
923 | this.unselectOperator();
924 | this.selectedLinkId = linkId;
925 | this.colorizeLink(linkId, this.options.defaultSelectedLinkColor);
926 | },
927 |
928 | deleteOperator: function (operatorId) {
929 | this._deleteOperator(operatorId, false);
930 | },
931 |
932 | _deleteOperator: function (operatorId, replace) {
933 | if (!this.callbackEvent('operatorDelete', [operatorId, replace])) {
934 | return false;
935 | }
936 | if (!replace) {
937 | for (var linkId in this.data.links) {
938 | if (this.data.links.hasOwnProperty(linkId)) {
939 | var currentLink = this.data.links[linkId];
940 | if (currentLink.fromOperator == operatorId || currentLink.toOperator == operatorId) {
941 | this._deleteLink(linkId, true);
942 | }
943 | }
944 | }
945 | }
946 | if (!replace && operatorId == this.selectedOperatorId) {
947 | this.unselectOperator();
948 | }
949 | this.data.operators[operatorId].internal.els.operator.remove();
950 | delete this.data.operators[operatorId];
951 |
952 | this.callbackEvent('afterChange', ['operator_delete']);
953 | },
954 |
955 | deleteLink: function (linkId) {
956 | this._deleteLink(linkId, false);
957 | },
958 |
959 | _deleteLink: function (linkId, forced) {
960 | if (this.selectedLinkId == linkId) {
961 | this.unselectLink();
962 | }
963 | if (!this.callbackEvent('linkDelete', [linkId, forced])) {
964 | if (!forced) {
965 | return;
966 | }
967 | }
968 | this.colorizeLink(linkId, 'transparent');
969 | var linkData = this.data.links[linkId];
970 | var fromOperator = linkData.fromOperator;
971 | var fromConnector = linkData.fromConnector;
972 | var toOperator = linkData.toOperator;
973 | var toConnector = linkData.toConnector;
974 | var overallGroup = linkData.internal.els.overallGroup;
975 | if (overallGroup.remove) {
976 | overallGroup.remove();
977 | } else {
978 | overallGroup.parentNode.removeChild(overallGroup);
979 | }
980 | delete this.data.links[linkId];
981 |
982 | this._cleanMultipleConnectors(fromOperator, fromConnector, 'from');
983 | this._cleanMultipleConnectors(toOperator, toConnector, 'to');
984 |
985 | this.callbackEvent('afterChange', ['link_delete']);
986 | },
987 |
988 | _cleanMultipleConnectors: function (operator, connector, linkFromTo) {
989 | if (!this.data.operators[operator].internal.properties[linkFromTo == 'from' ? 'outputs' : 'inputs'][connector].multiple) {
990 | return;
991 | }
992 |
993 | var maxI = -1;
994 | var fromToOperator = linkFromTo + 'Operator';
995 | var fromToConnector = linkFromTo + 'Connector';
996 | var fromToSubConnector = linkFromTo + 'SubConnector';
997 | var els = this.data.operators[operator].internal.els;
998 | var subConnectors = els.connectors[connector];
999 | var nbSubConnectors = subConnectors.length;
1000 |
1001 | for (var linkId in this.data.links) {
1002 | if (this.data.links.hasOwnProperty(linkId)) {
1003 | var linkData = this.data.links[linkId];
1004 | if (linkData[fromToOperator] == operator && linkData[fromToConnector] == connector) {
1005 | if (maxI < linkData[fromToSubConnector]) {
1006 | maxI = linkData[fromToSubConnector];
1007 | }
1008 | }
1009 | }
1010 | }
1011 |
1012 | var nbToDelete = Math.min(nbSubConnectors - maxI - 2, nbSubConnectors - 1);
1013 | for (var i = 0; i < nbToDelete; i++) {
1014 | subConnectors[subConnectors.length - 1].remove();
1015 | subConnectors.pop();
1016 | els.connectorArrows[connector].pop();
1017 | els.connectorSmallArrows[connector].pop();
1018 | }
1019 | },
1020 |
1021 | deleteSelected: function () {
1022 | if (this.selectedLinkId != null) {
1023 | this.deleteLink(this.selectedLinkId);
1024 | }
1025 | if (this.selectedOperatorId != null) {
1026 | this.deleteOperator(this.selectedOperatorId);
1027 | }
1028 | },
1029 |
1030 | setPositionRatio: function (positionRatio) {
1031 | this.positionRatio = positionRatio;
1032 | },
1033 |
1034 | getPositionRatio: function () {
1035 | return this.positionRatio;
1036 | },
1037 |
1038 | getData: function () {
1039 | var keys = ['operators', 'links'];
1040 | var data = {};
1041 | data.operators = $.extend(true, {}, this.data.operators);
1042 | data.links = $.extend(true, {}, this.data.links);
1043 | for (var keyI in keys) {
1044 | if (keys.hasOwnProperty(keyI)) {
1045 | var key = keys[keyI];
1046 | for (var objId in data[key]) {
1047 | if (data[key].hasOwnProperty(objId)) {
1048 | delete data[key][objId].internal;
1049 | }
1050 | }
1051 | }
1052 | }
1053 | data.operatorTypes = this.data.operatorTypes;
1054 | return data;
1055 | },
1056 |
1057 | getDataRef: function () {
1058 | return this.data;
1059 | },
1060 |
1061 | setOperatorTitle: function (operatorId, title) {
1062 | this.data.operators[operatorId].internal.els.title.html(title);
1063 | if (typeof this.data.operators[operatorId].properties == 'undefined') {
1064 | this.data.operators[operatorId].properties = {};
1065 | }
1066 | this.data.operators[operatorId].properties.title = title;
1067 | this._refreshInternalProperties(this.data.operators[operatorId]);
1068 | this.callbackEvent('afterChange', ['operator_title_change']);
1069 | },
1070 |
1071 | setOperatorBody: function (operatorId, body) {
1072 | this.data.operators[operatorId].internal.els.body.html(body);
1073 | if (typeof this.data.operators[operatorId].properties == 'undefined') {
1074 | this.data.operators[operatorId].properties = {};
1075 | }
1076 | this.data.operators[operatorId].properties.body = body;
1077 | this._refreshInternalProperties(this.data.operators[operatorId]);
1078 | this.callbackEvent('afterChange', ['operator_body_change']);
1079 | },
1080 |
1081 | getOperatorTitle: function (operatorId) {
1082 | return this.data.operators[operatorId].internal.properties.title;
1083 | },
1084 |
1085 | getOperatorBody: function (operatorId) {
1086 | return this.data.operators[operatorId].internal.properties.body;
1087 | },
1088 |
1089 | setOperatorData: function (operatorId, operatorData) {
1090 | var infos = this.getOperatorCompleteData(operatorData);
1091 | for (var linkId in this.data.links) {
1092 | if (this.data.links.hasOwnProperty(linkId)) {
1093 | var linkData = this.data.links[linkId];
1094 | if ((linkData.fromOperator == operatorId && typeof infos.outputs[linkData.fromConnector] == 'undefined') ||
1095 | (linkData.toOperator == operatorId && typeof infos.inputs[linkData.toConnector] == 'undefined')) {
1096 | this._deleteLink(linkId, true);
1097 | }
1098 | }
1099 | }
1100 | this._deleteOperator(operatorId, true);
1101 | this.createOperator(operatorId, operatorData);
1102 | this._refreshOperatorConnectors(operatorId);
1103 | this.redrawLinksLayer();
1104 | this.callbackEvent('afterChange', ['operator_data_change']);
1105 | },
1106 |
1107 | getBoundingOperatorRect: function (operatorId) {
1108 | if (!this.data.operators[operatorId]) {
1109 | return null;
1110 | }
1111 |
1112 | var elOperator = this.data.operators[operatorId].internal.els.operator;
1113 | var operator = this.data.operators[operatorId];
1114 |
1115 | return {
1116 | 'left': operator.left,
1117 | 'top': operator.top,
1118 | 'width': elOperator.width(),
1119 | 'height': elOperator.height(),
1120 | };
1121 | },
1122 |
1123 | doesOperatorExists: function (operatorId) {
1124 | return typeof this.data.operators[operatorId] != 'undefined';
1125 | },
1126 |
1127 | getOperatorData: function (operatorId) {
1128 | var data = $.extend(true, {}, this.data.operators[operatorId]);
1129 | delete data.internal;
1130 | return data;
1131 | },
1132 |
1133 | getLinksFrom: function(operatorId) {
1134 | var result = [];
1135 |
1136 | for (var linkId in this.data.links) {
1137 | if (this.data.links.hasOwnProperty(linkId)) {
1138 | var linkData = this.data.links[linkId];
1139 | if (linkData.fromOperator === operatorId) {
1140 | result.push(linkData);
1141 | }
1142 | }
1143 | }
1144 |
1145 | return result;
1146 | },
1147 |
1148 | getLinksTo: function(operatorId) {
1149 | var result = [];
1150 |
1151 | for (var linkId in this.data.links) {
1152 | if (this.data.links.hasOwnProperty(linkId)) {
1153 | var linkData = this.data.links[linkId];
1154 | if (linkData.toOperator === operatorId) {
1155 | result.push(linkData);
1156 | }
1157 | }
1158 | }
1159 |
1160 | return result;
1161 | },
1162 |
1163 | getOperatorFullProperties: function (operatorData) {
1164 | if (typeof operatorData.type != 'undefined') {
1165 | var typeProperties = this.data.operatorTypes[operatorData.type];
1166 | var operatorProperties = {};
1167 | if (typeof operatorData.properties != 'undefined') {
1168 | operatorProperties = operatorData.properties;
1169 | }
1170 | return $.extend({}, typeProperties, operatorProperties);
1171 | } else {
1172 | return operatorData.properties;
1173 | }
1174 | },
1175 |
1176 | _refreshInternalProperties: function (operatorData) {
1177 | operatorData.internal.properties = this.getOperatorFullProperties(operatorData);
1178 | }
1179 | });
1180 | });
1181 |
--------------------------------------------------------------------------------
/jquery.flowchart.min.css:
--------------------------------------------------------------------------------
1 | .flowchart-container{position:relative;overflow:hidden}.flowchart-links-layer,.flowchart-operators-layer,.flowchart-temporary-link-layer{position:absolute;top:0;left:0;width:100%;height:100%}.flowchart-operators-layer,.flowchart-temporary-link-layer{pointer-events:none}.flowchart-temporary-link-layer{display:none}.flowchart-link,.flowchart-operator{cursor:default}.flowchart-operator-connector{position:relative;padding-top:5px;padding-bottom:5px}.flowchart-operator-connector-label{font-size:small}.flowchart-operator-inputs .flowchart-operator-connector-label{margin-left:14px}.flowchart-operator-outputs .flowchart-operator-connector-label{text-align:right;margin-right:5px}.flowchart-operator-connector-arrow{width:0;height:0;border-top:10px solid transparent;border-bottom:10px solid transparent;border-left:10px solid #ccc;position:absolute;top:0}.flowchart-operator-connector-small-arrow{width:0;height:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid transparent;position:absolute;top:5px;pointer-events:none}.flowchart-operator-connector:hover .flowchart-operator-connector-arrow{border-left:10px solid #999}.flowchart-operator-inputs .flowchart-operator-connector-arrow{left:-1px}.flowchart-operator-outputs .flowchart-operator-connector-arrow{right:-10px}.flowchart-operator-inputs .flowchart-operator-connector-small-arrow{left:-1px}.flowchart-operator-outputs .flowchart-operator-connector-small-arrow{right:-7px}.unselectable{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.flowchart-operator{position:absolute;width:140px;border:1px solid #ccc;background:#fafafa;pointer-events:auto}.flowchart-operator.hover{border-color:#999}.flowchart-operator.selected{border-color:#555}.flowchart-operator .flowchart-operator-title{width:100%;padding:5px;font-weight:bold;box-sizing:border-box;border-bottom:1px solid #ddd;background:#f0f0f0;height:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:move}.flowchart-operator .flowchart-operator-inputs-outputs{display:table;width:100%;margin-top:5px;margin-bottom:5px}.flowchart-operator .flowchart-operator-inputs,.flowchart-default-operator .flowchart-operator-outputs{display:table-cell;width:50%}.flowchart-vertical .flowchart-operator-inputs,.flowchart-vertical .flowchart-operator-outputs{position:relative;text-align:center;display:table;width:100%}.flowchart-vertical .flowchart-operator-connector-set{display:table-cell}.flowchart-vertical .flowchart-operator-connector{position:relative}.flowchart-vertical .flowchart-operator-connector-label{position:relative;text-align:center;width:100%}.flowchart-vertical .flowchart-operator-inputs .flowchart-operator-connector-label{margin-left:auto}.flowchart-vertical .flowchart-operator-connector-arrow{border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid #ccc;left:calc(50% - 10px)}.flowchart-vertical .flowchart-operator-connector:hover .flowchart-operator-connector-arrow{border-left-color:transparent;border-top-color:#999}.flowchart-vertical .flowchart-operator-connector-small-arrow{border-right:5px solid transparent;top:2px;left:calc(50% - 5px)}.flowchart-vertical .flowchart-operator-connector-arrow{top:0}.flowchart-vertical .flowchart-operator-outputs .flowchart-operator-connector-arrow{bottom:-20px;top:auto}.flowchart-vertical .flowchart-operator-outputs .flowchart-operator-connector-small-arrow{left:calc(50% - 5px);bottom:-12px;top:auto}.flowchart-vertical .flowchart-link rect{display:none}.flowchart-operator-body{padding:5px;cursor:move}
2 |
--------------------------------------------------------------------------------
/jquery.flowchart.min.js:
--------------------------------------------------------------------------------
1 | "remove"in Element.prototype||(Element.prototype.remove=function(){this.parentNode&&(alert(this.innerHTML),this.parentNode.removeChild(this))}),jQuery(function(t){t.widget("flowchart.flowchart",{options:{canUserEditLinks:!0,canUserMoveOperators:!0,data:{},distanceFromArrow:3,defaultOperatorClass:"flowchart-default-operator",defaultLinkColor:"#3366ff",defaultSelectedLinkColor:"black",linkWidth:10,grid:20,multipleLinksOnOutput:!1,multipleLinksOnInput:!1,linkVerticalDecal:0,verticalConnection:!1,onOperatorSelect:function(t){return!0},onOperatorUnselect:function(){return!0},onOperatorMouseOver:function(t){return!0},onOperatorMouseOut:function(t){return!0},onLinkSelect:function(t){return!0},onLinkUnselect:function(){return!0},onOperatorCreate:function(t,e,o){return!0},onLinkCreate:function(t,e){return!0},onOperatorDelete:function(t){return!0},onLinkDelete:function(t,e){return!0},onOperatorMoved:function(t,e){},onAfterChange:function(t){}},data:null,objs:null,maskNum:0,linkNum:0,operatorNum:0,lastOutputConnectorClicked:null,selectedOperatorId:null,selectedLinkId:null,positionRatio:1,globalId:null,_create:function(){void 0===document.__flowchartNumber?document.__flowchartNumber=0:document.__flowchartNumber++,this.globalId=document.__flowchartNumber,this._unitVariables(),this.element.addClass("flowchart-container"),this.options.verticalConnection&&this.element.addClass("flowchart-vertical"),this.objs.layers.links=t(' '),this.objs.layers.links.appendTo(this.element),this.objs.layers.operators=t('
'),this.objs.layers.operators.appendTo(this.element),this.objs.layers.temporaryLink=t(' '),this.objs.layers.temporaryLink.appendTo(this.element);var e=document.createElementNS("http://www.w3.org/2000/svg","line");e.setAttribute("x1","0"),e.setAttribute("y1","0"),e.setAttribute("x2","0"),e.setAttribute("y2","0"),e.setAttribute("stroke-dasharray","6,6"),e.setAttribute("stroke-width","4"),e.setAttribute("stroke","black"),e.setAttribute("fill","none"),this.objs.layers.temporaryLink[0].appendChild(e),this.objs.temporaryLink=e,this._initEvents(),void 0!==this.options.data&&this.setData(this.options.data)},_unitVariables:function(){this.data={operators:{},links:{}},this.objs={layers:{operators:null,temporaryLink:null,links:null},linksContext:null,temporaryLink:null}},_initEvents:function(){var e=this;this.element.mousemove(function(o){var r=t(this).offset();e._mousemove((o.pageX-r.left)/e.positionRatio,(o.pageY-r.top)/e.positionRatio,o)}),this.element.click(function(o){var r=t(this).offset();e._click((o.pageX-r.left)/e.positionRatio,(o.pageY-r.top)/e.positionRatio,o)}),this.objs.layers.operators.on("pointerdown mousedown touchstart",".flowchart-operator",function(t){t.stopImmediatePropagation()}),this.objs.layers.operators.on("click",".flowchart-operator",function(o){0==t(o.target).closest(".flowchart-operator-connector").length&&e.selectOperator(t(this).data("operator_id"))}),this.objs.layers.operators.on("click",".flowchart-operator-connector",function(){var o=t(this);e.options.canUserEditLinks&&e._connectorClicked(o.closest(".flowchart-operator").data("operator_id"),o.data("connector"),o.data("sub_connector"),o.closest(".flowchart-operator-connector-set").data("connector_type"))}),this.objs.layers.links.on("mousedown touchstart",".flowchart-link",function(t){t.stopImmediatePropagation()}),this.objs.layers.links.on("mouseover",".flowchart-link",function(){e._connecterMouseOver(t(this).data("link_id"))}),this.objs.layers.links.on("mouseout",".flowchart-link",function(){e._connecterMouseOut(t(this).data("link_id"))}),this.objs.layers.links.on("click",".flowchart-link",function(){e.selectLink(t(this).data("link_id"))}),this.objs.layers.operators.on("mouseover",".flowchart-operator",function(o){e._operatorMouseOver(t(this).data("operator_id"))}),this.objs.layers.operators.on("mouseout",".flowchart-operator",function(o){e._operatorMouseOut(t(this).data("operator_id"))})},setData:function(t){for(var e in this._clearOperatorsLayer(),this.data.operatorTypes={},void 0!==t.operatorTypes&&(this.data.operatorTypes=t.operatorTypes),this.data.operators={},t.operators)t.operators.hasOwnProperty(e)&&this.createOperator(e,t.operators[e]);for(var o in this.data.links={},t.links)t.links.hasOwnProperty(o)&&this.createLink(o,t.links[o]);this.redrawLinksLayer()},addLink:function(t){for(;void 0!==this.data.links[this.linkNum];)this.linkNum++;return this.createLink(this.linkNum,t),this.linkNum},createLink:function(e,o){var r=t.extend(!0,{},o);if(this.callbackEvent("linkCreate",[e,r])){var n=this._getSubConnectors(r),a=n[0],i=n[1],s=this.options.multipleLinksOnOutput,l=this.options.multipleLinksOnInput;if(!s||!l)for(var p in this.data.links)if(this.data.links.hasOwnProperty(p)){var c=this.data.links[p],h=this._getSubConnectors(c),u=h[0],d=h[1];if(!s&&!this.data.operators[r.fromOperator].properties.outputs[r.fromConnector].multipleLinks&&c.fromOperator==r.fromOperator&&c.fromConnector==r.fromConnector&&u==a){this.deleteLink(p);continue}l||this.data.operators[r.toOperator].properties.inputs[r.toConnector].multipleLinks||c.toOperator!=r.toOperator||c.toConnector!=r.toConnector||d!=i||this.deleteLink(p)}this._autoCreateSubConnector(r.fromOperator,r.fromConnector,"outputs",a),this._autoCreateSubConnector(r.toOperator,r.toConnector,"inputs",i),this.data.links[e]=r,this._drawLink(e),this.callbackEvent("afterChange",["link_create"])}},_autoCreateSubConnector:function(t,e,o,r){var n=this.data.operators[t].internal.properties[o][e];if(n.multiple)for(var a=this.data.operators[t].internal.els,i=this.data.operators[t].internal.els.connectors[e].length;i');r.addClass(o.class);var n=t('
');n.html(o.title),n.appendTo(r);var a=t('
');a.html(o.body),o.body&&a.appendTo(r);var i=t('
'),s=t('
'),l=t('
');this.options.verticalConnection?(s.prependTo(r),l.appendTo(r)):(i.appendTo(r),s.appendTo(i),l.appendTo(i));var p=this,c={},h={},u={},d={},f={operator:r,title:n,body:a,connectorSets:u,connectors:d,connectorArrows:c,connectorSmallArrows:h};function k(e,o,r,n){var a=t('
');if(a.data("connector_type",n),a.appendTo(r),c[e]=[],h[e]=[],d[e]=[],u[e]=a,t.isArray(o.label))for(var i=0;i');i.appendTo(n),i.data("connector",e),i.data("sub_connector",a);var s=t('
');s.text(o.label.replace("(:i)",a+1)),s.appendTo(i);var l=t('
');l.appendTo(i);var p=t('
');p.appendTo(i),r.connectors[e].push(i),r.connectorArrows[e].push(l),r.connectorSmallArrows[e].push(p)},getOperatorElement:function(t){return this._getOperatorFullElement(t).operator},addOperator:function(t){for(;void 0!==this.data.operators[this.operatorNum];)this.operatorNum++;return this.createOperator(this.operatorNum,t),this.operatorNum},createOperator:function(e,o){o.internal={},this._refreshInternalProperties(o);var r=this._getOperatorFullElement(o);if(!this.callbackEvent("operatorCreate",[e,o,r]))return!1;var n=this.options.grid;n&&(o.top=Math.round(o.top/n)*n,o.left=Math.round(o.left/n)*n),r.operator.appendTo(this.objs.layers.operators),r.operator.css({top:o.top,left:o.left}),r.operator.data("operator_id",e),this.data.operators[e]=o,this.data.operators[e].internal.els=r,e==this.selectedOperatorId&&this._addSelectedClass(e);var a,i,s=this;function l(t,e){for(var r in o.top=e.top,o.left=e.left,s.data.links)if(s.data.links.hasOwnProperty(r)){var n=s.data.links[r];n.fromOperator!=t&&n.toOperator!=t||s._refreshLinkPositions(r)}}this.options.canUserMoveOperators&&r.operator.draggable({containment:!o.internal.properties.uncontained&&this.element,handle:".flowchart-operator-title, .flowchart-operator-body",start:function(e,o){if(null==s.lastOutputConnectorClicked){var r=s.element.offset();a=(e.pageX-r.left)/s.positionRatio-parseInt(t(e.target).css("left"),10),i=(e.pageY-r.top)/s.positionRatio-parseInt(t(e.target).css("top"),10)}else e.preventDefault()},drag:function(e,n){if(s.options.grid){var p=s.options.grid,c=s.element.offset();if(n.position.left=Math.round(((e.pageX-c.left)/s.positionRatio-a)/p)*p,n.position.top=Math.round(((e.pageY-c.top)/s.positionRatio-i)/p)*p,!o.internal.properties.uncontained){var h=t(this);n.position.left=Math.min(Math.max(n.position.left,0),s.element.width()-h.outerWidth()),n.position.top=Math.min(Math.max(n.position.top,0),s.element.height()-h.outerHeight())}n.offset.left=Math.round(n.position.left+c.left),n.offset.top=Math.round(n.position.top+c.top),r.operator.css({left:n.position.left,top:n.position.top})}l(t(this).data("operator_id"),n.position)},stop:function(e,o){s._unsetTemporaryLink();var n=t(this).data("operator_id");l(n,o.position),r.operator.css({height:"auto"}),s.callbackEvent("operatorMoved",[n,o.position]),s.callbackEvent("afterChange",["operator_moved"])}});this.callbackEvent("afterChange",["operator_create"])},_connectorClicked:function(t,e,o,r){if("outputs"==r){new Date;this.lastOutputConnectorClicked={operator:t,connector:e,subConnector:o},this.objs.layers.temporaryLink.show();var n=this.getConnectorPosition(t,e,o),a=n.x+n.width,i=n.y;this.objs.temporaryLink.setAttribute("x1",a.toString()),this.objs.temporaryLink.setAttribute("y1",i.toString()),this._mousemove(a,i)}if("inputs"==r&&null!=this.lastOutputConnectorClicked){var s={fromOperator:this.lastOutputConnectorClicked.operator,fromConnector:this.lastOutputConnectorClicked.connector,fromSubConnector:this.lastOutputConnectorClicked.subConnector,toOperator:t,toConnector:e,toSubConnector:o};this.addLink(s),this._unsetTemporaryLink()}},_unsetTemporaryLink:function(){this.lastOutputConnectorClicked=null,this.objs.layers.temporaryLink.hide()},_mousemove:function(t,e,o){null!=this.lastOutputConnectorClicked&&(this.objs.temporaryLink.setAttribute("x2",t),this.objs.temporaryLink.setAttribute("y2",e))},_click:function(e,o,r){var n=t(r.target);0==n.closest(".flowchart-operator-connector").length&&this._unsetTemporaryLink(),0==n.closest(".flowchart-operator").length&&this.unselectOperator(),0==n.closest(".flowchart-link").length&&this.unselectLink()},_removeSelectedClassOperators:function(){this.objs.layers.operators.find(".flowchart-operator").removeClass("selected")},unselectOperator:function(){if(null!=this.selectedOperatorId){if(!this.callbackEvent("operatorUnselect",[]))return;this._removeSelectedClassOperators(),this.selectedOperatorId=null}},_addSelectedClass:function(t){this.data.operators[t].internal.els.operator.addClass("selected")},callbackEvent:function(t,e){var o="on"+t.charAt(0).toUpperCase()+t.slice(1),r=this.options[o].apply(this,e);if(!1!==r){var n={result:r};this.element.trigger(t,e.concat([n])),r=n.result}return r},selectOperator:function(t){this.callbackEvent("operatorSelect",[t])&&(this.unselectLink(),this._removeSelectedClassOperators(),this._addSelectedClass(t),this.selectedOperatorId=t)},addClassOperator:function(t,e){this.data.operators[t].internal.els.operator.addClass(e)},removeClassOperator:function(t,e){this.data.operators[t].internal.els.operator.removeClass(e)},removeClassOperators:function(t){this.objs.layers.operators.find(".flowchart-operator").removeClass(t)},_addHoverClassOperator:function(t){this.data.operators[t].internal.els.operator.addClass("hover")},_removeHoverClassOperators:function(){this.objs.layers.operators.find(".flowchart-operator").removeClass("hover")},_operatorMouseOver:function(t){this.callbackEvent("operatorMouseOver",[t])&&this._addHoverClassOperator(t)},_operatorMouseOut:function(t){this.callbackEvent("operatorMouseOut",[t])&&this._removeHoverClassOperators()},getSelectedOperatorId:function(){return this.selectedOperatorId},getSelectedLinkId:function(){return this.selectedLinkId},_shadeColor:function(t,e){var o=parseInt(t.slice(1),16),r=e<0?0:255,n=e<0?-1*e:e,a=o>>16,i=o>>8&255,s=255&o;return"#"+(16777216+65536*(Math.round((r-a)*n)+a)+256*(Math.round((r-i)*n)+i)+(Math.round((r-s)*n)+s)).toString(16).slice(1)},colorizeLink:function(t,e){var o=this.data.links[t];o.internal.els.path.setAttribute("stroke",e),o.internal.els.rect.setAttribute("fill",e),this.options.verticalConnection?(o.internal.els.fromSmallConnector.css("border-top-color",e),o.internal.els.toSmallConnector.css("border-top-color",e)):(o.internal.els.fromSmallConnector.css("border-left-color",e),o.internal.els.toSmallConnector.css("border-left-color",e))},uncolorizeLink:function(t){this.colorizeLink(t,this.getLinkMainColor(t))},_connecterMouseOver:function(t){this.selectedLinkId!=t&&this.colorizeLink(t,this._shadeColor(this.getLinkMainColor(t),-.4))},_connecterMouseOut:function(t){this.selectedLinkId!=t&&this.uncolorizeLink(t)},unselectLink:function(){if(null!=this.selectedLinkId){if(!this.callbackEvent("linkUnselect",[]))return;this.uncolorizeLink(this.selectedLinkId,this.options.defaultSelectedLinkColor),this.selectedLinkId=null}},selectLink:function(t){this.unselectLink(),this.callbackEvent("linkSelect",[t])&&(this.unselectOperator(),this.selectedLinkId=t,this.colorizeLink(t,this.options.defaultSelectedLinkColor))},deleteOperator:function(t){this._deleteOperator(t,!1)},_deleteOperator:function(t,e){if(!this.callbackEvent("operatorDelete",[t,e]))return!1;if(!e)for(var o in this.data.links)if(this.data.links.hasOwnProperty(o)){var r=this.data.links[o];r.fromOperator!=t&&r.toOperator!=t||this._deleteLink(o,!0)}e||t!=this.selectedOperatorId||this.unselectOperator(),this.data.operators[t].internal.els.operator.remove(),delete this.data.operators[t],this.callbackEvent("afterChange",["operator_delete"])},deleteLink:function(t){this._deleteLink(t,!1)},_deleteLink:function(t,e){if(this.selectedLinkId==t&&this.unselectLink(),this.callbackEvent("linkDelete",[t,e])||e){this.colorizeLink(t,"transparent");var o=this.data.links[t],r=o.fromOperator,n=o.fromConnector,a=o.toOperator,i=o.toConnector,s=o.internal.els.overallGroup;s.remove?s.remove():s.parentNode.removeChild(s),delete this.data.links[t],this._cleanMultipleConnectors(r,n,"from"),this._cleanMultipleConnectors(a,i,"to"),this.callbackEvent("afterChange",["link_delete"])}},_cleanMultipleConnectors:function(t,e,o){if(this.data.operators[t].internal.properties["from"==o?"outputs":"inputs"][e].multiple){var r=-1,n=o+"Operator",a=o+"Connector",i=o+"SubConnector",s=this.data.operators[t].internal.els,l=s.connectors[e],p=l.length;for(var c in this.data.links)if(this.data.links.hasOwnProperty(c)){var h=this.data.links[c];h[n]==t&&h[a]==e&&r=1.10",
40 | "jquery-ui": ">=1.11"
41 | }
42 | }
--------------------------------------------------------------------------------