260 lines
13 KiB
HTML
260 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>GoJS Changed Events -- Northwoods Software</title>
|
|
<!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
|
|
<script src="../release/go.js"></script>
|
|
<script src="goIntro.js"></script>
|
|
</head>
|
|
<body onload="goIntro()">
|
|
<div id="container" class="container-fluid">
|
|
<div id="content">
|
|
|
|
<h1>Changed Events</h1>
|
|
<p>
|
|
There are three basic kinds of events that <b>GoJS</b> generates:
|
|
<a>DiagramEvent</a>s, <a>InputEvent</a>s, and <a>ChangedEvent</a>s.
|
|
This page talks about the latter, which are generated as <a>Diagram</a>s, <a>GraphObject</a>s,
|
|
<a>Model</a>s, or Model data objects are modified.
|
|
See the page <a href="events.html">Events</a> for the former two kinds of events.
|
|
</p>
|
|
|
|
<p>
|
|
<a>ChangedEvent</a>s in <b>GoJS</b> are notifications of state changes, mostly object property changes.
|
|
The ChangedEvent records the kind of change that occurred and enough information to be able to undo and redo them.
|
|
</p>
|
|
<p>
|
|
Changed events are produced by both <a>Model</a> and <a>Diagram</a>.
|
|
They are multicast events, so you can call <a>Model.addChangedListener</a> and <a>Diagram.addChangedListener</a>,
|
|
as well as the corresponding removeChangedListener methods.
|
|
<a>ChangedEvent</a>s received by a Model change listener will have a non-null value for <a>ChangedEvent.model</a>.
|
|
Similarly, ChangedEvents received by a Diagram change listener will have non-null value for <a>ChangedEvent.diagram</a>.
|
|
</p>
|
|
<p>
|
|
A <a>Diagram</a> always registers itself as a listener on its <a>Model</a>, so that it can automatically
|
|
notice changes to the model and update its Parts accordingly.
|
|
Furthermore the <a>UndoManager</a>, if enabled, automatically listens to changes to both the model and the diagram,
|
|
so that it can record the change history and perform undo and redo.
|
|
</p>
|
|
|
|
<h2 id="ModelAndDataChanges">Model and Data changes</h2>
|
|
|
|
<h3 id="ModelPropertyChanges">Model property changes</h3>
|
|
<p>
|
|
Model ChangedEvents record state changes either to data in a model or to the <a>Model</a> itself.
|
|
ChangedEvents for models are generated by calls to <a>Model.setDataProperty</a> and by <a>Model</a> property setters.
|
|
</p>
|
|
<p>
|
|
For property changes, that information includes the <a>ChangedEvent.object</a> that was modified,
|
|
the <a>ChangedEvent.propertyName</a>, and the <a>ChangedEvent.oldValue</a> and <a>ChangedEvent.newValue</a> values
|
|
for that property.
|
|
Property changes are identified by the <a>ChangedEvent.change</a> property value being <a>ChangedEvent.Property</a>.
|
|
</p>
|
|
<p>
|
|
Some changes represent structural changes to the model, not just simple model data changes.
|
|
"Structural" changes are the insertion, modification, or removal of relationships that the model is responsible for maintaining.
|
|
In such cases the <a>ChangedEvent.modelChange</a> property will be a non-empty string naming the kind of change.
|
|
The following names for Property <a>ChangedEvent</a>s correspond to structural model data changes:
|
|
</p>
|
|
<ul>
|
|
<li>"<b>nodeDataArray</b>", when the <a>Model.nodeDataArray</a> Array has been replaced</li>
|
|
<li>"<b>nodeCategory</b>", due to a call to <a>Model.setCategoryForNodeData</a></li>
|
|
|
|
<li>"<b>nodeGroupKey</b>", due to a call to <a>GraphLinksModel.setGroupKeyForNodeData</a></li>
|
|
<li>"<b>linkDataArray</b>", when the <a>GraphLinksModel.linkDataArray</a> Array has been replaced</li>
|
|
<li>"<b>linkFromKey</b>", due to a call to <a>GraphLinksModel.setFromKeyForLinkData</a></li>
|
|
<li>"<b>linkToKey</b>", due to a call to <a>GraphLinksModel.setToKeyForLinkData</a></li>
|
|
<li>"<b>linkFromPortId</b>", due to a call to <a>GraphLinksModel.setFromPortIdForLinkData</a></li>
|
|
<li>"<b>linkToPortId</b>", due to a call to <a>GraphLinksModel.setToPortIdForLinkData</a></li>
|
|
<li>"<b>linkLabelKeys</b>", due to a call to <a>GraphLinksModel.setLabelKeysForLinkData</a></li>
|
|
<li>"<b>linkCategory</b>", due to a call to <a>GraphLinksModel.setCategoryForLinkData</a></li>
|
|
|
|
<li>"<b>nodeParentKey</b>", due to a call to <a>TreeModel.setParentKeyForNodeData</a></li>
|
|
<li>"<b>parentLinkCategory</b>", due to a call to <a>TreeModel.setParentLinkCategoryForNodeData</a></li>
|
|
</ul>
|
|
<p>
|
|
The value of <a>ChangedEvent.modelChange</a> will be one of these strings.
|
|
The value of <a>ChangedEvent.propertyName</a> depends on the name of the actual data property that was modified.
|
|
For example, for the model property change "linkFromKey", the actual property name defaults to "from".
|
|
But you might be using a different property name by having set <a>GraphLinksModel.linkFromKeyProperty</a> to some other data property name.
|
|
</p>
|
|
<p>
|
|
Any property can be changed on a node data or link data object, by calling <a>Model.setDataProperty</a>.
|
|
Such a call will result in the property name to be recorded as the <a>ChangedEvent.propertyName</a>.
|
|
These cases are treated as normal property changes, not structural model changes,
|
|
so <a>ChangedEvent.modelChange</a> will be the empty string.
|
|
The value of <a>ChangedEvent.object</a> will of course be the JavaScript object that was modified.
|
|
</p>
|
|
<p>
|
|
Finally, there are property changes on the model itself.
|
|
For a listing of such properties, see the documentation for <a>Model</a>, <a>GraphLinksModel</a>, and <a>TreeModel</a>.
|
|
These cases are also treated as normal property changes, so <a>ChangedEvent.modelChange</a> will be the empty string.
|
|
Both <a>ChangedEvent.model</a> and <a>ChangedEvent.object</a> will be the model itself.
|
|
</p>
|
|
|
|
<h3 id="ModelCollectionChanges">Model collection changes</h3>
|
|
<p>
|
|
Other kinds of changed events include <a>ChangedEvent.Insert</a> and <a>ChangedEvent.Remove</a>.
|
|
In addition to all of the previously mentioned ChangedEvent properties used to record a property change,
|
|
the <a>ChangedEvent.oldParam</a> and <a>ChangedEvent.newParam</a> provide the "index" information
|
|
needed to be able to properly undo and redo the change.
|
|
</p>
|
|
<p>
|
|
The following names for Insert and Remove <a>ChangedEvent</a>s correspond to model changes to collections:
|
|
</p>
|
|
<ul>
|
|
<li>"<b>nodeDataArray</b>", due to a call to <a>Model.addNodeData</a> or <a>Model.removeNodeData</a></li>
|
|
<li>"<b>linkDataArray</b>", due to a call to <a>GraphLinksModel.addLinkData</a> or <a>GraphLinksModel.removeLinkData</a></li>
|
|
<li>"<b>linkLabelKeys</b>", due to a call to <a>GraphLinksModel.addLabelKeyForLinkData</a>
|
|
or <a>GraphLinksModel.removeLabelKeyForLinkData</a></li>
|
|
</ul>
|
|
|
|
<h3 id="Transactions">Transactions</h3>
|
|
<p>
|
|
The final kind of model changed event is <a>ChangedEvent.Transaction</a>.
|
|
These are not strictly object changes in the normal sense, but they do notify when a transaction starts or finishes,
|
|
or when an undo or redo starts or finishes.
|
|
</p>
|
|
<p>
|
|
The following values of <a>ChangedEvent.propertyName</a> describe the kind of transaction-related event that just occurred:
|
|
</p>
|
|
<ul>
|
|
<li>"<b>StartingFirstTransaction</b>"</li>
|
|
<li>"<b>StartedTransaction</b>"</li>
|
|
<li>"<b>CommittingTransaction</b>"</li>
|
|
<li>"<b>CommittedTransaction</b>"</li>
|
|
<li>"<b>RolledBackTransaction</b>"</li>
|
|
<li>"<b>StartingUndo</b>"</li>
|
|
<li>"<b>FinishedUndo</b>"</li>
|
|
<li>"<b>StartingRedo</b>"</li>
|
|
<li>"<b>FinishedRedo</b>"</li>
|
|
</ul>
|
|
<p>
|
|
In each case the <a>ChangedEvent.object</a> is the <a>Transaction</a> holding a sequence of <a>ChangedEvent</a>s.
|
|
The <a>ChangedEvent.oldValue</a> is the name of the transaction --
|
|
the string passed to <a>UndoManager.startTransaction</a> or <a>UndoManager.commitTransaction</a>.
|
|
The various standard commands and tools that perform transactions document the transaction name(s) that they employ.
|
|
But your code can employ as many transaction names as you like.
|
|
</p>
|
|
<p class="box bg-danger">
|
|
As a general rule, you should not make any changes to the model or any of its data in a listener
|
|
for any Transaction ChangedEvent.
|
|
</p>
|
|
|
|
<h3 id="SavingModelWhenTransactionsComplete">Saving the Model when Transactions Complete</h3>
|
|
<p>
|
|
It is commonplace to want to update a server database when a transaction has finished.
|
|
Use the <a>ChangedEvent.isTransactionFinished</a> read-only property to detect that case.
|
|
You'll want to implement a Changed listener as follows:
|
|
</p>
|
|
<pre>
|
|
// notice whenever a transaction or undo/redo has occurred
|
|
diagram.addModelChangedListener(function(evt) {
|
|
if (evt.isTransactionFinished) saveModel(evt.model);
|
|
});
|
|
</pre>
|
|
<p>
|
|
The value of <a>Transaction.changes</a> will be a List of <a>ChangedEvent</a>s, in the order that they were recorded.
|
|
Those ChangedEvents represent changes both to the <a>Model</a> and to the <a>Diagram</a> or its <a>GraphObject</a>s.
|
|
Model changes will have <code>e.model !== null</code>; diagram changes will have <code>e.diagram !== null</code>.
|
|
</p>
|
|
|
|
<h3 id="IncrementallySavingChangesToModel">Incrementally Saving Changes to the Model</h3>
|
|
<p>
|
|
If you do not want to save the whole model at the end of each transaction, but only certain changes to the model,
|
|
you can iterate over the list of changes to pick out the ones that you care about.
|
|
For example, here is a listener that logs a message only when node data is added to or removed from the <a>Model.nodeDataArray</a>.
|
|
</p>
|
|
<pre>
|
|
diagram.addModelChangedListener(function(evt) {
|
|
// ignore unimportant Transaction events
|
|
if (!evt.isTransactionFinished) return;
|
|
var txn = evt.object; // a Transaction
|
|
if (txn === null) return;
|
|
// iterate over all of the actual ChangedEvents of the Transaction
|
|
txn.changes.each(function(e) {
|
|
// ignore any kind of change other than adding/removing a node
|
|
if (e.modelChange !== "nodeDataArray") return;
|
|
// record node insertions and removals
|
|
if (e.change === go.ChangedEvent.Insert) {
|
|
console.log(evt.propertyName + " added node with key: " + e.newValue.key);
|
|
} else if (e.change === go.ChangedEvent.Remove) {
|
|
console.log(evt.propertyName + " removed node with key: " + e.oldValue.key);
|
|
}
|
|
});
|
|
});
|
|
</pre>
|
|
<p>
|
|
The above listener will put out messages as the user adds nodes (including by copying) and deletes nodes.
|
|
The <a>ChangedEvent.propertyName</a> of the Transaction event (i.e. <i>evt</i> in the code above)
|
|
will be either "CommittedTransaction", "FinishedUndo", or "FinishedRedo".
|
|
Note that a "FinishedUndo" of the removal of a node is really adding the node,
|
|
just as the undo of the insertion of a node actually removes it.
|
|
</p>
|
|
<p>
|
|
Similarly, here is an example of noticing when links are connected, reconnected, or disconnected.
|
|
This not only checks for insertions to and removals from <a>GraphLinksModel.linkDataArray</a>,
|
|
but also changes to the "from" and the "to" properties of the link data.
|
|
</p>
|
|
<pre>
|
|
diagram.addModelChangedListener(function(evt) {
|
|
// ignore unimportant Transaction events
|
|
if (!evt.isTransactionFinished) return;
|
|
var txn = evt.object; // a Transaction
|
|
if (txn === null) return;
|
|
// iterate over all of the actual ChangedEvents of the Transaction
|
|
txn.changes.each(function(e) {
|
|
// record node insertions and removals
|
|
if (e.change === go.ChangedEvent.Property) {
|
|
if (e.modelChange === "linkFromKey") {
|
|
console.log(evt.propertyName + " changed From key of link: " +
|
|
e.object + " from: " + e.oldValue + " to: " + e.newValue);
|
|
} else if (e.modelChange === "linkToKey") {
|
|
console.log(evt.propertyName + " changed To key of link: " +
|
|
e.object + " from: " + e.oldValue + " to: " + e.newValue);
|
|
}
|
|
} else if (e.change === go.ChangedEvent.Insert && e.modelChange === "linkDataArray") {
|
|
console.log(evt.propertyName + " added link: " + e.newValue);
|
|
} else if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
|
|
console.log(evt.propertyName + " removed link: " + e.oldValue);
|
|
}
|
|
});
|
|
});
|
|
</pre>
|
|
<p>
|
|
Note: the above code only works for a <a>GraphLinksModel</a>, where the link data are separate JavaScript objects.
|
|
</p>
|
|
|
|
<p>
|
|
Look at the <a href="../samples/UpdateDemo.html">Update Demo</a> for a demonstration of how you can keep
|
|
track of changes to a model when a transaction is committed or when an undo or redo is finished.
|
|
The common pattern is to iterate over the ChangedEvents of the current Transaction
|
|
in order to decide what to record in a database.
|
|
</p>
|
|
|
|
<h2 id="DiagramAndGraphObjectChanges">Diagram and GraphObject changes</h2>
|
|
<p>
|
|
Diagram ChangedEvents record state changes to <a>GraphObject</a>s or <a>RowColumnDefinition</a>s in a diagram,
|
|
or to a <a>Layer</a> in a diagram, or to the <a>Diagram</a> itself.
|
|
For such events, <a>ChangedEvent.diagram</a> will be non-null.
|
|
</p>
|
|
<p>
|
|
Most ChangedEvents for diagrams record property changes, such as when some code sets the <a>TextBlock.text</a> property
|
|
or the <a>Part.location</a> property.
|
|
There are a few places which generate ChangedEvents recording insertions into or removals from collections,
|
|
such as <a>Panel.insertAt</a>.
|
|
There are never any ChangedEvents for diagrams that are <a>ChangedEvent.Transaction</a>.
|
|
</p>
|
|
<p>
|
|
Although ChangedEvents for diagrams are important for undo/redo in order to retain visual fidelity,
|
|
one normally ignores them when saving models. Only ChangedEvents for models record state changes to model data.
|
|
So for saving to a database, you will want to consider only those ChangedEvents for which <a>ChangedEvent.model</a> is non-null.
|
|
</p>
|
|
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|