UNPKG

38.7 kBHTMLView Raw
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4<meta charset="utf-8"/>
5<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
6
7<link rel="stylesheet" href="../assets/css/style.css"/>
8<!-- Copyright 1998-2021 by Northwoods Software Corporation. --> <title> GoJS Validation -- Northwoods Software </title>
9 <link rel="stylesheet" href="../assets/css/prism.css" />
10 </head>
11 <script>
12
13 window.diagrams = [];
14 window.goCode = function(pre, w, h, parentid, animation) {
15 window.diagrams.push([pre, w, h, parentid, animation]);
16 }
17 </script>
18 <body>
19 <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary">
20 <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2">
21 <div class="md:pl-4">
22 <a class="text-white hover:text-white no-underline hover:no-underline
23 font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../">
24 <h1 class="mb-0 p-1 ">GoJS</h1>
25 </a>
26 </div>
27 <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation">
28 <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
29 <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
30 <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
31 </svg>
32 </button>
33 <div id="topnavList" class="hidden sm:block items-center w-auto mt-0 text-white p-0 z-20">
34 <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0">
35 <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li>
36 <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li>
37 <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li>
38 <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li>
39 <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li>
40 <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li>
41 <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li>
42 <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html"
43 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li>
44 <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html"
45 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li>
46 </ul>
47 </div>
48 </div>
49 <hr class="border-b border-gray-600 opacity-50 my-0 py-0" />
50 </nav>
51
52 <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
53
54 <div id="navSide" class="flex flex-col w-full md:w-40 lg:w-48 text-gray-700 bg-white flex-shrink-0">
55 <div class="flex-shrink-0 px-8 py-4">
56 <button id="navButton" class="rounded-lg md:hidden focus:outline-none focus:ring" aria-label="Navigation">
57 <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
58 <path id="navOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
59 <path id="navClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
60 </svg>
61 </button>
62 </div>
63 <nav id="navList" class="min-h-screen hidden md:block sidebar-nav flex-grow px-1 lg:px-4 pb-4 md:pb-0 md:overflow-y-auto break-words">
64 <a href="index.html">Basics</a>
65 <a href="buildingObjects.html">Building Parts</a>
66 <a href="usingModels.html">Using Models</a>
67 <a href="dataBinding.html">Data Binding</a>
68 <a href="react.html">GoJS with React</a>
69 <a href="angular.html">GoJS with Angular</a>
70 <a href="textBlocks.html">TextBlocks</a>
71 <a href="shapes.html">Shapes</a>
72 <a href="pictures.html">Pictures</a>
73 <a href="panels.html">Panels</a>
74 <a href="tablePanels.html">Table Panels</a>
75 <a href="brush.html">Brushes</a>
76 <a href="sizing.html">Sizing Objects</a>
77 <a href="itemArrays.html">Item Arrays</a>
78 <a href="changedEvents.html">Changed Events</a>
79 <a href="transactions.html">Transactions</a>
80 <a href="viewport.html">Coordinates</a>
81 <a href="initialView.html">Initial View</a>
82 <a href="collections.html">Collections</a>
83 <a href="links.html">Links</a>
84 <a href="linkLabels.html">Link Labels</a>
85 <a href="connectionPoints.html">Link Points</a>
86 <a href="ports.html">Ports</a>
87 <a href="nodes.html">Nodes</a>
88 <a href="debugging.html">Debugging</a>
89 <a href="layouts.html">Layouts</a>
90 <a href="trees.html">Trees</a>
91 <a href="subtrees.html">SubTrees</a>
92 <a href="groups.html">Groups</a>
93 <a href="subgraphs.html">SubGraphs</a>
94 <a href="sizedGroups.html">Sized Groups</a>
95 <a href="selection.html">Selection</a>
96 <a href="highlighting.html">Highlighting</a>
97 <a href="animation.html">Animation</a>
98 <a href="toolTips.html">ToolTips</a>
99 <a href="contextmenus.html">Context Menus</a>
100 <a href="events.html">Diagram Events</a>
101 <a href="tools.html">Tools</a>
102 <a href="commands.html">Commands</a>
103 <a href="permissions.html">Permissions</a>
104 <a href="validation.html">Validation</a>
105 <a href="HTMLInteraction.html">HTML Interaction</a>
106 <a href="layers.html">Layers &amp; Z-ordering</a>
107 <a href="palette.html">Palette</a>
108 <a href="overview.html">Overview</a>
109 <a href="resizing.html">Resizing Diagrams</a>
110 <a href="replacingDeleting.html">Replacing and Deleting</a>
111 <a href="buttons.html">Buttons</a>
112 <a href="templateMaps.html">Template Maps</a>
113 <a href="legends.html">Legends and Titles</a>
114 <a href="extensions.html">Extensions</a>
115 <a href="geometry.html">Geometry Strings</a>
116 <a href="grids.html">Grid Patterns</a>
117 <a href="graduatedPanels.html">Graduated Panels</a>
118 <a href="makingImages.html">Diagram Images</a>
119 <a href="makingSVG.html">Diagram SVG</a>
120 <a href="printing.html">Printing</a>
121 <a href="serverSideImages.html">Server-side Images</a>
122 <a href="nodeScript.html">GoJS in Node.js</a>
123 <a href="testing.html">Testing</a>
124 <a href="storage.html">Storage</a>
125 <a href="performance.html">Performance</a>
126 <a href="source.html">Building from Source</a>
127 <a href="platforms.html">Platforms</a>
128 <a href="deployment.html">Deployment</a>
129 </nav>
130 </div>
131 <div class="pt-4 px-2 md:px-0 lg:px-4 pb-16 w-full overflow-hidden">
132
133
134
135
136<h1>Validation</h1>
137<p>
138Some operations require more sophisticated controls than the binary permission flags discussed in the previous <a href="permissions.html">section</a>.
139When the user tries to draw a new link or reconnect an existing link, your application may want to restrict which links may be made,
140depending on the data.
141When the user tries to add a node to a group, your application may want to control whether it is permitted for that particular
142node in that particular group.
143When the user edits some text, your application may want to limit the kinds of strings that they enter.
144</p>
145<p>
146Although not exactly "validation", you can also limit how users drag (move or copy) parts by setting several properties on <a>Part</a> and customizing the <a>DraggingTool</a>.
147</p>
148
149<h2 id="LinkingValidation">Linking Validation</h2>
150<p>
151There are a number of <a>GraphObject</a> properties that let you control what links the user may draw or reconnect.
152These properties apply to each port element and affect the links that may connect with that port.
153</p>
154
155<h3 id="LinkableProperties">Linkable properties</h3>
156<p>
157The primary properties are <a>GraphObject.fromLinkable</a> and <a>GraphObject.toLinkable</a>.
158If you do not have a <a>Node</a> containing an element with fromLinkable: true and another node
159with toLinkable: true, the user will not be able to draw a new link between the nodes.
160</p>
161<pre class="lang-js" id="linkable"><code>
162 diagram.nodeTemplate =
163 $(go.Node, "Auto",
164 new go.Binding("location", "loc", go.Point.parse),
165 $(go.Shape, "Ellipse",
166 { fill: "green", portId: "", cursor: "pointer" },
167 new go.Binding("fromLinkable", "from"),
168 new go.Binding("toLinkable", "to")),
169 $(go.TextBlock,
170 { stroke: "white", margin: 3 },
171 new go.Binding("text", "key"))
172 );
173
174 var nodeDataArray = [
175 { key: "From1", loc: "0 0", from: true },
176 { key: "From2", loc: "0 100", from: true },
177 { key: "To1", loc: "150 0", to: true },
178 { key: "To2", loc: "150 100", to: true }
179 ];
180 var linkDataArray = [
181 // initially no links
182 ];
183 diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
184</code></pre>
185<script>goCode("linkable", 600, 150)</script>
186<p>
187Mouse down on the green ellipse (the cursor changes to a "pointer") and drag to start drawing a new link.
188Note how the only permitted links are those going from a "From" node to a "To" node.
189This is true even if you start the linking gesture on a "To" node.
190</p>
191
192<h3 id="SpanOfLinkableProperties">Span of Linkable properties</h3>
193<p>
194Because the <a>TextBlock</a> in the above example is not declared to be a port (i.e. there is no value for <a>GraphObject.portId</a>),
195mouse events on the TextBlock do not start the <a>LinkingTool</a>.
196This allows users the ability to select and move the node as well as any number of other operations.
197</p>
198<p>
199You can certainly declare a <a>Panel</a> to have <a>GraphObject.fromLinkable</a> or <a>GraphObject.toLinkable</a> be true.
200This will cause all elements inside that panel to behave as part of the port, including starting a linking operation.
201Sometimes you will want to make the whole <a>Node</a> linkable.
202If you still want the user to be able to select and drag the node, you will need to make some easy-to-click elements not-"linkable" within the node.
203You can do that by explicitly setting <a>GraphObject.fromLinkable</a> and/or <a>GraphObject.toLinkable</a> to false.
204The default value for those two properties is null, which means the "linkable"-ness is inherited from the containing panel.
205</p>
206
207<h2 id="OtherLinkingPermissionProperties">Other linking permission properties</h2>
208<p>
209Just because you have set <a>GraphObject.fromLinkable</a> and <a>GraphObject.toLinkable</a>
210to true on the desired port objects
211does not mean that you want to allow users to create a link from every such port/node to every other port/node.
212There are other <a>GraphObject</a> properties governing linkability for both the "from" and the "to" ends.
213</p>
214
215<h3 id="LinkableDuplicatesProperties">LinkableDuplicates properties</h3>
216<p>
217One restriction that you may have noticed before is that the user cannot draw a second link between the same pair
218of nodes in the same direction.
219This example sets <a>GraphObject.fromLinkableDuplicates</a> or <a>GraphObject.toLinkableDuplicates</a> to true,
220in order to permit such duplicate links between nodes.
221</p>
222<pre class="lang-js" id="linkableDuplicates"><code>
223 diagram.nodeTemplate =
224 $(go.Node, "Auto",
225 new go.Binding("location", "loc", go.Point.parse),
226 $(go.Shape, "Ellipse",
227 { fill: "green", portId: "", cursor: "pointer",
228 fromLinkableDuplicates: true, toLinkableDuplicates: true },
229 new go.Binding("fromLinkable", "from"),
230 new go.Binding("toLinkable", "to")),
231 $(go.TextBlock,
232 { stroke: "white", margin: 3 },
233 new go.Binding("text", "key"))
234 );
235
236 var nodeDataArray = [
237 { key: "From1", loc: "0 0", from: true },
238 { key: "From2", loc: "0 100", from: true },
239 { key: "To1", loc: "150 0", to: true },
240 { key: "To2", loc: "150 100", to: true }
241 ];
242 var linkDataArray = [
243 // initially no links
244 ];
245 diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
246</code></pre>
247<script>goCode("linkableDuplicates", 600, 150)</script>
248<p>
249Now try drawing multiple links between "From1" and "To1".
250You can see how the links are automatically spread apart.
251Try dragging one of the nodes to see what happens with the link routing.
252A similar effect occurs also when the link's <a>Link.curve</a> is <a>Link,Bezier</a>.
253</p>
254
255<h3 id="LinkableSelfNodeProperties">LinkableSelfNode properties</h3>
256<p>
257Another standard restriction is that the user cannot draw a link from a node to itself.
258Again it is easy to remove that restriction: just set <a>GraphObject.fromLinkableSelfNode</a>
259and <a>GraphObject.toLinkableSelfNode</a> to true.
260Note though that each node has to be both <a>GraphObject.fromLinkable</a> and <a>GraphObject.toLinkable</a>.
261</p>
262<pre class="lang-js" id="linkableSelfNodes"><code>
263 diagram.nodeTemplate =
264 $(go.Node, "Auto",
265 new go.Binding("location", "loc", go.Point.parse),
266 $(go.Shape, "Ellipse",
267 { fill: "green", portId: "", cursor: "pointer",
268 fromLinkable: true, toLinkable: true,
269 fromLinkableDuplicates: true, toLinkableDuplicates: true,
270 fromLinkableSelfNode: true, toLinkableSelfNode: true }),
271 $(go.TextBlock,
272 { stroke: "white", margin: 3 },
273 new go.Binding("text", "key"))
274 );
275
276 var nodeDataArray = [
277 { key: "Node1", loc: "0 0" },
278 { key: "Node2", loc: "150 50" }
279 ];
280 var linkDataArray = [
281 // initially no links
282 ];
283 diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
284</code></pre>
285<script>goCode("linkableSelfNodes", 600, 150)</script>
286<p>
287To draw a reflexive link, start drawing a new link but stay near the node when you release the mouse button.
288This example also sets the "Duplicates" properties to true, so that you can draw multiple reflexive links.
289</p>
290
291<p>
292In these examples there is only one port per node.
293When there are multiple ports in a node, the restrictions actually apply per port, not per node.
294But the restrictions of the "LinkableSelfNode" properties do span the whole node,
295so they must be applied to both ports within a node for a link to connect to its own node.
296</p>
297
298<h3 id="MaxLinksProperties">MaxLinks properties</h3>
299<p>
300The final linking restriction properties control how many links may connect to a node/port.
301This example sets the <a>GraphObject.toMaxLinks</a> property to 2,
302even though <a>GraphObject.toLinkableDuplicates</a> is true,
303to limit how many links may go into "to" nodes.
304</p>
305<pre class="lang-js" id="linkableMax"><code>
306 diagram.nodeTemplate =
307 $(go.Node, "Auto",
308 new go.Binding("location", "loc", go.Point.parse),
309 $(go.Shape, "Ellipse",
310 { fill: "green", portId: "", cursor: "pointer",
311 fromLinkableDuplicates: true, toLinkableDuplicates: true,
312 toMaxLinks: 2 }, // at most TWO links can come into this node
313 new go.Binding("fromLinkable", "from"),
314 new go.Binding("toLinkable", "to")),
315 $(go.TextBlock,
316 { stroke: "white", margin: 3 },
317 new go.Binding("text", "key"))
318 );
319
320 var nodeDataArray = [
321 { key: "From1", loc: "0 0", from: true },
322 { key: "From2", loc: "0 100", from: true },
323 { key: "To1", loc: "150 0", to: true },
324 { key: "To2", loc: "150 100", to: true }
325 ];
326 var linkDataArray = [
327 // initially no links
328 ];
329 diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
330</code></pre>
331<script>goCode("linkableMax", 600, 150)</script>
332<p>
333This example has no limit on the number of links that may come out of "from" nodes.
334</p>
335<p>
336If this property is set, it is most commonly set to one.
337Of course it depends on the nature of the application.
338</p>
339<p>
340Note that the <a>GraphObject.toMaxLinks</a> and <a>GraphObject.fromMaxLinks</a> properties are independent of each other.
341If you want to control the total number of links connecting with a port, not only "to" or "from" but both directions,
342then you cannot use those two properties and instead must implement your own link validation predicate, as discussed below.
343</p>
344
345<h2 id="CyclesInGraphs">Cycles in graphs</h2>
346<p>
347If you want to make sure that the graph structure that your users create never have any cycles of links,
348or that the graph is always tree-structured, <b>GoJS</b> makes that easy to enforce.
349Just set <a>Diagram.validCycle</a> to <a>Diagram,CycleNotDirected</a> or <a>Diagram,CycleDestinationTree</a>.
350The default value is <a>Diagram,CycleAll</a>, which imposes no restrictions -- all kinds of link cycles are allowed.
351</p>
352<p>
353This example has nodes that allow links both to and from each node.
354However the assignment of <a>Diagram.validCycle</a> will prevent the user from drawing
355a second incoming link to any node and also ensures that the user draw no cycles in the graph.
356</p>
357<pre class="lang-js" id="tree"><code>
358 diagram.nodeTemplate =
359 $(go.Node, "Auto",
360 $(go.Shape, "Ellipse",
361 { fill: "green", portId: "", cursor: "pointer",
362 fromLinkable: true, toLinkable: true }),
363 $(go.TextBlock,
364 { stroke: "white", margin: 3 },
365 new go.Binding("text", "key"))
366 );
367
368 var nodeDataArray = [
369 { key: "Node1" }, { key: "Node2" }, { key: "Node3" },
370 { key: "Node4" }, { key: "Node5" }, { key: "Node6" },
371 { key: "Node7" }, { key: "Node8" }, { key: "Node9" }
372 ];
373 var linkDataArray = [
374 // initially no links
375 ];
376 diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
377
378 // only allow links that maintain tree-structure
379 diagram.validCycle = go.Diagram.CycleDestinationTree;
380</code></pre>
381<script>goCode("tree", 600, 250)</script>
382<p>
383As you draw more links you can see how the set of potential linking destinations keeps getting smaller.
384</p>
385
386<h2 id="GeneralLinkingValidation">General linking validation</h2>
387<p>
388It may be the case that the semantics of your application will cause the set of valid link destinations to depend
389on the node data (i.e. at the node and port at which the link started from and at the possible destination node/port)
390in a manner that can only be implemented using code: a predicate function.
391</p>
392<p>
393You can implement such domain-specific validation by setting <a>LinkingBaseTool.linkValidation</a> or <a>Node.linkValidation</a>.
394These predicates, if supplied, are called for each pair of ports that the linking tool considers.
395If the predicate returns false, the link may not be made.
396Setting the property on the <a>LinkingTool</a> or <a>RelinkingTool</a>causes the predicate to be applied to all linking operations,
397whereas setting the property on the <a>Node</a> only applies to linking operations involving that node.
398The predicates are called only if all of the standard link checks pass, based on the properties discussed above.
399</p>
400<p>
401In this example there are nodes of three different colors.
402The <a>LinkingTool</a> and <a>RelinkingTool</a> are customized to use a function, <code>sameColor</code>,
403to make sure the links only connect nodes of the same color.
404Mouse-down and drag on the ellipses (where the cursor changes to a "pointer") to start drawing a new link.
405You will see that the only permitted link destinations are nodes of the same color that do not already have a link to it from the same node.
406</p>
407<pre class="lang-js" id="linking"><code>
408 diagram.nodeTemplate =
409 $(go.Node, "Auto",
410 $(go.Shape, "Ellipse",
411 { cursor: "pointer", portId: "",
412 fromLinkable: true, toLinkable: true },
413 new go.Binding("fill", "color")),
414 $(go.TextBlock,
415 { stroke: "white", margin: 3 },
416 new go.Binding("text", "key"))
417 );
418
419 diagram.linkTemplate =
420 $(go.Link,
421 { curve: go.Link.Bezier, relinkableFrom: true, relinkableTo: true },
422 $(go.Shape, { strokeWidth: 2 },
423 new go.Binding("stroke", "fromNode", function(n) { return n.data.color; })
424 .ofObject()),
425 $(go.Shape, { toArrow: "Standard", stroke: null},
426 new go.Binding("fill", "fromNode", function(n) { return n.data.color; })
427 .ofObject())
428 );
429
430 // this predicate is true if both nodes have the same color
431 function sameColor(fromnode, fromport, tonode, toport) {
432 return fromnode.data.color === tonode.data.color;
433 // this could look at the fromport.fill and toport.fill instead,
434 // assuming that the ports are Shapes, which they are because portID was set on them,
435 // and that there is a data Binding on the Shape.fill
436 }
437
438 // only allow new links between ports of the same color
439 diagram.toolManager.linkingTool.linkValidation = sameColor;
440
441 // only allow reconnecting an existing link to a port of the same color
442 diagram.toolManager.relinkingTool.linkValidation = sameColor;
443
444 var nodeDataArray = [
445 { key: "Red1", color: "red" },
446 { key: "Blue1", color: "blue" },
447 { key: "Green1", color: "green" },
448 { key: "Green2", color: "green" },
449 { key: "Red2", color: "red" },
450 { key: "Blue2", color: "blue" },
451 { key: "Red3", color: "red" },
452 { key: "Green3", color: "green" },
453 { key: "Blue3", color: "blue" }
454 ];
455 var linkDataArray = [
456 // initially no links
457 ];
458 diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
459</code></pre>
460<script>goCode("linking", 600, 250)</script>
461<p>
462To emphasize the color restriction, links have their colors bound to the "from" node data.
463</p>
464
465<h3 id="Limitingtotalnumberoflinksconnectingwithanode">Limiting total number of links connecting with a node</h3>
466<p>
467 One can limit the number of links coming into a port by setting <a>GraphObject.toMaxLinks</a>.
468 Similarly, one can limit the number of links coming out of a port by setting <a>GraphObject.fromMaxLinks</a>.
469 But what if you want to limit the total number of links connecting with a port regardless of whether they are coming into or going out of a port?
470 Such constraints can only be implemented by a link validation predicate.
471</p>
472<p>
473 When wanting to limit the total number of links in either direction, connecting with each port, one can use this <a>Node.linkValidation</a> predicate:
474</p>
475<pre class="lang-js"><code>
476 $(go.Node, . . .,
477 {
478 linkValidation: function(fromnode, fromport, tonode, toport) {
479 // total number of links connecting with a port is limited to 1:
480 return fromnode.findLinksConnected(fromport.portId).count +
481 tonode.findLinksConnected(toport.portId).count < 1;
482 }
483 }, . . .
484</code></pre>
485<p>
486 When wanting to limit the total number of links in either direction, connecting with a node for all of its ports, one can use this <a>Node.linkValidation</a> predicate:
487</p>
488<pre class="lang-js"><code>
489 $(go.Node, . . .,
490 {
491 linkValidation: function(fromnode, fromport, tonode, toport) {
492 // total number of links connecting with all ports of a node is limited to 1:
493 return fromnode.linksConnected.count + tonode.linksConnected.count < 1;
494 }
495 }, . . .
496</code></pre>
497
498<h2 id="GroupingValidation">Grouping validation</h2>
499<p>
500When you want to limit the kinds of nodes that the user may add to a particular group,
501you can implement a predicate as the <a>CommandHandler.memberValidation</a> or <a>Group.memberValidation</a> property.
502Setting the property on the <a>CommandHandler</a> causes the predicate to be applied to all Groups,
503whereas setting the property on the <a>Group</a> only applies to that group.
504</p>
505<p>
506In this example the <code>samePrefix</code> predicate is used to determine if a Node
507may be dropped into a Group.
508Try dragging the simple textual nodes on the left side into either of the groups on the right side.
509Only when dropping the node onto a group that is highlit "green" will the node be added as a member of the group.
510You can verify that by moving the group to see if the textual node moves too.
511</p>
512<pre class="lang-js" id="grouping"><code>
513 // this predicate is true if both node data keys start with the same letter
514 function samePrefix(group, node) {
515 if (group === null) return true; // when maybe dropping a node in the background
516 if (node instanceof go.Group) return false; // don't add Groups to Groups
517 return group.data.key.charAt(0) === node.data.key.charAt(0);
518 };
519
520 diagram.nodeTemplate =
521 $(go.Node,
522 new go.Binding("location", "loc", go.Point.parse),
523 $(go.TextBlock,
524 new go.Binding("text", "key"))
525 );
526
527 diagram.groupTemplate =
528 $(go.Group, "Vertical",
529 {
530 // only allow those simple nodes that have the same data key prefix:
531 memberValidation: samePrefix,
532 // don't need to define handlers on member Nodes and Links
533 handlesDragDropForMembers: true,
534 // support highlighting of Groups when allowing a drop to add a member
535 mouseDragEnter: function(e, grp, prev) {
536 // this will call samePrefix; it is true if any node has the same key prefix
537 if (grp.canAddMembers(grp.diagram.selection)) {
538 var shape = grp.findObject("SHAPE");
539 if (shape) shape.fill = "green";
540 grp.diagram.currentCursor = "";
541 } else {
542 grp.diagram.currentCursor = "not-allowed";
543 }
544 },
545 mouseDragLeave: function(e, grp, next) {
546 var shape = grp.findObject("SHAPE");
547 if (shape) shape.fill = "rgba(128,128,128,0.33)";
548 grp.diagram.currentCursor = "";
549 },
550 // actually add permitted new members when a drop occurs
551 mouseDrop: function(e, grp) {
552 if (grp.canAddMembers(grp.diagram.selection)) {
553 // this will only add nodes with the same key prefix
554 grp.addMembers(grp.diagram.selection, true);
555 } else { // and otherwise cancel the drop
556 grp.diagram.currentTool.doCancel();
557 }
558 }
559 },
560 // make sure all Groups are behind all regular Nodes
561 { layerName: "Background" },
562 new go.Binding("location", "loc", go.Point.parse),
563 $(go.TextBlock,
564 { alignment: go.Spot.Left, font: "Bold 12pt Sans-Serif" },
565 new go.Binding("text", "key")),
566 $(go.Shape,
567 { name: "SHAPE", width: 100, height: 100,
568 fill: "rgba(128,128,128,0.33)" })
569 );
570
571 diagram.mouseDrop = function(e) {
572 // dropping in diagram background removes nodes from any group
573 diagram.commandHandler.addTopLevelParts(diagram.selection, true);
574 };
575
576 var nodeDataArray = [
577 { key: "A group", isGroup: true, loc: "100 10" },
578 { key: "B group", isGroup: true, loc: "100 140" },
579 { key: "A1", loc: "10 30" }, // can be added to "A" group
580 { key: "A2", loc: "10 60" },
581 { key: "B1", loc: "10 90" }, // can be added to "B" group
582 { key: "B2", loc: "10 120" },
583 { key: "C1", loc: "10 150" } // cannot be added to either group
584 ];
585 diagram.model = new go.GraphLinksModel(nodeDataArray, []);
586</code></pre>
587<script>goCode("grouping", 600, 300)</script>
588<p>
589These groups are fixed size groups -- they do not use <a>Placeholder</a>s.
590So when a node is dropped into them the group does not automatically resize itself to surround its member nodes.
591But that is also a benefit when dragging a node out of a group.
592</p>
593<p>
594The validation predicate is also called when dragging a node that is already a member of a group.
595You can see how it is acceptable to drop the node into its existing containing group.
596And when it is dragged outside of the group into the diagram's background, the predicate is called with null as the "group" argument.
597</p>
598<p>
599In this example it is always OK to drop a node in the background of the diagram rather than into a group.
600If you want to disallow dropping in the background, you can call <code>myDiagram.currentTool.doCancel()</code>
601in the <a>Diagram.mouseDrop</a> event handler.
602If you want to show feedback during the drag in the background, you can implement a <a>Diagram.mouseDragOver</a> event handler that sets
603<code>myDiagram.currentCursor = "not-allowed"</code>.
604This would be behavior similar to that implemented above when dragging inside a Group.
605</p>
606
607
608<h2 id="TextEditingValidation">Text editing validation</h2>
609<p>
610You can also limit what text the user enters when they do in-place text editing of a <a>TextBlock</a>.
611First, to enable any editing at all, you will need to set <a>TextBlock.editable</a> to true.
612There may be many TextBlocks within a Part, but you might want to limit text editing to particular TextBlocks.
613</p>
614<p>
615Normally there is no limitation on what text the user may enter.
616If you want to provide a predicate to approve the input when the user finishes editing,
617set the <a>TextEditingTool.textValidation</a> or <a>TextBlock.textValidation</a> property.
618Setting the property on the <a>TextEditingTool</a> causes the predicate to be applied to all TextBlocks,
619whereas setting the property on the <a>TextBlock</a> only applies to that text object.
620</p>
621<pre class="lang-js" id="textEditing"><code>
622 // this predicate is true if the new string has at least three characters
623 // and has a vowel in it
624 function okName(textblock, oldstr, newstr) {
625 return newstr.length >= 3 && /[aeiouy]/i.test(newstr);
626 };
627
628 diagram.nodeTemplate =
629 $(go.Node, "Auto",
630 $(go.Shape, { fill: "lightyellow" }),
631 $(go.Panel, "Vertical",
632 { margin: 3 },
633 $(go.TextBlock,
634 { editable: true }, // no validation predicate
635 new go.Binding("text", "text1")),
636 $(go.TextBlock,
637 { editable: true,
638 isMultiline: false, // don't allow embedded newlines
639 textValidation: okName }, // new string must be an OK name
640 new go.Binding("text", "text2"))
641 )
642 );
643
644 var nodeDataArray = [
645 { key: 1, text1: "Hello", text2: "Dolly!" },
646 { key: 2, text1: "Goodbye", text2: "Mr. Chips" }
647 ];
648 diagram.model = new go.GraphLinksModel(nodeDataArray, []);
649</code></pre>
650<script>goCode("textEditing", 600, 100)</script>
651<p>
652 Note how editing the top TextBlock accepts text without any vowels,
653 but the bottom one does not accept it and instead leaves the text editor open.
654</p>
655<p>
656 If you want to execute code after a text edit completes, implement a "TextEdited"
657 <a>DiagramEvent</a> listener.
658</p>
659
660<h3 id="ShowingTextEditingErrorMessage">Showing a Text Editing Error Message</h3>
661<p>
662If you would like to show a custom error message when text validation fails,
663one way is to show a tooltip <a>Adornment</a>.
664Here is an example where a valid string must contain the letter "W".
665</p>
666<pre class="lang-js" id="textEditingMessage"><code>
667diagram.nodeTemplate =
668 $(go.Node, "Auto",
669 $(go.Shape,
670 { fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
671 new go.Binding("fill", "color")),
672 $(go.TextBlock,
673 {
674 margin: 8,
675 editable: true,
676 isMultiline: false,
677 textValidation: function(tb, olds, news) {
678 return news.indexOf("W") >= 0; // new string must contain a "W"
679 },
680 errorFunction: function(tool, olds, news) {
681 // create and show tooltip about why editing failed for this textblock
682 var mgr = tool.diagram.toolManager;
683 mgr.hideToolTip(); // hide any currently showing tooltip
684 var node = tool.textBlock.part;
685 // create a GoJS tooltip, which is an Adornment
686 var tt = $("ToolTip",
687 {
688 "Border.fill": "pink",
689 "Border.stroke": "red",
690 "Border.strokeWidth": 2
691 },
692 $(go.TextBlock,
693 "Unable to replace the string '" + olds + "' with '" + news +
694 "' on node '" + node.key +
695 "'\nbecause the new string does not contain the capital letter 'W'."));
696 mgr.showToolTip(tt, node);
697 },
698 textEdited: function(tb, olds, news) {
699 var mgr = tb.diagram.toolManager;
700 mgr.hideToolTip();
701 }
702 },
703 new go.Binding("text").makeTwoWay())
704 );
705
706diagram.model = new go.GraphLinksModel([
707 { key: 1, text: "Alpha" },
708 { key: 2, text: "Beta" }
709], [
710 { from: 1, to: 2 }
711]);
712</code></pre>
713<script>goCode("textEditingMessage", 600, 200)</script>
714<p>
715 Try editing the text of a node by twice clicking on some text.
716 If the string does not have the letter "W" in it, it will show an error message describing the problem.
717</p>
718 </div>
719 </div>
720
721 <div class="bg-nwoods-primary">
722 <section class="max-w-screen-lg text-white container mx-auto py-2 px-12">
723 <p id="version" class="leading-none mb-2 my-4">GoJS</p>
724 </section>
725 </div><footer class="bg-nwoods-primary text-white">
726 <div class="container max-w-screen-lg mx-auto px-8">
727 <div class="w-full py-6">
728
729 <div class="max-w-screen-lg xl:max-w-screen-xl mx-auto px-4 sm:px-6 md:px-8">
730 <ul class="text-sm font-medium pb-14 sm:pb-20 grid grid-cols-1 sm:grid-cols-3 gap-y-10">
731 <li class="list-none row-span-2">
732 <h2 class="text-base font-semibold tracking-wide">GoJS</h2>
733 <ul class="list-none space-y-4 md:space-y-1 px-0">
734 <li>
735 <a href="../samples/index.html">Samples</a>
736 </li>
737 <li>
738 <a href="../learn/index.html">Learn</a>
739 </li>
740 <li>
741 <a href="../intro/index.html">Intro</a>
742 </li>
743 <li>
744 <a href="../api/index.html">API</a>
745 </li>
746 <li>
747 <a href="../changelog.html">Changelog</a>
748 </li>
749 <li>
750 <a href="https://github.com/NorthwoodsSoftware/GoJS">GitHub</a>
751 </li>
752 </ul>
753 </li>
754 <li class="list-none row-span-2">
755 <h2 class="text-base font-semibold tracking-wide">Support</h2>
756 <ul class="list-none space-y-4 md:space-y-1 px-0">
757 <li>
758 <a href="https://www.nwoods.com/contact.html"
759 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a>
760 </li>
761 <li>
762 <a href="https://forum.nwoods.com/c/gojs">Forum</a>
763 </li>
764 <li>
765 <a href="https://www.nwoods.com/app/activate.aspx?sku=gojs">Activate</a>
766 </li>
767 <li>
768 <a href="https://www.nwoods.com/sales/index.html"
769 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a>
770 </li>
771 <li>
772 <a href="https://www.youtube.com/channel/UC9We8EoX596-6XFjJDtZIDg">Videos</a>
773 </li>
774 </ul>
775 </li>
776 <li class="list-none row-span-2">
777 <h2 class="text-base font-semibold tracking-wide">Company</h2>
778 <ul class="list-none space-y-4 md:space-y-1 px-0">
779 <li>
780 <a href="https://www.nwoods.com">Northwoods</a>
781 </li>
782 <li>
783 <a href="https://www.nwoods.com/about.html">About Us</a>
784 </li>
785 <li>
786 <a href="https://www.nwoods.com/contact.html">Contact Us</a>
787 </li>
788 <li>
789 <a href="https://twitter.com/northwoodsgo">Twitter</a>
790 </li>
791
792 </ul>
793 </li>
794 </ul>
795
796
797 <p class="text-sm text-gray-100 md:mb-6">
798 Copyright 1998-2021 <a class="text-white" href="https://www.nwoods.com">Northwoods Software</a>
799 </p>
800 </div>
801 </div>
802</footer> </body>
803
804<script async src="https://www.googletagmanager.com/gtag/js?id=UA-1506307-5"></script>
805<script>
806 window.dataLayer = window.dataLayer || [];
807 function gtag(){dataLayer.push(arguments);}
808 gtag('js', new Date()); gtag('config', 'UA-1506307-5');
809 var getOutboundLink = function(url, label) {
810 gtag('event', 'click', {
811 'event_category': 'outbound',
812 'event_label': label,
813 'transport_type': 'beacon'
814 });
815 }
816
817 // topnav
818 var topButton = document.getElementById("topnavButton");
819 var topnavList = document.getElementById("topnavList");
820 topButton.addEventListener("click", function() {
821 this.classList.toggle("active");
822 topnavList.classList.toggle("hidden");
823 document.getElementById("topnavOpen").classList.toggle("hidden");
824 document.getElementById("topnavClosed").classList.toggle("hidden");
825 });
826</script>
827 <script src="../assets/js/prism.js"></script>
828 <script src="../release/go.js"></script>
829 <script src="../assets/js/goDoc.js"></script>
830 <script>
831 document.addEventListener("DOMContentLoaded", function() {
832 if (window.go) document.getElementById('version').textContent = "GoJS version " + go.version;
833 if (window.goDoc) window.goDoc();
834 var d = window.diagrams;
835 for (var i = 0; i < d.length; i++) {
836 var dargs = d[i];
837 goCodeExecute(dargs[0], dargs[1], dargs[2], dargs[3], dargs[4]);
838 }
839 if (window.extra) window.extra();
840 });
841 </script>
842</html>