As a transitional step, this site will temporarily be made Read-Only from July 8th until the new community launch. During this time, you can still search and read articles and discussions.

While the community is read-only, if you have questions or issues requiring TIBCO review/response, please access the new TIBCO Community and select "Ask A Question."

You will need to register or log in or register to engage in the new community.

Using Undoable Nodes with the TIBCO Spotfire® Document Model Framework API

Last updated:
12:52am May 10, 2017

Overview

This page describes how to use undoable nodes with the TIBCO Spotfire® Document Model Framework API.

Document nodes must not be modified. Instead they may contain Undoable Nodes, a special, modifiable kind of nodes.

The Document Model Framework provides classes for primitive undoable properties as well as undoable data structures. The values that are assigned to an undoable property or undoable data structure must be immutable. The Document Model Framework ensures that all fields of the document node classes used as values are marked as readonly. If a class is immutable but for some reason does not pass the tests, it can be explicitly marked with the Immutable attribute .

If an undoable node refers to a document node then that node is considered to be owned by the undoable property. A node may only have one unique owner. An UndoableCrossReferenceProperty may refer to a node without owning it.

Primitive Undoable Properties

Undoable Properties 

The Undoable Property is the most basic kind of undoable node. It is a field where the Document Model Framework can record any change of the field.

The framework provides a single class for undoable properties:

  • UndoableProperty<T>: Manages a property that owns its value. Assignments to the property will be recorded as undoable commands.
Implementation Pattern

The following example defines a HeaderText property which holds a string.

  1. Define a Property Name for the property.
  2. Define a private field of type UndoableProperty<string>.
  3. Define a property with a
  4. get accessor that reads the Value property from the undoable property
  5. set accessor that sets the Value property.
  6. In the constructor of the class, create the property using the CreateProperty method.
  7. In the GetObjectData method, serialize the property using the SerializeProperty method.
  8. In the deserialization constructor, deserialize the property using the DeserializeProperty method.
[Serializable]
public sealed class Example : DocumentNode
{
    public new sealed class PropertyNames : DocumentNode.PropertyNames
    {
        public static PropertyName HeaderText = CreatePropertyName("HeaderText");
    }
 
    private readonly UndoableProperty<string> headerText;
 
    public string HeaderText
    {
        get { return headerText.Value; }
        set { headerText.Value = value; }
    }
 
    public Example(string text)
    {
        CreateProperty<string>(PropertyNames.HeaderText, out this.headerText, text);
    }
 
    protected override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        SerializeProperty(info, context, this.headerText);
    }
 
    internal Example(SerializationInfo info, StreamingContext context) : base(info, context)
    {
        DeserializeProperty(
           info,
           context,
           PropertyNames.HeaderText,
           out this.headerText);
    }
}

 

Undoable Cross Reference Properties 

Undoable Cross Reference Properties are fields that contain references to other document nodes. They enable the Document Model Framework to observe changes to the monitored field.

Undoable cross reference properties are similar to undoable properties. The difference is that the node referenced by an undoable cross reference property is not considered to be owned by that property. If a node that is referred to by an undoable cross referenced property is removed from the document, the value of the undoable cross reference property becomes null. A cross reference property never keeps a dangling reference to a node that is no longer a part of the document.

Cross reference properties are useful when you want to act on a node without owning it. Consider a bookmark that refers to a page: Applying the bookmark makes that page active, but the page is still not owned by the bookmark. If the bookmark is removed, the page remains a node in the page collection at the top level of the document.

UndoableCrossReferenceProperties commonly reference the data DataTable or DataColumns a visualization, a tool or a panel will use.

The framework provides a single class for undoable cross reference properties:

Implementation Pattern

The following example defines a ReferenceNode property which holds a reference to a document node of type SomeNode. It is built in a steps:

  1. Define a Property Name for the property.
  2. Define a private field of type UndoableCrossReferenceProperty<SomeNode>.
  3. Define a property with a
  4. get accessor that reads the Value property from the undoable property
  5. set accessor that sets the Value property.
  6. In the constructor of the class, create the property using the CreateProperty method.
  7. In the GetObjectData method, serialize the property using the SerializeProperty method.
  8. In the deserialization constructor, deserialize the property using the DeserializeProperty method.
[Serializable]
public sealed class NodeWithReference : DocumentNode
{
    public new sealed class PropertyNames : DocumentNode.PropertyNames
    {
        public static PropertyName ReferencedNode = CreatePropertyName("ReferencedNode");
    }
 
    private readonly UndoableCrossReferenceProperty<SomeNode> referencedNode;
 
    /// <summary/>
    public NodeWithReference(SomeNode someNode)
    {
        CreateProperty<SomeNode>(
           PropertyNames.ReferencedNode,
           out this.referencedNode,
           someNode);
    }
 
    /// <summary>The referenced node.</summary>
    public SomeNode ReferencedNode
    {
        get { return this.referencedNode.Value; }
        set { this.referencedNode.Value = value; }
    }
 
    protected override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
 
        SerializeProperty(info, context, this.referencedNode);
    }
 
    internal NodeWithReference(SerializationInfo info, StreamingContext context) : base(info, context)
    {
        DeserializeProperty(
           info,
           context,
           PropertyNames.ReferencedNode,
           out this.referencedNode);
    } 
}

 

Undoable Data Structures

Undoable Lists 

Undoable Lists correspond to Undoable Properties, but contain a list of values instead of a single value.

The framework provides two classes to choose from. It is often convenient to derive from DocumentNodeList<T> to include the standard set of operations:

  • DocumentNodeListBase<T>: Abstract base class for list-like document nodes. It does not expose any public methods to modify the collection, it is up to derived classes to do so.
     
  • DocumentNodeList<T>: Abstract base class for list-like document nodes. It exposes standard IList<T> methods to modify the collection.

Undoable lists are implemented using balanced binary search trees. The cost of indexing the list is logarithmic to the size of the list, not constant as is usually the case for lists implemented by dynamically resizing arrays. The cost to add or remove an element in the list, regardless of where in the list the element is added or removed, is also logarithmic. Dynamically resizing arrays has a linear time cost.

If you repeatedly index into a list it may be a good idea, for efficiency, to first copy the list into an ordinary list, which provides constant time indexing.

Implementation Pattern

The following example defines a class that implements a list of strings. To keep the class simple, it only has a method to add an element at the end of the list. A real class will usually define a series of operations. The MyCollection class is built in a steps:

  1. Define a Property Name for the property.
  2. Define a private field of type UndoableList<string>.
  3. Create the list using the CreateProperty method.
  4. In the GetObjectData method, serialize the property using the SerializeProperty method.
  5. In the deserialization constructor, deserialize the property using the DeserializeProperty method.
  6. Define public method performing operations, in this case one that adds to the collection.
[Serializable]
public sealed class MyCollection : DocumentNode
{
    public new sealed class PropertyNames : DocumentNode.PropertyNames
    {
        public static PropertyName Items = CreatePropertyName("Items");
    }
 
    private readonly UndoableList<string> items;
 
    /// <summary/>
    public MyCollection()
    {
        CreateProperty<string>(PropertyNames.Items, out this.items);
    }
 
    protected override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
 
        SerializeProperty(info, context, this.items);
    }
 
    internal MyCollection(SerializationInfo info, StreamingContext context) : base(info, context)
    {
        DeserializeProperty(
           info,
           context,
           PropertyNames.Items,
           out this.items);
    }
 
    public void Add(string item)
    {
        this.items.Add(item);
    }
 
    // Other methods for operating on lists that you wish to expose follow here.
}

 

Undoable Sets 

Undoable Sets are analogous to Undoable Lists and Undoable Dictionaries, but implement sets.

The framework provides one class for undoable sets:

  • UndoableSet<T>: A Set of Document Nodes where all operations on the set are undoable.

Undoable sets are implemented using a balanced binary search tree where the hash codes of the entries are used to order the elements. Adding, removing or checking for containment has a logarithmic time cost.

Implementation Pattern

Refer to Undoable Lists.

Undoable Dictionaries 

Undoable Dictionaries are analogous to Undoable Lists and Undoable Sets, but implement dictionaries.

The framework provides one class for undoable dictionaries:

Implementation Pattern

Refer to Undoable Lists.

Undoable Keyed Collections 

Undoable Keyed Collections combine features from lists and a dictionarys. The elements are primarily ordered as a list, but may also be indexed by a property of the elements.

The framework provides one class for undoable sets:

The undoable keyed collection data structure is implemented by two balanced binary search trees. The first is ordered by list position, the other by specified property. Indexing, adding, removing, looking up or checking for containment takes logarithmic time.

When the property of a node that is stored in an undoable keyed collection is changed the second search tree structure is automatically updated so that the element can be found quickly. This takes logarithmic time.

Data table columns can profit from being stored in an undoable keyed collection: First each column is assigned a position in the list based on the order of import from the data source. Then a column can be located based on a property, like its name.

Implementation Pattern

Refer to Undoable Lists.