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 | <title> GoJS Data Binding -- 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 & 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>Data Binding</h1>
|
137 | <p>
|
138 | Data binding is a way to extract a value from a source object and set a property on a target object.
|
139 | The target objects are normally <a>GraphObject</a>s;
|
140 | the source objects are usually JavaScript data objects held in a model.
|
141 | </p>
|
142 | <p>
|
143 | You could write code that gets a desired value from the model data,
|
144 | searches the <a>Diagram</a> for the appropriate <a>Part</a>, searches for the target <a>GraphObject</a>
|
145 | within the visual tree of that Part, and then sets one or more properties on that GraphObject with that value,
|
146 | perhaps after modifying or converting the original value in a way appropriate for the individual properties.
|
147 | However data binding offers a declarative way to specify such behavior just by supplying a
|
148 | <a>Binding</a> that names the properties on the source object and on the target object.
|
149 | </p>
|
150 | <p>
|
151 | Trying to bind a non-existent property of a <a>GraphObject</a> will probably result in a warning or error
|
152 | that you can see in the console log. Always check the console log for any kinds of potential exceptions that
|
153 | are normally suppressed by the binding system.
|
154 | </p>
|
155 | <p>
|
156 | Data bindings are used to keep <a>GraphObject</a> properties in sync with their <a>Part</a>'s data's properties.
|
157 | They are not used to establish or maintain relationships between Parts. Each kind of <a>Model</a> has its
|
158 | own methods for declaring the relationships between parts.
|
159 | </p>
|
160 |
|
161 | <h2 id="RelationshipsOfPartsAndDataAndBinding">The Relationships of Parts and Data and Binding</h2>
|
162 | <p>
|
163 | First, look at a diagram that includes comments about the GraphObjects used to build some example nodes and links:
|
164 | </p>
|
165 | <pre class="lang-js" id="commented" style="display:none"><code>
|
166 | diagram.nodeTemplate =
|
167 | $(go.Node, "Auto",
|
168 | { scale : 1.6, isShadowed: true },
|
169 | new go.Binding("location", "pos", go.Point.parse),
|
170 | { locationSpot: go.Spot.Center, portId: "NODE" },
|
171 | $(go.Shape, "RoundedRectangle",
|
172 | { fill: "white", portId: "SHAPE" },
|
173 | new go.Binding("fill", "color")),
|
174 | $(go.TextBlock,
|
175 | { margin: 4, portId: "TEXTBLOCK" },
|
176 | new go.Binding("text", "txt"))
|
177 | );
|
178 |
|
179 | diagram.linkTemplate =
|
180 | $(go.Link,
|
181 | { isShadowed: true },
|
182 | $(go.Shape,
|
183 | { strokeWidth: 5, stroke: "orange" })
|
184 | );
|
185 |
|
186 | // Represents the nodeDataArray for the two nodes
|
187 | diagram.nodeTemplateMap.add("dataNode",
|
188 | $(go.Node, "Auto",
|
189 | {
|
190 | locationSpot: go.Spot.Center,
|
191 | scale: 1.2,
|
192 | selectionAdorned: true,
|
193 | fromSpot: go.Spot.AllSides,
|
194 | toSpot: go.Spot.AllSides,
|
195 | shadowColor: "#C5C1AA"
|
196 | },
|
197 | new go.Binding("location", "pos", go.Point.parse),
|
198 | $(go.Shape, "Rectangle",
|
199 | { fill: "lightgray" }),
|
200 | $(go.Panel, "Vertical",
|
201 | { defaultStretch: go.GraphObject.Horizontal },
|
202 | $(go.TextBlock, headerStyle(), // Header:
|
203 | { portId: "HEADER" },
|
204 | new go.Binding("text", "head")),
|
205 | $(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
|
206 | $(go.TextBlock, textStyle(), // Location:
|
207 | { portId: "LOCATION" },
|
208 | new go.Binding("text", "loc")),
|
209 | $(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
|
210 | $(go.TextBlock, textStyle(), // Fill:
|
211 | { portId: "FILL" },
|
212 | new go.Binding("text", "color")),
|
213 | $(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
|
214 | $(go.TextBlock, textStyle(), // Text:
|
215 | { portId: "TEXT" },
|
216 | new go.Binding("text", "txt")),
|
217 | $(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
|
218 | $(go.TextBlock, textStyle(), // Text:
|
219 | { portId: "PARENT" },
|
220 | new go.Binding("text", "parent"))
|
221 | )
|
222 | )
|
223 | );
|
224 |
|
225 | diagram.linkTemplateMap.add("dataNode", // Links from dataNode to Nodes
|
226 | $(go.Link,
|
227 | { routing: go.Link.Orthogonal, corner: 5 },
|
228 | $(go.Shape, { stroke: "gray", strokeWidth: 2 }),
|
229 | $(go.Shape, { toArrow: "Standard", stroke: "gray", fill: "gray" })
|
230 | ));
|
231 |
|
232 | diagram.nodeTemplateMap.add("title",
|
233 | $(go.Node, "Auto",
|
234 | new go.Binding("location", "pos", go.Point.parse),
|
235 | $(go.TextBlock,
|
236 | { font: "bold 25pt sans-serif", textAlign: "center"},
|
237 | new go.Binding("text", "txt"))
|
238 | ));
|
239 |
|
240 | diagram.nodeTemplateMap.add("nodeDataArray",
|
241 | $(go.Node, "Auto",
|
242 | {
|
243 | locationSpot: go.Spot.Center,
|
244 | scale: 1.2,
|
245 | selectionAdorned: true,
|
246 | fromSpot: go.Spot.AllSides,
|
247 | toSpot: go.Spot.AllSides,
|
248 | shadowColor: "#C5C1AA"
|
249 | },
|
250 | new go.Binding("location", "pos", go.Point.parse),
|
251 | $(go.Shape, "Rectangle", { fill: "lightgray" }),
|
252 | $(go.Panel, "Vertical",
|
253 | { defaultStretch: go.GraphObject.Horizontal },
|
254 | $(go.TextBlock, headerStyle(),
|
255 | { portId: "HEADER", text: "nodeDataArray" }),
|
256 | $(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
|
257 | $(go.TextBlock, textStyle(),
|
258 | { portId: "dataNode1", desiredSize: new go.Size(NaN,16) }),
|
259 | $(go.Shape, "LineH", { height: 1, stretch: go.GraphObject.Fill }),
|
260 | $(go.TextBlock, textStyle(),
|
261 | { portId: "dataNode2", desiredSize: new go.Size(NaN,16) })
|
262 | )
|
263 | ));
|
264 |
|
265 | // Comments
|
266 | diagram.nodeTemplateMap.add("Comment", // Template for comment node
|
267 | $(go.Node,
|
268 | new go.Binding("location", "pos", go.Point.parse),
|
269 | { locationSpot: go.Spot.Center},
|
270 | $(go.TextBlock,
|
271 | { stroke: "brown", textAlign: "center" },
|
272 | new go.Binding("text", "txt"),
|
273 | new go.Binding("font", "bold", function(b) { return b ? "bold 10pt sans-serif" : "10pt sans-serif"; }))
|
274 | ));
|
275 |
|
276 | diagram.nodeTemplateMap.add("LinkLabel", // Template for comments on links
|
277 | $(go.Node,
|
278 | new go.Binding("segmentIndex"),
|
279 | new go.Binding("segmentOffset")
|
280 | ));
|
281 |
|
282 | diagram.linkTemplateMap.add("Comment", // Template for links from comments
|
283 | $(go.Link,
|
284 | { curve: go.Link.Bezier },
|
285 | new go.Binding("curviness"),
|
286 | $(go.Shape, { stroke: "brown" }),
|
287 | $(go.Shape, { toArrow: "OpenTriangle", stroke: "brown" })
|
288 | ));
|
289 |
|
290 | diagram.linkTemplateMap.add("Binding",
|
291 | $(go.Link,
|
292 | { curve: go.Link.Bezier },
|
293 | new go.Binding("curviness"),
|
294 | $(go.Shape, { stroke: "green" , strokeWidth: 2, strokeDashArray: [10, 10] }),
|
295 | $(go.Shape, { toArrow: "OpenTriangle", stroke: "green", strokeWidth: 2 })
|
296 | ));
|
297 |
|
298 | diagram.linkTemplateMap.add("Data",
|
299 | $(go.Link,
|
300 | { curve: go.Link.Bezier },
|
301 | new go.Binding("curviness"),
|
302 | $(go.Shape, { stroke: "gray" , strokeWidth: 2 }),
|
303 | $(go.Shape, { toArrow: "Standard", fill: "gray", stroke: "gray", strokeWidth: 2 }),
|
304 | $(go.TextBlock, ".data", { font: "bold 12pt Courier", segmentOffset: new go.Point(0, -10) })
|
305 | ));
|
306 |
|
307 | var model = new go.GraphLinksModel();
|
308 | model.linkFromPortIdProperty = "fPID";
|
309 | model.linkToPortIdProperty = "tPID"
|
310 | model.linkLabelKeysProperty = "labels";
|
311 |
|
312 | model.nodeDataArray = [
|
313 | { key: 1, txt: "Alpha", color: "lightblue", pos: "50 20"},
|
314 | { key: 2, txt: "Beta", color: "lightgreen", pos: "50 270"},
|
315 | { key: 3, category: "dataNode", pos: "300 66", head: "key: 1", txt: "text: Alpha", color: "color: lightblue", loc: "location: 50 0", parent: "parent: null"},
|
316 | { key: 4, category: "dataNode", pos: "300 316", head: "key: 2", txt: "text: Beta", color: "color: lightgreen", loc: "location: 50 250", parent: "parent: 1"},
|
317 | { key: 5, category: "nodeDataArray", pos: "500 125"},
|
318 | { key: 6, category: "title", pos: "320 -100", txt: "TreeModel,\n data"},
|
319 | { key: 7, category: "title", pos: "-50 -100", txt: "Diagram,\nNodes, Links"},
|
320 | { key: -1, category: "Comment", pos: "310 190", txt: "These two\ndata Objects are\nare held in the\nnodeDataArray."},
|
321 | { key: -2, category: "Comment", pos: "100 100", txt: "a Link", bold: true},
|
322 | { key: -21, category: "LinkLabel"},
|
323 | { key: -3, category: "Comment", pos: "0 130", txt: "two Nodes", bold: true},
|
324 | { key: -4, category: "Comment", pos: "190 180", txt: "data binding", bold: true},
|
325 | { key: -41, category: "LinkLabel", segmentOffset: new go.Point(45, 0)},
|
326 | { key: -42, category: "LinkLabel", segmentOffset: new go.Point(20, 0)},
|
327 | { key: -43, category: "LinkLabel", segmentOffset: new go.Point(-10, 0)},
|
328 | { key: -44, category: "LinkLabel", segmentOffset: new go.Point(25, 0)},
|
329 | { key: -45, category: "LinkLabel", segmentOffset: new go.Point(20, 0)},
|
330 | { key: -46, category: "LinkLabel", segmentOffset: new go.Point(-10, 0)}
|
331 | ];
|
332 | model.linkDataArray = [
|
333 | { from: 1, to: 2, labels: [-21] },
|
334 | { from: 1, tPID: "HEADER", to: 3, category: "Data", curviness: 0},
|
335 | { from: 2, tPID: "HEADER", to: 4, category: "Data", curviness: 0},
|
336 | { from: -21, tPID: "HEADER", to: 4, category: "Data", curviness: -50},
|
337 | { from: 5, fPID: "dataNode1", to: 3, tPID: "HEADER", category: "dataNode"},
|
338 | { from: 5, fPID: "dataNode2", to: 4, tPID: "HEADER", category: "dataNode"},
|
339 |
|
340 | { from: -1, to: 3, category: "Comment"},
|
341 | { from: -1, to: 4, category: "Comment"},
|
342 | { from: -2, to: -21, category: "Comment", curviness: 10},
|
343 | { from: -3, to: 1, category: "Comment", curviness: 10},
|
344 | { from: -3, to: 2, category: "Comment", curviness: -10},
|
345 | { from: -4, to: -41, category: "Comment", curviness: 5},
|
346 | { from: -4, to: -42, category: "Comment", curviness: 5},
|
347 | { from: -4, to: -43, category: "Comment", curviness: 5},
|
348 | { from: -4, to: -44, category: "Comment", curviness: 5},
|
349 | { from: -4, to: -45, category: "Comment", curviness: 5},
|
350 | { from: -4, to: -46, category: "Comment", curviness: 5},
|
351 | { from: -4, to: -47, category: "Comment", curviness: 5},
|
352 |
|
353 | { from: 3, fPID: "LOCATION", to: 1, tPID: "NODE", category: "Binding", curviness: 10, labels: [-41]},
|
354 | { from: 3, fPID: "FILL", to: 1, tPID: "SHAPE" , category: "Binding", curviness: 30, labels: [-42]},
|
355 | { from: 3, fPID: "TEXT", to: 1, tPID: "TEXTBLOCK", category: "Binding", curviness: 50, labels: [-43]},
|
356 | { from: 4, fPID: "LOCATION", to: 2, tPID: "NODE", category: "Binding", curviness: 10, labels: [-44]},
|
357 | { from: 4, fPID: "FILL", to: 2, tPID: "SHAPE" , category: "Binding", curviness: 30, labels: [-45]},
|
358 | { from: 4, fPID: "TEXT", to: 2, tPID: "TEXTBLOCK", category: "Binding", curviness: 50, labels: [-46]}
|
359 | ];
|
360 |
|
361 | diagram.model = model;
|
362 |
|
363 | // Formatting
|
364 | function headerStyle() {
|
365 | return {
|
366 | margin: 3,
|
367 | font: "bold 12pt sans-serif",
|
368 | minSize: new go.Size(16, 16),
|
369 | maxSize: new go.Size(120, NaN),
|
370 | textAlign: "center"
|
371 | };
|
372 | }
|
373 | function textStyle() {
|
374 | return {
|
375 | margin: 2,
|
376 | font: "10pt sans-serif",
|
377 | minSize: new go.Size(16, 16),
|
378 | maxSize: new go.Size(120, NaN),
|
379 | textAlign: "center"
|
380 | };
|
381 | }
|
382 | </code></pre>
|
383 | <script>goCode("commented", 650, 550)</script>
|
384 | <p>
|
385 | The two <a>Node</a>s and one <a>Link</a> belong to the <a>Diagram</a> and are on the left side, with shadows.
|
386 | The <a>TreeModel</a> and the two data objects in its <a>Model.nodeDataArray</a> are on the right side, in gray.
|
387 | </p>
|
388 | <p>
|
389 | Each <a>Node</a> and <a>Link</a> has a <a>Panel.data</a> property that references the data object in the model.
|
390 | Thus it is easy, given a Node, to refer to all of the data properties that you have put on the data in the model.
|
391 | These references are drawn as gray links.
|
392 | </p>
|
393 | <p>
|
394 | Each <a>Node</a> also has three <a>Binding</a>s, drawn with dashed green lines:
|
395 | </p>
|
396 | <ul>
|
397 | <li>to the <a>Part.location</a> property from the <code>data.location</code> property</li>
|
398 | <li>to the <a>Shape.fill</a> property from the <code>data.color</code> property</li>
|
399 | <li>to the <a>TextBlock.text</a> property from the <code>data.text</code> property</li>
|
400 | </ul>
|
401 | <p>
|
402 | The use of templates and data binding greatly simplify the information that must be stored in model data,
|
403 | and allow great flexibility in representing nodes and links in various manners independent of the model data.
|
404 | But not all data properties need to be used in Bindings in the template.
|
405 | </p>
|
406 | <p>
|
407 | Note that <a>Binding</a>s are <em>not</em> references from the data to any <a>Part</a>.
|
408 | The whole point of separating models from diagrams is to avoid references from data to Diagrams or Nodes or Links or Tools.
|
409 | The only references from diagram to model are the <a>Diagram.model</a> property and each node or link's <a>Panel.data</a> property.
|
410 | </p>
|
411 |
|
412 | <h2 id="BindingStringAndNumberProperties">Binding string and number properties</h2>
|
413 | <p>
|
414 | It is easy to data bind <a>GraphObject</a> properties to data properties.
|
415 | In this example we not only data bind <a>TextBlock.text</a> and <a>Shape.fill</a> in nodes to property values of node data,
|
416 | but for thicker colored lines we also bind <a>Shape.stroke</a> and <a>Shape.strokeWidth</a> in links to property values of link data.
|
417 | </p>
|
418 | <p>
|
419 | All you need to do is add to the target <a>GraphObject</a> a new <a>Binding</a> that
|
420 | names the target property on the visual object and the source property on the data object.
|
421 | Of course the target property must be a settable property; some GraphObject properties are not settable.
|
422 | If you specify a target property name that does not exist you will get warning messages in the console.
|
423 | If the source property value is undefined, the binding is not evaluated.
|
424 | </p>
|
425 | <pre class="lang-js" id="simpleModelWithBind"><code>
|
426 | diagram.nodeTemplate =
|
427 | $(go.Node, "Auto",
|
428 | $(go.Shape, "RoundedRectangle",
|
429 | { fill: "white" },
|
430 | new go.Binding("fill", "color")), // shape.fill = data.color
|
431 | $(go.TextBlock,
|
432 | { margin: 5 },
|
433 | new go.Binding("text", "key")) // textblock.text = data.key
|
434 | );
|
435 |
|
436 | diagram.linkTemplate =
|
437 | $(go.Link,
|
438 | $(go.Shape,
|
439 | new go.Binding("stroke", "color"), // shape.stroke = data.color
|
440 | new go.Binding("strokeWidth", "thick")), // shape.strokeWidth = data.thick
|
441 | $(go.Shape,
|
442 | { toArrow: "OpenTriangle", fill: null },
|
443 | new go.Binding("stroke", "color"), // shape.stroke = data.color
|
444 | new go.Binding("strokeWidth", "thick")) // shape.strokeWidth = data.thick
|
445 | );
|
446 |
|
447 | var nodeDataArray = [
|
448 | { key: "Alpha", color: "lightblue" },
|
449 | { key: "Beta", color: "pink" }
|
450 | ];
|
451 | var linkDataArray = [
|
452 | { from: "Alpha", to: "Beta", color: "blue", thick: 2 }
|
453 | ];
|
454 | diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
455 | </code></pre>
|
456 | <script>goCode("simpleModelWithBind", 250, 150)</script>
|
457 | <p>
|
458 | Note that there are two bindings using the "color" property of the source link data.
|
459 | There is one for each target <a>Shape</a> in the <a>Link</a> template;
|
460 | each binds the <a>Shape.stroke</a> property.
|
461 | </p>
|
462 |
|
463 | <h2 id="BindingObjectPropertiesSuchAsLocation">Binding object properties such as <b>Part.location</b></h2>
|
464 | <p>
|
465 | You can also data bind properties that have values that are objects.
|
466 | For example it is common to data bind the <a>Part.location</a> property.
|
467 | </p>
|
468 | <p>
|
469 | The value of Part.location is a <a>Point</a>, so in this example the data property must be a Point.
|
470 | </p>
|
471 | <pre class="lang-js" id="bindLocationPoint"><code>
|
472 | diagram.nodeTemplate =
|
473 | $(go.Node, "Auto",
|
474 | new go.Binding("location", "loc"), // get the Node.location from the data.loc value
|
475 | $(go.Shape, "RoundedRectangle",
|
476 | { fill: "white" },
|
477 | new go.Binding("fill", "color")),
|
478 | $(go.TextBlock,
|
479 | { margin: 5 },
|
480 | new go.Binding("text", "key"))
|
481 | );
|
482 |
|
483 | var nodeDataArray = [
|
484 | // for each node specify the location using Point values
|
485 | { key: "Alpha", color: "lightblue", loc: new go.Point(0, 0) },
|
486 | { key: "Beta", color: "pink", loc: new go.Point(100, 50) }
|
487 | ];
|
488 | var linkDataArray = [
|
489 | { from: "Alpha", to: "Beta" }
|
490 | ];
|
491 | diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
492 | </code></pre>
|
493 | <script>goCode("bindLocationPoint", 250, 150)</script>
|
494 | <p>
|
495 | For conciseness the rest of these examples make use of the default <a>Diagram.linkTemplate</a>.
|
496 | </p>
|
497 |
|
498 | <h2 id="ConversionFunctions">Conversion functions</h2>
|
499 | <p>
|
500 | But what if you want the data property value for the location to be something other than a <a>Point</a>?
|
501 | You can provide a conversion function that converts the actual data property value to the needed value type or format.
|
502 | </p>
|
503 | <p>
|
504 | For situations like this example, the <a>Point</a> class includes a static function,
|
505 | <a>Point,parse</a>, that you can use to convert a string into a Point object.
|
506 | It expects two numbers to be in the input string, representing the <a>Point.x</a> and <a>Point.y</a> values.
|
507 | It returns a Point object with those values.
|
508 | </p>
|
509 | <p>
|
510 | You can pass a conversion function as the third argument to the <a>Binding</a> constructor.
|
511 | In this case it is <a>Point,parse</a>.
|
512 | This allows the location to be specified in the form of a string ("100 50") rather than as an expression that returns a <a>Point</a>.
|
513 | For data properties on model objects, you will often want to use strings as the representation of
|
514 | <a>Point</a>s, <a>Size</a>s, <a>Rect</a>s, <a>Margin</a>s, and <a>Spot</a>s, rather than references to objects of those classes.
|
515 | Strings are easily read and written in JSON and XML.
|
516 | Trying to read/write classes of objects would take extra space and would require additional cooperation on the part of both the writer and the reader.
|
517 | </p>
|
518 | <pre class="lang-js" id="bindLocationString"><code>
|
519 | diagram.nodeTemplate =
|
520 | $(go.Node, "Auto",
|
521 | new go.Binding("location", "loc", go.Point.parse), // convert string into a Point value
|
522 | $(go.Shape, "RoundedRectangle",
|
523 | { fill: "white" },
|
524 | new go.Binding("fill", "color")),
|
525 | $(go.TextBlock,
|
526 | { margin: 5 },
|
527 | new go.Binding("text", "key"))
|
528 | );
|
529 |
|
530 | var nodeDataArray = [
|
531 | { key: "Alpha", color: "lightblue", loc: "0 0" }, // note string values for location
|
532 | { key: "Beta", color: "pink", loc: "100 50" }
|
533 | ];
|
534 | var linkDataArray = [
|
535 | { from: "Alpha", to: "Beta" }
|
536 | ];
|
537 | diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
538 | </code></pre>
|
539 | <script>goCode("bindLocationString", 250, 150)</script>
|
540 |
|
541 | <p>
|
542 | Conversion functions can be named or anonymous functions.
|
543 | They take a data property value and return a value suitable for the property that is being set.
|
544 | They should not have any side-effects.
|
545 | They may get called any number of times in any order.
|
546 | </p>
|
547 | <p>
|
548 | Here is an example that has several <a>Shape</a> properties data-bound to the same boolean data property named "highlight".
|
549 | Each conversion function takes the boolean value and returns the appropriate value for the property that is data-bound.
|
550 | This makes it trivial to control the appearance of each node from the data by setting the "highlight" data property to be either false or true.
|
551 | </p>
|
552 | <pre class="lang-js" id="bindHighlight"><code>
|
553 | diagram.nodeTemplate =
|
554 | $(go.Node, "Auto",
|
555 | new go.Binding("location", "loc", go.Point.parse),
|
556 | $(go.Shape, "RoundedRectangle",
|
557 | { // default values if the data.highlight is undefined:
|
558 | fill: "yellow", stroke: "orange", strokeWidth: 2 },
|
559 | new go.Binding("fill", "highlight", function(v) { return v ? "pink" : "lightblue"; }),
|
560 | new go.Binding("stroke", "highlight", function(v) { return v ? "red" : "blue"; }),
|
561 | new go.Binding("strokeWidth", "highlight", function(v) { return v ? 3 : 1; })),
|
562 | $(go.TextBlock,
|
563 | { margin: 5 },
|
564 | new go.Binding("text", "key"))
|
565 | );
|
566 |
|
567 | var nodeDataArray = [
|
568 | { key: "Alpha", loc: "0 0", highlight: false },
|
569 | { key: "Beta", loc: "100 50", highlight: true },
|
570 | { key: "Gamma", loc: "0 100" } // highlight property undefined: use defaults
|
571 | ];
|
572 | var linkDataArray = [
|
573 | { from: "Alpha", to: "Beta" }
|
574 | ];
|
575 | diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
576 | </code></pre>
|
577 | <script>goCode("bindHighlight", 250, 150)</script>
|
578 |
|
579 | <p class="box bg-danger">
|
580 | Note that a conversion function can only return property values.
|
581 | You cannot return GraphObjects to replace objects in the visual tree of the Part.
|
582 | If you need to show different GraphObjects based on bound data,
|
583 | you can bind the <a>GraphObject.visible</a> or the <a>GraphObject.opacity</a> property.
|
584 | If you really want different visual structures
|
585 | you can use multiple templates (<a href="templateMaps.html">Template Maps</a>).
|
586 | </p>
|
587 |
|
588 | <h2 id="ChangingDataValues">Changing data values</h2>
|
589 | <p>
|
590 | The examples above all depend on the data bindings being evaluated when the <a>Part</a>
|
591 | has been created and its <a>Panel.data</a> property is set to refer to the corresponding
|
592 | node or link data.
|
593 | These actions occur automatically when the <a>Diagram</a> creates diagram parts
|
594 | for the data in the model upon setting <a>Diagram.model</a>.
|
595 | </p>
|
596 | <p>
|
597 | However, <b>GoJS</b> cannot know when the data property of an arbitrary JavaScript object has been modified.
|
598 | If you want to change some data object in a model and have the diagram be automatically updated,
|
599 | what you should do depends on the nature of the property that you are changing.
|
600 | </p>
|
601 | <p>
|
602 | For most data properties, ones that the model does not treat specially but are data-bound,
|
603 | you can just call <a>Model.setDataProperty</a>.
|
604 | In this example we modify the value of "highlight" on a node data object.
|
605 | For fun, this modification occurs about twice a second.
|
606 | </p>
|
607 | <pre class="lang-js" id="changeBoundValue"><code>
|
608 | diagram.nodeTemplate =
|
609 | $(go.Node, "Auto",
|
610 | { locationSpot: go.Spot.Center },
|
611 | $(go.Shape, "RoundedRectangle",
|
612 | { // default values if the data.highlight is undefined:
|
613 | fill: "yellow", stroke: "orange", strokeWidth: 2 },
|
614 | new go.Binding("fill", "highlight", function(v) { return v ? "pink" : "lightblue"; }),
|
615 | new go.Binding("stroke", "highlight", function(v) { return v ? "red" : "blue"; }),
|
616 | new go.Binding("strokeWidth", "highlight", function(v) { return v ? 3 : 1; })),
|
617 | $(go.TextBlock,
|
618 | { margin: 5 },
|
619 | new go.Binding("text", "key"))
|
620 | );
|
621 |
|
622 | diagram.model.nodeDataArray = [
|
623 | { key: "Alpha", highlight: false } // just one node, and no links
|
624 | ];
|
625 |
|
626 | function flash() {
|
627 | // all model changes should happen in a transaction
|
628 | diagram.model.commit(function(m) {
|
629 | var data = m.nodeDataArray[0]; // get the first node data
|
630 | m.set(data, "highlight", !data.highlight);
|
631 | }, "flash");
|
632 | }
|
633 | function loop() {
|
634 | setTimeout(function() { flash(); loop(); }, 500);
|
635 | }
|
636 | loop();
|
637 | </code></pre>
|
638 | <script>goCode("changeBoundValue", 250, 150)</script>
|
639 |
|
640 | <h2 id="ChangingGraphStructure">Changing graph structure</h2>
|
641 | <p>
|
642 | Data binding is not used to establish relationships between parts.
|
643 | For data properties that a particular model knows about,
|
644 | such as "to" or "from" for link data in a <a>GraphLinksModel</a>,
|
645 | you must call the appropriate model methods in order to modify the data property.
|
646 | Modifying a data property directly without calling the appropriate model method
|
647 | may cause inconsistencies or undefined behavior.
|
648 | </p>
|
649 | <p>
|
650 | For node data, the model methods are
|
651 | <a>Model.setCategoryForNodeData</a>,
|
652 | <a>Model.setKeyForNodeData</a>,
|
653 | <a>GraphLinksModel.setGroupKeyForNodeData</a>,
|
654 | <a>TreeModel.setParentKeyForNodeData</a>, and
|
655 | <a>TreeModel.setParentLinkCategoryForNodeData</a>.
|
656 | For link data, the model methods are
|
657 | <a>GraphLinksModel.setCategoryForLinkData</a>,
|
658 | <a>GraphLinksModel.setFromKeyForLinkData</a>,
|
659 | <a>GraphLinksModel.setFromPortIdForLinkData</a>,
|
660 | <a>GraphLinksModel.setToKeyForLinkData</a>,
|
661 | <a>GraphLinksModel.setToPortIdForLinkData</a>, and
|
662 | <a>GraphLinksModel.setLabelKeysForLinkData</a>.
|
663 | </p>
|
664 | <p>
|
665 | This example changes the "to" property of a link data, causing the link to
|
666 | connect to a different node.
|
667 | This example uses the default Link template, which does not have any
|
668 | data bindings.
|
669 | The change in the link relationship is accomplished by calling a model method,
|
670 | not via a data binding.
|
671 | </p>
|
672 | <pre class="lang-js" id="changeLinkTo"><code>
|
673 | diagram.nodeTemplate =
|
674 | $(go.Node, "Auto",
|
675 | { locationSpot: go.Spot.Center },
|
676 | $(go.Shape, "RoundedRectangle",
|
677 | { fill: "yellow", stroke: "orange", strokeWidth: 2 }),
|
678 | $(go.TextBlock,
|
679 | { margin: 5 },
|
680 | new go.Binding("text", "key"))
|
681 | );
|
682 |
|
683 | var nodeDataArray = [
|
684 | { key: "Alpha" },
|
685 | { key: "Beta" },
|
686 | { key: "Gamma" }
|
687 | ];
|
688 | var linkDataArray = [
|
689 | { from: "Alpha", to: "Beta" }
|
690 | ];
|
691 | diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
|
692 |
|
693 | function switchTo() {
|
694 | // all model changes should happen in a transaction
|
695 | diagram.model.commit(function(m) {
|
696 | var data = m.linkDataArray[0]; // get the first link data
|
697 | if (m.getToKeyForLinkData(data) === "Beta")
|
698 | m.setToKeyForLinkData(data, "Gamma");
|
699 | else
|
700 | m.setToKeyForLinkData(data, "Beta");
|
701 | }, "reconnect link");
|
702 | }
|
703 | function loop() {
|
704 | setTimeout(function() { switchTo(); loop(); }, 1000);
|
705 | }
|
706 | loop();
|
707 | </code></pre>
|
708 | <script>goCode("changeLinkTo", 250, 150)</script>
|
709 |
|
710 | <h2 id="BindingToGraphObjectSources">Binding to <b>GraphObject</b> sources</h2>
|
711 | <p>
|
712 | The binding source object need not be a plain JavaScript data object held in the diagram's model.
|
713 | The source object may instead be a named <a>GraphObject</a> in the same <a>Part</a>.
|
714 | The source property must be a settable property of the class.
|
715 | The binding is evaluated when the property is set to a new value.
|
716 | </p>
|
717 | <p>
|
718 | One common use of such a binding is to change the appearance of a Part when the <a>Part.isSelected</a>.
|
719 | Call <a>Binding.ofObject</a> to cause the Binding to use the object whose <a>GraphObject.name</a> is the given name.
|
720 | Use the empty string, "", or no argument, to refer to the whole Part itself.
|
721 | This is a convenience so that you do not need to name the whole Part.
|
722 | "ofObject" really means "of the GraphObject named ...", as found by <a>Panel.findObject</a> when there is a string argument.
|
723 | </p>
|
724 | <p>
|
725 | In the example below, the <a>Shape.fill</a> is bound to the <a>Part.isSelected</a> property.
|
726 | When the node is selected or de-selected, the <a>Part.isSelected</a> property changes value, so the binding is evaluated.
|
727 | The conversion function gets a boolean value and returns the desired brush color to be used as the shape's fill.
|
728 | This example also turns off selection adornments, so that the only visual way to tell that a node is selected is by the shape's fill color.
|
729 | </p>
|
730 | <pre class="lang-js" id="bindingElements"><code>
|
731 | diagram.nodeTemplate =
|
732 | $(go.Node, "Auto",
|
733 | { selectionAdorned: false }, // no blue selection handle!
|
734 | $(go.Shape, "RoundedRectangle",
|
735 | // bind Shape.fill to Node.isSelected converted to a color
|
736 | new go.Binding("fill", "isSelected", function(sel) {
|
737 | return sel ? "dodgerblue" : "lightgray";
|
738 | }).ofObject()), // no name means bind to the whole Part
|
739 | $(go.TextBlock,
|
740 | { margin: 5 },
|
741 | new go.Binding("text", "descr"))
|
742 | );
|
743 |
|
744 | diagram.model.nodeDataArray = [
|
745 | { descr: "Select me!" },
|
746 | { descr: "I turn blue when selected." }
|
747 | ];
|
748 | </code></pre>
|
749 | <script>goCode("bindingElements", 450, 100)</script>
|
750 |
|
751 | <p>
|
752 | Caution: do not declare cycles of binding dependencies -- that will result in undefined behavior.
|
753 | <a>GraphObject</a> binding sources also require the <a>Part</a> to be bound to data (i.e. <a>Part.data</a> must be non-null).
|
754 | The property on the GraphObject must be settable, so it does not work on read-only properties
|
755 | such as ones that return computed values (e.g. <a>Part.isTopLevel</a>) or Iterators (e.g. <a>Node.linksConnected</a>).
|
756 | </p>
|
757 |
|
758 | <h2 id="BindingToSharedModelDataSource">Binding to the shared <b>Model.modelData</b> source</h2>
|
759 | <p>
|
760 | The binding source object may be a third kind of source, besides the <a>Panel.data</a> or some <a>GraphObject</a> within the panel.
|
761 | It can also be the JavaScript Object that is the shared <a>Model.modelData</a> object.
|
762 | This permits binding of Node or Link element properties to shared properties in the model
|
763 | that will exist and may be modified even though no nodes or links exist in the model.
|
764 | </p>
|
765 | <p>
|
766 | In the example below, the <a>Shape.fill</a> is bound to the "color" property on the <a>Model.modelData</a> object.
|
767 | As you click the button the <code>changeColor</code> function modifies the <code>modelData</code> object
|
768 | by calling <a>Model.setDataProperty</a>.
|
769 | </p>
|
770 | <pre class="lang-js" id="bindingModel"><code>
|
771 | diagram.nodeTemplate =
|
772 | $(go.Node, "Auto",
|
773 | $(go.Shape, "RoundedRectangle",
|
774 | { fill: "white" }, // the default value if there is no modelData.color property
|
775 | new go.Binding("fill", "color").ofModel()), // meaning a property of Model.modelData
|
776 | $(go.TextBlock,
|
777 | { margin: 5 },
|
778 | new go.Binding("text"))
|
779 | );
|
780 |
|
781 | // start all nodes yellow
|
782 | diagram.model.modelData.color = "yellow";
|
783 |
|
784 | diagram.model.nodeDataArray = [
|
785 | { text: "Alpha" },
|
786 | { text: "Beta" }
|
787 | ];
|
788 |
|
789 | diagram.undoManager.isEnabled = true;
|
790 |
|
791 | changeColor = function() { // define a function named "changeColor" callable by button.onclick
|
792 | diagram.model.commit(function(m) {
|
793 | // alternate between lightblue and lightgreen colors
|
794 | var oldcolor = m.modelData.color;
|
795 | var newcolor = (oldcolor === "lightblue" ? "lightgreen" : "lightblue");
|
796 | m.set(m.modelData, "color", newcolor);
|
797 | }, "changed shared color");
|
798 | }
|
799 | </code></pre>
|
800 | <script>goCode("bindingModel", 450, 100)</script>
|
801 | <button id="changeColorButton" onclick="changeColor()">Change shared color</button>
|
802 |
|
803 | <h2 id="TwoWayDataBinding">Two-way data binding</h2>
|
804 | <p>
|
805 | All of the bindings above only transfer values from the source data to target properties.
|
806 | But sometimes you would like to be able to transfer values from <a>GraphObject</a>s back to the model data,
|
807 | to keep the model data up-to-date with the diagram.
|
808 | This is possible by using a TwoWay <a>Binding</a>, which can pass values not only from source to target,
|
809 | but also from the target object back to the source data.
|
810 | </p>
|
811 | <pre class="lang-js" id="bindTwoWay"><code>
|
812 | diagram.nodeTemplate =
|
813 | $(go.Node, "Auto",
|
814 | { locationSpot: go.Spot.Center },
|
815 | new go.Binding("location", "loc").makeTwoWay(), // TwoWay Binding
|
816 | $(go.Shape, "RoundedRectangle",
|
817 | { fill: "lightblue", stroke: "blue", strokeWidth: 2 }),
|
818 | $(go.TextBlock,
|
819 | { margin: 5 },
|
820 | new go.Binding("text", "key"))
|
821 | );
|
822 |
|
823 | var nodeDataArray = [
|
824 | { key: "Alpha", loc: new go.Point(0, 0) }
|
825 | ];
|
826 | diagram.model = new go.GraphLinksModel(nodeDataArray);
|
827 |
|
828 | window.shiftNode = (function() { // define a function named "shiftNode" callable by button.onclick
|
829 | // all model changes should happen in a transaction
|
830 | diagram.commit(function(d) {
|
831 | var data = d.model.nodeDataArray[0]; // get the first node data
|
832 | var node = d.findNodeForData(data); // find the corresponding Node
|
833 | var p = node.location.copy(); // make a copy of the location, a Point
|
834 | p.x += 10;
|
835 | if (p.x > 200) p.x = 0;
|
836 | // changing the Node.location also changes the data.loc property due to TwoWay binding
|
837 | node.location = p;
|
838 | // show the updated location held by the "loc" property of the node data
|
839 | document.getElementById("bindTwoWayData").textContent = data.loc.toString();
|
840 | }, "shift node");
|
841 | });
|
842 | window.shiftNode();
|
843 |
|
844 | </code></pre>
|
845 | <p>
|
846 | Click on the button to move the <a>Node</a>.
|
847 | The effect is basically what happens when the user drags the node.
|
848 | In this example, the TwoWay <a>Binding</a> on <a>Node.location</a> will update the
|
849 | "loc" property of the node data that is the Node's <a>Part.data</a>.
|
850 | </p>
|
851 | <button onclick="shiftNode()">shiftNode()</button>
|
852 | nodedata.loc: <code id="bindTwoWayData"></code>
|
853 | <script>goCode("bindTwoWay", 250, 150)</script>
|
854 | <p>
|
855 | Just as you can use a conversion function when going from source to target,
|
856 | you can supply a conversion function to <a>Binding.makeTwoWay</a> for going from target to source.
|
857 | For example, to represent the location as a string in the model data instead of as a <a>Point</a>:
|
858 | </p>
|
859 | <pre class="lang-js"><code>
|
860 | // storage representation of Points/Sizes/Rects/Margins/Spots is as strings, not objects:
|
861 | new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify)
|
862 | </code></pre>
|
863 | <p>
|
864 | However, you must not have a TwoWay binding on the node data property that is the "key" property.
|
865 | (That defaults to the name "key" but is actually the value of <a>Model.nodeKeyProperty</a>.)
|
866 | That property value must always be unique among all node data within the model and is known by the Model.
|
867 | A TwoWay binding might change the value, causing a multitude of problems.
|
868 | Similarly, the <a>Node.key</a> property is read-only, to prevent accidental changes of the key value.
|
869 | </p>
|
870 |
|
871 | <h2 id="ReasonsForTwoWayBindings">Reasons for TwoWay Bindings</h2>
|
872 | <p>
|
873 | The basic reason for using a TwoWay <a>Binding</a> on a settable property is to make sure that any changes to that property
|
874 | will be copied to the corresponding model data.
|
875 | By making sure that the <a>Model</a> is up-to-date, you can easily "save the diagram" just by saving the model
|
876 | and "loading a diagram" is just a matter of loading a model into memory and setting <a>Diagram.model</a>.
|
877 | If you are careful to only hold JSON-serializable data in the model data, you can just use the <a>Model.toJson</a>
|
878 | and <a>Model,fromJson</a> methods for converting a model to and from a textual representation.
|
879 | </p>
|
880 | <p>
|
881 | <em>Most bindings do not need to be TwoWay.</em>
|
882 | For performance reasons you should not make a Binding be TwoWay unless you actually need to propagate changes back to the data.
|
883 | Most settable properties are only set on initialization and then never change.
|
884 | </p>
|
885 | <p>
|
886 | Settable properties only change value when some code sets them.
|
887 | That code might be in code that you write as part of your app.
|
888 | Or it might be in a command (see <a href="commands.html">Commands</a>) or a tool (see <a href="tools.html">Tools</a>).
|
889 | Here is a list of properties for which a TwoWay Binding is plausible because one of the predefined commands or tools modify them:
|
890 | </p>
|
891 | <ul>
|
892 | <li><a>Part.location</a>, by <a>DraggingTool</a> if it is enabled</li>
|
893 | <li><a>Link.points</a>, by <a>LinkReshapingTool</a> if it is enabled</li>
|
894 | <li><a>GraphObject.desiredSize</a>, by <a>ResizingTool</a> if it is enabled</li>
|
895 | <li><a>GraphObject.angle</a>, by <a>RotatingTool</a> if it is enabled</li>
|
896 | <li><a>TextBlock.text</a>, by <a>TextEditingTool</a> if it is enabled</li>
|
897 | <li><a>Part.isSelected</a>, by many tools and commands</li>
|
898 | <li><a>Node.isTreeExpanded</a> and <a>Node.wasTreeExpanded</a>, by <a>CommandHandler.collapseTree</a> and <a>CommandHandler.expandTree</a>, called by a "TreeExpanderButton"</li>
|
899 | <li><a>Group.isSubGraphExpanded</a> and <a>Group.wasSubGraphExpanded</a>, by <a>CommandHandler.collapseSubGraph</a> and <a>CommandHandler.expandSubGraph</a>, called by a "SubGraphExpanderButton"</li>
|
900 | </ul>
|
901 | <p>
|
902 | You will not need to use a TwoWay binding on a property if the Tool that modifies it cannot run,
|
903 | or if the command that modifies it cannot be invoked.
|
904 | You probably will not need a TwoWay binding on any other properties unless you write code to modify them.
|
905 | And even then it is sometimes better to write the code to modify the model data directly by calling <a>Model.setDataProperty</a>,
|
906 | depending on a OneWay Binding to update the GraphObject property.
|
907 | </p>
|
908 | <p>
|
909 | It is also possible to use TwoWay Bindings where the source is a GraphObject rather than model data.
|
910 | This is needed less frequently, when you do <em>not</em> want to have the state stored in the model,
|
911 | but you do want to synchronize properties of GraphObjects within the same Part.
|
912 | <em>Use TwoWay Bindings sparingly.</em>
|
913 | </p>
|
914 |
|
915 | </div>
|
916 | </div>
|
917 |
|
918 | <div class="bg-nwoods-primary">
|
919 | <section class="max-w-screen-lg text-white container mx-auto py-2 px-12">
|
920 | <p id="version" class="leading-none mb-2 my-4">GoJS</p>
|
921 | </section>
|
922 | </div><footer class="bg-nwoods-primary text-white">
|
923 | <div class="container max-w-screen-lg mx-auto px-8">
|
924 | <div class="w-full py-6">
|
925 |
|
926 | <div class="max-w-screen-lg xl:max-w-screen-xl mx-auto px-4 sm:px-6 md:px-8">
|
927 | <ul class="text-sm font-medium pb-14 sm:pb-20 grid grid-cols-1 sm:grid-cols-3 gap-y-10">
|
928 | <li class="list-none row-span-2">
|
929 | <h2 class="text-base font-semibold tracking-wide">GoJS</h2>
|
930 | <ul class="list-none space-y-4 md:space-y-1 px-0">
|
931 | <li>
|
932 | <a href="../samples/index.html">Samples</a>
|
933 | </li>
|
934 | <li>
|
935 | <a href="../learn/index.html">Learn</a>
|
936 | </li>
|
937 | <li>
|
938 | <a href="../intro/index.html">Intro</a>
|
939 | </li>
|
940 | <li>
|
941 | <a href="../api/index.html">API</a>
|
942 | </li>
|
943 | <li>
|
944 | <a href="../changelog.html">Changelog</a>
|
945 | </li>
|
946 | <li>
|
947 | <a href="https://github.com/NorthwoodsSoftware/GoJS">GitHub</a>
|
948 | </li>
|
949 | </ul>
|
950 | </li>
|
951 | <li class="list-none row-span-2">
|
952 | <h2 class="text-base font-semibold tracking-wide">Support</h2>
|
953 | <ul class="list-none space-y-4 md:space-y-1 px-0">
|
954 | <li>
|
955 | <a href="https://www.nwoods.com/contact.html"
|
956 | target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a>
|
957 | </li>
|
958 | <li>
|
959 | <a href="https://forum.nwoods.com/c/gojs">Forum</a>
|
960 | </li>
|
961 | <li>
|
962 | <a href="https://www.nwoods.com/app/activate.aspx?sku=gojs">Activate</a>
|
963 | </li>
|
964 | <li>
|
965 | <a href="https://www.nwoods.com/sales/index.html"
|
966 | target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a>
|
967 | </li>
|
968 | <li>
|
969 | <a href="https://www.youtube.com/channel/UC9We8EoX596-6XFjJDtZIDg">Videos</a>
|
970 | </li>
|
971 | </ul>
|
972 | </li>
|
973 | <li class="list-none row-span-2">
|
974 | <h2 class="text-base font-semibold tracking-wide">Company</h2>
|
975 | <ul class="list-none space-y-4 md:space-y-1 px-0">
|
976 | <li>
|
977 | <a href="https://www.nwoods.com">Northwoods</a>
|
978 | </li>
|
979 | <li>
|
980 | <a href="https://www.nwoods.com/about.html">About Us</a>
|
981 | </li>
|
982 | <li>
|
983 | <a href="https://www.nwoods.com/contact.html">Contact Us</a>
|
984 | </li>
|
985 | <li>
|
986 | <a href="https://twitter.com/northwoodsgo">Twitter</a>
|
987 | </li>
|
988 |
|
989 | </ul>
|
990 | </li>
|
991 | </ul>
|
992 |
|
993 |
|
994 | <p class="text-sm text-gray-100 md:mb-6">
|
995 | Copyright 1998-2021 <a class="text-white" href="https://www.nwoods.com">Northwoods Software</a>
|
996 | </p>
|
997 | </div>
|
998 | </div>
|
999 | </footer> </body>
|
1000 |
|
1001 | <script async src="https://www.googletagmanager.com/gtag/js?id=UA-1506307-5"></script>
|
1002 | <script>
|
1003 | window.dataLayer = window.dataLayer || [];
|
1004 | function gtag(){dataLayer.push(arguments);}
|
1005 | gtag('js', new Date()); gtag('config', 'UA-1506307-5');
|
1006 | var getOutboundLink = function(url, label) {
|
1007 | gtag('event', 'click', {
|
1008 | 'event_category': 'outbound',
|
1009 | 'event_label': label,
|
1010 | 'transport_type': 'beacon'
|
1011 | });
|
1012 | }
|
1013 |
|
1014 |
|
1015 | var topButton = document.getElementById("topnavButton");
|
1016 | var topnavList = document.getElementById("topnavList");
|
1017 | topButton.addEventListener("click", function() {
|
1018 | this.classList.toggle("active");
|
1019 | topnavList.classList.toggle("hidden");
|
1020 | document.getElementById("topnavOpen").classList.toggle("hidden");
|
1021 | document.getElementById("topnavClosed").classList.toggle("hidden");
|
1022 | });
|
1023 | </script>
|
1024 | <script src="../assets/js/prism.js"></script>
|
1025 | <script src="../release/go.js"></script>
|
1026 | <script src="../assets/js/goDoc.js"></script>
|
1027 | <script>
|
1028 | document.addEventListener("DOMContentLoaded", function() {
|
1029 | if (window.go) document.getElementById('version').textContent = "GoJS version " + go.version;
|
1030 | if (window.goDoc) window.goDoc();
|
1031 | var d = window.diagrams;
|
1032 | for (var i = 0; i < d.length; i++) {
|
1033 | var dargs = d[i];
|
1034 | goCodeExecute(dargs[0], dargs[1], dargs[2], dargs[3], dargs[4]);
|
1035 | }
|
1036 | if (window.extra) window.extra();
|
1037 | });
|
1038 | </script>
|
1039 | </html>
|