UNPKG

27.4 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 Changed Events -- 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>Changed Events</h1>
137<p>
138There are three basic kinds of events that <b>GoJS</b> generates:
139<a>DiagramEvent</a>s, <a>InputEvent</a>s, and <a>ChangedEvent</a>s.
140This page talks about the latter, which are generated as <a>Diagram</a>s, <a>GraphObject</a>s,
141<a>Model</a>s, or Model data objects are modified.
142See the page <a href="events.html">Events</a> for the former two kinds of events.
143</p>
144
145<p>
146<a>ChangedEvent</a>s in <b>GoJS</b> are notifications of state changes, mostly object property changes.
147The ChangedEvent records the kind of change that occurred and enough information to be able to undo and redo them.
148</p>
149<p>
150Changed events are produced by both <a>Model</a> and <a>Diagram</a>.
151They are multicast events, so you can call <a>Model.addChangedListener</a> and <a>Diagram.addChangedListener</a>,
152as well as the corresponding removeChangedListener methods.
153For convenience you can also specify a Model change listener on a Diagram: <a>Diagram.addModelChangedListener</a>.
154<a>ChangedEvent</a>s received by a Model change listener will have a non-null value for <a>ChangedEvent.model</a>.
155Similarly, ChangedEvents received by a Diagram change listener will have non-null value for <a>ChangedEvent.diagram</a>.
156</p>
157<p>
158A <a>Diagram</a> always registers itself as a listener on its <a>Model</a>, so that it can automatically
159notice changes to the model and update its Parts accordingly.
160Furthermore the <a>UndoManager</a>, if enabled, automatically listens to changes to both the model and the diagram,
161so that it can record the change history and perform undo and redo.
162</p>
163
164<h2 id="ModelAndDataChanges">Model and Data changes</h2>
165
166<h3 id="ModelPropertyChanges">Model property changes</h3>
167<p>
168Model ChangedEvents record state changes either to data in a model or to the <a>Model</a> itself.
169ChangedEvents for models are generated by calls to <a>Model.setDataProperty</a> and by <a>Model</a> property setters.
170</p>
171<p>
172For property changes, that information includes the <a>ChangedEvent.object</a> that was modified,
173the <a>ChangedEvent.propertyName</a>, and the <a>ChangedEvent.oldValue</a> and <a>ChangedEvent.newValue</a> values
174for that property.
175Property changes are identified by the <a>ChangedEvent.change</a> property value being <a>ChangedEvent,Property</a>.
176</p>
177<p>
178Some changes represent structural changes to the model, not just simple model data changes.
179"Structural" changes are the insertion, modification, or removal of relationships that the model is responsible for maintaining.
180In such cases the <a>ChangedEvent.modelChange</a> property will be a non-empty string naming the kind of change.
181The following names for Property <a>ChangedEvent</a>s correspond to structural model data changes:
182</p>
183<ul>
184 <li>"<b>nodeDataArray</b>", when the <a>Model.nodeDataArray</a> Array has been replaced</li>
185 <li>"<b>nodeCategory</b>", due to a call to <a>Model.setCategoryForNodeData</a></li>
186
187 <li>"<b>nodeGroupKey</b>", due to a call to <a>GraphLinksModel.setGroupKeyForNodeData</a></li>
188 <li>"<b>linkDataArray</b>", when the <a>GraphLinksModel.linkDataArray</a> Array has been replaced</li>
189 <li>"<b>linkFromKey</b>", due to a call to <a>GraphLinksModel.setFromKeyForLinkData</a></li>
190 <li>"<b>linkToKey</b>", due to a call to <a>GraphLinksModel.setToKeyForLinkData</a></li>
191 <li>"<b>linkFromPortId</b>", due to a call to <a>GraphLinksModel.setFromPortIdForLinkData</a></li>
192 <li>"<b>linkToPortId</b>", due to a call to <a>GraphLinksModel.setToPortIdForLinkData</a></li>
193 <li>"<b>linkLabelKeys</b>", due to a call to <a>GraphLinksModel.setLabelKeysForLinkData</a></li>
194 <li>"<b>linkCategory</b>", due to a call to <a>GraphLinksModel.setCategoryForLinkData</a></li>
195
196 <li>"<b>nodeParentKey</b>", due to a call to <a>TreeModel.setParentKeyForNodeData</a></li>
197 <li>"<b>parentLinkCategory</b>", due to a call to <a>TreeModel.setParentLinkCategoryForNodeData</a></li>
198</ul>
199<p>
200The value of <a>ChangedEvent.modelChange</a> will be one of these strings.
201The value of <a>ChangedEvent.propertyName</a> depends on the name of the actual data property that was modified.
202For example, for the model property change "linkFromKey", the actual property name defaults to "from".
203But you might be using a different property name by having set <a>GraphLinksModel.linkFromKeyProperty</a> to some other data property name.
204</p>
205<p>
206Any property can be changed on a node data or link data object, by calling <a>Model.setDataProperty</a>.
207Such a call will result in the property name to be recorded as the <a>ChangedEvent.propertyName</a>.
208These cases are treated as normal property changes, not structural model changes,
209so <a>ChangedEvent.modelChange</a> will be the empty string.
210The value of <a>ChangedEvent.object</a> will of course be the JavaScript object that was modified.
211</p>
212<p>
213Some changes may happen temporarily because some code, such as in a Tool, might want to use temporary objects for their own purposes.
214However your change listener might not be interested in such <a>ChangedEvent</a>s.
215If that is the case, you may want to ignore the ChangedEvent if <a>Model.skipsUndoManager</a> (or <a>Diagram.skipsUndoManager</a>) is true.
216</p>
217<p>
218Finally, there are property changes on the model itself.
219For a listing of such properties, see the documentation for <a>Model</a>, <a>GraphLinksModel</a>, and <a>TreeModel</a>.
220These cases are also treated as normal property changes, so <a>ChangedEvent.modelChange</a> will be the empty string.
221Both <a>ChangedEvent.model</a> and <a>ChangedEvent.object</a> will be the model itself.
222</p>
223
224<h3 id="ModelCollectionChanges">Model collection changes</h3>
225<p>
226Other kinds of changed events include <a>ChangedEvent,Insert</a> and <a>ChangedEvent,Remove</a>.
227In addition to all of the previously mentioned ChangedEvent properties used to record a property change,
228the <a>ChangedEvent.oldParam</a> and <a>ChangedEvent.newParam</a> provide the "index" information
229needed to be able to properly undo and redo the change.
230</p>
231<p>
232The following names for Insert and Remove <a>ChangedEvent</a>s correspond to model changes to collections:
233</p>
234<ul>
235 <li>"<b>nodeDataArray</b>", due to a call to <a>Model.addNodeData</a> or <a>Model.removeNodeData</a></li>
236 <li>"<b>linkDataArray</b>", due to a call to <a>GraphLinksModel.addLinkData</a> or <a>GraphLinksModel.removeLinkData</a></li>
237 <li>"<b>linkLabelKeys</b>", due to a call to <a>GraphLinksModel.addLabelKeyForLinkData</a>
238 or <a>GraphLinksModel.removeLabelKeyForLinkData</a></li>
239</ul>
240
241<h3 id="Transactions">Transactions</h3>
242<p>
243The final kind of model changed event is <a>ChangedEvent,Transaction</a>.
244These are not strictly object changes in the normal sense, but they do notify when a transaction starts or finishes,
245or when an undo or redo starts or finishes.
246</p>
247<p>
248The following values of <a>ChangedEvent.propertyName</a> describe the kind of transaction-related event that just occurred:
249</p>
250<ul>
251 <li>"<b>StartingFirstTransaction</b>"</li>
252 <li>"<b>StartedTransaction</b>"</li>
253 <li>"<b>CommittingTransaction</b>"</li>
254 <li>"<b>CommittedTransaction</b>"</li>
255 <li>"<b>RolledBackTransaction</b>"</li>
256 <li>"<b>StartingUndo</b>"</li>
257 <li>"<b>FinishedUndo</b>"</li>
258 <li>"<b>StartingRedo</b>"</li>
259 <li>"<b>FinishedRedo</b>"</li>
260</ul>
261<p>
262In each case the <a>ChangedEvent.object</a> is the <a>Transaction</a> holding a sequence of <a>ChangedEvent</a>s.
263The <a>ChangedEvent.oldValue</a> is the name of the transaction --
264the string passed to <a>UndoManager.startTransaction</a> or <a>UndoManager.commitTransaction</a>.
265The various standard commands and tools that perform transactions document the transaction name(s) that they employ.
266But your code can employ as many transaction names as you like.
267</p>
268<p class="box bg-danger">
269As a general rule, you should not make any changes to the model or any of its data in a listener
270for any Transaction ChangedEvent.
271</p>
272
273<h3 id="SavingModelWhenTransactionsComplete">Saving the Model when Transactions Complete</h3>
274<p>
275It is commonplace to want to update a server database when a transaction has finished.
276Use the <a>ChangedEvent.isTransactionFinished</a> read-only property to detect that case.
277You'll want to implement a Changed listener as follows:
278</p>
279<pre class="lang-js"><code>
280 // notice whenever a transaction or undo/redo has occurred
281 diagram.addModelChangedListener(function(evt) {
282 if (evt.isTransactionFinished) saveModel(evt.model);
283 });
284</code></pre>
285<p>
286The value of <a>Transaction.changes</a> will be a List of <a>ChangedEvent</a>s, in the order that they were recorded.
287Those ChangedEvents represent changes both to the <a>Model</a> and to the <a>Diagram</a> or its <a>GraphObject</a>s.
288Model changes will have <code>e.model !== null</code>; diagram changes will have <code>e.diagram !== null</code>.
289</p>
290
291<h3 id="IncrementallySavingChangesToModel">Incrementally Saving Changes to the Model</h3>
292<p>
293If you do not want to save the whole model at the end of each transaction, but only certain changes to the model,
294you can iterate over the list of changes to pick out the ones that you care about.
295For example, here is a listener that logs a message only when node data is added to or removed from the <a>Model.nodeDataArray</a>.
296</p>
297<pre class="lang-js"><code>
298 diagram.addModelChangedListener(function(evt) {
299 // ignore unimportant Transaction events
300 if (!evt.isTransactionFinished) return;
301 var txn = evt.object; // a Transaction
302 if (txn === null) return;
303 // iterate over all of the actual ChangedEvents of the Transaction
304 txn.changes.each(function(e) {
305 // ignore any kind of change other than adding/removing a node
306 if (e.modelChange !== "nodeDataArray") return;
307 // record node insertions and removals
308 if (e.change === go.ChangedEvent.Insert) {
309 console.log(evt.propertyName + " added node with key: " + e.newValue.key);
310 } else if (e.change === go.ChangedEvent.Remove) {
311 console.log(evt.propertyName + " removed node with key: " + e.oldValue.key);
312 }
313 });
314 });
315</code></pre>
316<p>
317The above listener will put out messages as the user adds nodes (including by copying) and deletes nodes.
318The <a>ChangedEvent.propertyName</a> of the Transaction event (i.e. <i>evt</i> in the code above)
319will be either "CommittedTransaction", "FinishedUndo", or "FinishedRedo".
320Note that a "FinishedUndo" of the removal of a node is really adding the node,
321just as the undo of the insertion of a node actually removes it.
322</p>
323<p>
324Similarly, here is an example of noticing when links are connected, reconnected, or disconnected.
325This not only checks for insertions to and removals from <a>GraphLinksModel.linkDataArray</a>,
326but also changes to the "from" and the "to" properties of the link data.
327</p>
328<pre class="lang-js"><code>
329 diagram.addModelChangedListener(function(evt) {
330 // ignore unimportant Transaction events
331 if (!evt.isTransactionFinished) return;
332 var txn = evt.object; // a Transaction
333 if (txn === null) return;
334 // iterate over all of the actual ChangedEvents of the Transaction
335 txn.changes.each(function(e) {
336 // record node insertions and removals
337 if (e.change === go.ChangedEvent.Property) {
338 if (e.modelChange === "linkFromKey") {
339 console.log(evt.propertyName + " changed From key of link: " +
340 e.object + " from: " + e.oldValue + " to: " + e.newValue);
341 } else if (e.modelChange === "linkToKey") {
342 console.log(evt.propertyName + " changed To key of link: " +
343 e.object + " from: " + e.oldValue + " to: " + e.newValue);
344 }
345 } else if (e.change === go.ChangedEvent.Insert && e.modelChange === "linkDataArray") {
346 console.log(evt.propertyName + " added link: " + e.newValue);
347 } else if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
348 console.log(evt.propertyName + " removed link: " + e.oldValue);
349 }
350 });
351 });
352</code></pre>
353<p>
354Note: the above code only works for a <a>GraphLinksModel</a>, where the link data are separate JavaScript objects.
355</p>
356
357<p>
358Look at the <a href="../samples/UpdateDemo.html">Update Demo</a> for a demonstration of how you can keep
359track of changes to a model when a transaction is committed or when an undo or redo is finished.
360The common pattern is to iterate over the ChangedEvents of the current Transaction
361in order to decide what to record in a database.
362</p>
363
364<p>
365It is also possible to send incremental updates to a database using <a>Model.toIncrementalJson</a> or <a>Model.toIncrementalData</a>,
366which iterate over the changes in a transaction and group them into a JSON-formatted string or an object representing any updates.
367</p>
368<pre class="lang-js"><code>
369 diagram.addModelChangedListener(function(e) {
370 // ignore unimportant Transaction events
371 if (!evt.isTransactionFinished) return;
372 var json = e.model.toIncrementalJson(e);
373 var data = e.model.toIncrementalData(e);
374 ... send to server/database ...
375 });
376</code></pre>
377
378<p>
379Caution: don't call <code>JSON.stringify</code> on the result of <a>Model.toIncrementalData</a>,
380because that will not properly handle any instances of JavaScript classes that are referenced by the object's properties.
381Instead call <a>Model.toIncrementalJson</a>, which will produce a more compact textual serialization.
382If you have to do your own serialization, please ignore the internal hash id property that is placed on objects.
383</p>
384
385<h2 id="DiagramAndGraphObjectChanges">Diagram and GraphObject changes</h2>
386<p>
387Diagram ChangedEvents record state changes to <a>GraphObject</a>s or <a>RowColumnDefinition</a>s in a diagram,
388or to a <a>Layer</a> in a diagram, or to the <a>Diagram</a> itself.
389For such events, <a>ChangedEvent.diagram</a> will be non-null.
390</p>
391<p>
392Most ChangedEvents for diagrams record property changes, such as when some code sets the <a>TextBlock.text</a> property
393or the <a>Part.location</a> property.
394There are a few places which generate ChangedEvents recording insertions into or removals from collections,
395such as <a>Panel.insertAt</a>.
396There are never any ChangedEvents for diagrams that are <a>ChangedEvent,Transaction</a>.
397</p>
398<p>
399Although ChangedEvents for diagrams are important for undo/redo in order to retain visual fidelity,
400one normally ignores them when saving models. Only ChangedEvents for models record state changes to model data.
401So for saving to a database, you will want to consider only those ChangedEvents for which <a>ChangedEvent.model</a> is non-null.
402</p>
403
404 </div>
405 </div>
406
407 <div class="bg-nwoods-primary">
408 <section class="max-w-screen-lg text-white container mx-auto py-2 px-12">
409 <p id="version" class="leading-none mb-2 my-4">GoJS</p>
410 </section>
411 </div><footer class="bg-nwoods-primary text-white">
412 <div class="container max-w-screen-lg mx-auto px-8">
413 <div class="w-full py-6">
414
415 <div class="max-w-screen-lg xl:max-w-screen-xl mx-auto px-4 sm:px-6 md:px-8">
416 <ul class="text-sm font-medium pb-14 sm:pb-20 grid grid-cols-1 sm:grid-cols-3 gap-y-10">
417 <li class="list-none row-span-2">
418 <h2 class="text-base font-semibold tracking-wide">GoJS</h2>
419 <ul class="list-none space-y-4 md:space-y-1 px-0">
420 <li>
421 <a href="../samples/index.html">Samples</a>
422 </li>
423 <li>
424 <a href="../learn/index.html">Learn</a>
425 </li>
426 <li>
427 <a href="../intro/index.html">Intro</a>
428 </li>
429 <li>
430 <a href="../api/index.html">API</a>
431 </li>
432 <li>
433 <a href="../changelog.html">Changelog</a>
434 </li>
435 <li>
436 <a href="https://github.com/NorthwoodsSoftware/GoJS">GitHub</a>
437 </li>
438 </ul>
439 </li>
440 <li class="list-none row-span-2">
441 <h2 class="text-base font-semibold tracking-wide">Support</h2>
442 <ul class="list-none space-y-4 md:space-y-1 px-0">
443 <li>
444 <a href="https://www.nwoods.com/contact.html"
445 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a>
446 </li>
447 <li>
448 <a href="https://forum.nwoods.com/c/gojs">Forum</a>
449 </li>
450 <li>
451 <a href="https://www.nwoods.com/app/activate.aspx?sku=gojs">Activate</a>
452 </li>
453 <li>
454 <a href="https://www.nwoods.com/sales/index.html"
455 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a>
456 </li>
457 <li>
458 <a href="https://www.youtube.com/channel/UC9We8EoX596-6XFjJDtZIDg">Videos</a>
459 </li>
460 </ul>
461 </li>
462 <li class="list-none row-span-2">
463 <h2 class="text-base font-semibold tracking-wide">Company</h2>
464 <ul class="list-none space-y-4 md:space-y-1 px-0">
465 <li>
466 <a href="https://www.nwoods.com">Northwoods</a>
467 </li>
468 <li>
469 <a href="https://www.nwoods.com/about.html">About Us</a>
470 </li>
471 <li>
472 <a href="https://www.nwoods.com/contact.html">Contact Us</a>
473 </li>
474 <li>
475 <a href="https://twitter.com/northwoodsgo">Twitter</a>
476 </li>
477
478 </ul>
479 </li>
480 </ul>
481
482
483 <p class="text-sm text-gray-100 md:mb-6">
484 Copyright 1998-2021 <a class="text-white" href="https://www.nwoods.com">Northwoods Software</a>
485 </p>
486 </div>
487 </div>
488</footer> </body>
489
490<script async src="https://www.googletagmanager.com/gtag/js?id=UA-1506307-5"></script>
491<script>
492 window.dataLayer = window.dataLayer || [];
493 function gtag(){dataLayer.push(arguments);}
494 gtag('js', new Date()); gtag('config', 'UA-1506307-5');
495 var getOutboundLink = function(url, label) {
496 gtag('event', 'click', {
497 'event_category': 'outbound',
498 'event_label': label,
499 'transport_type': 'beacon'
500 });
501 }
502
503 // topnav
504 var topButton = document.getElementById("topnavButton");
505 var topnavList = document.getElementById("topnavList");
506 topButton.addEventListener("click", function() {
507 this.classList.toggle("active");
508 topnavList.classList.toggle("hidden");
509 document.getElementById("topnavOpen").classList.toggle("hidden");
510 document.getElementById("topnavClosed").classList.toggle("hidden");
511 });
512</script>
513 <script src="../assets/js/prism.js"></script>
514 <script src="../release/go.js"></script>
515 <script src="../assets/js/goDoc.js"></script>
516 <script>
517 document.addEventListener("DOMContentLoaded", function() {
518 if (window.go) document.getElementById('version').textContent = "GoJS version " + go.version;
519 if (window.goDoc) window.goDoc();
520 var d = window.diagrams;
521 for (var i = 0; i < d.length; i++) {
522 var dargs = d[i];
523 goCodeExecute(dargs[0], dargs[1], dargs[2], dargs[3], dargs[4]);
524 }
525 if (window.extra) window.extra();
526 });
527 </script>
528</html>