Framework / DOM / DOM Basics / Nodes
In This Topic
    Nodes
    In This Topic
     Nodes Overview

    All objects in the DOM directly or indirectly derive from the NNode abstract class. In their core DOM hierarchies are tree-like structures the items of which are NNode instances (nodes). The NNode class adds the core support for nodes aggregation and extended metadata information.

    The extended metadata information is created in each specific node type static constructor and is later accessible via the node Schema property (see Schemas for more info).

    Nodes provide a feature rich API that let you traverse the children, descendants and ancestors of a specific node.

    Nodes are also highly observable and raise events for all property changes, children changes and others (see Events for more info).

     The Node and its Schema

    There is a single instance of the NSchema class associated with each specific type of a NNode derivate. The node schema is used to extend the metadata information available for each type of node. The schema for a specific node instance can be obtained by the Schema property of each node instance. The schema of of the node must be created in the node static constructor and the node must expose a static reference to the created schema as shown in the following sample:

    My First Node
    Copy Code
    public class MyNode : NNode
    {
     // default constructor
     public MyNode()
     {
     }
     // static constructor
     static MyNode()
     {
      MyNodeSchema = NSchema.Create(typeof(MyNode), NNode.NNodeSchema);
     }
     public static readonly NSchema MyNodeSchema;
    }
    
    The schema instance returned by the NSchema.Create method, must be stored in a public static readonly field of the node type. The field name should follow the NodeClassName + "Schema" naming convention as shown in the example. All types of nodes should have a default constructor.

    The schema associated with each node instance is accessible from the Schema property of each node. See Schemas for more information.

     Node Properties

    The properties that a node has must first be declared in the node schema. Each node property is represented by an instance of the NProperty class, which is internally created by the schema to which the property belongs. You create node properties by calling the AddMember and AddSlot methods of the node schema. The returned property instances need to stored in public static readonly fields of the node class for later access. The class itself needs to implement CLR properties that expose the node properties. The following sample code extends the My First Node sample with two properties - the first one is backed by a member, the second by a slot.

    My First Node With Properties
    Copy Code
    public class MyNode : NNode
    {
     // default constructor
     public MyNode()
     {
     }
     // static constructor
     static MyNode()
     {
      MyNodeSchema = NSchema.Create(typeof(MyNode), NNode.NNodeSchema);
      // declare properties
      MyInt32Property = MyNodeSchema.AddMember("MyInt32", NDomType.Int32, 0,
       delegate(NNode node) { return ((MyNode)node).m_MyInt32; },       // property getter
       delegate(NNode node, object value) { ((MyNode)node).m_MyInt32 = (int)value; });  // property setter
      MyStringProperty = MyNodeSchema.AddSlot("MyString", NDomType.String, "");
     }
     // clr property exposing MyInt32 property
     public int MyInt32
     {
      get  {   return (int)GetValue(MyInt32Property);   }
      set  {   SetValue(MyInt32Property, value);  }
     }
     // clr property exposing MyString property
     public string MyString
     {
      get  {   return (string)GetValue(MyStringProperty);  }
      set  {   SetValue(MyStringProperty, value);  }
     }
     // fields
     private int m_MyInt32;
     // schema references
     public static readonly NSchema MyNodeSchema;
     public static readonly NProperty MyInt32Property;
     public static readonly NProperty MyStringProperty;
    }
    

     See Properties for more information.

     Nodes Aggregation

    The most essential part of making a hierarchy of nodes is the way in which you can place nodes inside other nodes - i.e. aggregate the nodes inside each other. In the DOM a node can aggregate another node in two ways - as a property node or as a child node. Because of that, node hierarchies are different from classical tree hierarchies, in which a parent node can only have child nodes. The following figure illustrates a hierarchy of NNode instances:

    figure 1. Node Aggregation

    Property node aggregation occurs when you assign a node as a value to a property of NNode type. The node itself does not consider the assigned node as child node, however the assigned node considers the node to be its parent. The current set of of non-null property nodes, assigned to a node can be obtained by the GetPropertyNodes() method. In figure 1 for example, nodes Root.PropertyA and Root.PropertyB are nodes that reside in the property dimension of the Root node. Their parent node is the Root node, however they are not children of Root node. See Properties for more information.

    Nodes can contain child nodes in one of the following two ways:

    • Container - a container node can only hold a limited number of child nodes of specific types. Each child node is also accessible by an unique name inside its parent node. In figure 1 for example the Root node declares two child slots - Child A and Child B. The node assigned to the Child A slot is the Root.ChildA node, while the node assigned to the Child B slot is the Root.ChildB node.
    • Collection - a collection node can hold an arbitrary number of child nodes, that share a common base node type - i.e. is a collection of nodes from a certain type. In figure 1 for example the node assigned to Child A slot of the Root node is a collection node. It can have an unlimited number of child nodes, that are accessible by their index.

    In order for a node to use one of the above mentioned child containment modes, the respective node schema must be declared as a container or collection one, by a call to the MakeContainer() or MakeCollection() NSchema methods. If none of this methods is called during the schema initialization, which happens in the static node constructor, the node type is considered to be a Leaf (cannot possibly contain any children). For any node instance the GetChildren() returns a list that contains the current child nodes of the node. See next section for more information about node children.

    Collectively the set of property nodes and child nodes form the set of the aggregated nodes. For any node instance the GetAggregatedNodes() method returns the union of its property nodes and children nodes sets.

    The GetAggregationInfo() method returns information about the way in which a node is aggregated inside its parent node.

     Child Nodes

    Only nodes, whose schemas are declared with the Container or Collection children aggregation method can have child nodes. The following code example creates one container and one collection node:

    My First Collection and Container Nodes
    Copy Code
    public class MyCollectionNode : NNode, INCollection<MyContainerNode>
    {
     public MyCollectionNode()
     {
     }
     static MyCollectionNode()
     {
      // create the schema and declare the node type as a collection of MyContainerNode instances
      MyCollectionNodeSchema = NSchema.Create(typeof(MyCollectionNode), NNode.NNodeSchema);
      MyCollectionNodeSchema.MakeColleciton(MyContainerNode.MyContainerNodeSchema);
     }
     // INCollection<MyContainerNode> Implementation
     public int Count { get { return GetChildrenCount(); } }
     public bool Contains(MyContainerNode item) { return IsChild(item); }
     public int IndexOf(MyContainerNode item) { return IndexOfChild(item); }
     public void Insert(int index, MyContainerNode item) { InsertChild(index, item); }
     public void RemoveAt(int index) { RemoveChildAt(index); }
     public void Add(MyContainerNode item) { AddChild(item); }
     public void Remove(MyContainerNode item) { RemoveChild(item); }
     public void Clear() { RemoveAllChildren(); }
     public void CopyTo(MyContainerNode[] array, int arrayIndex) { NIteratorHelpers<MyContainerNode>.CopyTo(GetIterator(), array, arrayIndex); }
     public INIterator<MyContainerNode> GetIterator() { return new NUnsafeCastIterator<NNode, MyContainerNode>(GetChildrenIterator()); }
     public INIterator<MyContainerNode> GetReverseIterator() { return new NUnsafeCastIterator<NNode, MyContainerNode>(GetReverseChildrenIterator()); }
     public MyContainerNode this[int index]
     {
      get { return (MyContainerNode)GetChildAt(index); }
      set { SetChildAt(index, value); }
     }
     public static readonly NSchema MyCollectionNodeSchema;
    }
    public class MyContainerNode : NNode
    {
     public MyContainerNode()
     {
     }
     static MyContainerNode()
     {
      // create the schema and declare the node type as a container
      MyContainerNodeSchema = NSchema.Create(typeof(MyContainerNode), NNode.NNodeSchema);
      MyContainerNodeSchema.MakeContainer();
      // declare the possible children it can have
      Child1Child = MyContainerNodeSchema.AddChild("Child1", typeof(MyCollectionNode));
      Child2Child = MyContainerNodeSchema.AddChild("Child2", typeof(NNode));
     }
     public MyCollectionNode Child1
     {
      get { return (MyCollectionNode)GetChild(Child1Child); }
      set { SetChild(Child1Child, value); }
     }
     public NNode Child2
     {
      get { return GetChild(Child2Child); }
      set { SetChild(Child2Child, value); }
     }
     public static readonly NSchema MyContainerNodeSchema;
     public static readonly NChild Child1Child;
     public static readonly NChild Child2Child;
    }
    

    The MyCollectionNode class is defined as a collection of MyContainerNode instances in its static constructor. By doing that you instruct NOV to automatically check all added nodes to the instances of MyCollectionNode to be either MyContainerNode instances, or instances of MyContainerNode derivates. The MyCollectionNode also implements the INCollection<MyContainerNode> interface to provide a strongly typed interface to its children. The latter is not mandatory, but is a good practice, since it makes the code that works with MyCollectionNode less error prone and more readable.

    The MyContainerNode class is defined as a container that can have only two child nodes. Child1 needs to be an instance of MyCollectionNode class (or a derived one) while Child2 may be any node. MyContainerNode also implements two CLR properties that expose access to the two child nodes.

    NNode implements a large set of methods that help you query and modify the node children directly. Most of the children query methods work regardless of whether the actual node is declared as a container, collection or leaf. The modification methods however will throw an exception, if the node towards which you execute the method does not use the respective aggregation children method. The following table summarizes the most important children query and modification methods.

    Method Signature                                                                    Description Leafs Containers Collections

    Children Query Methods

    int GetChildrenCount()
    int GetChildrenCount(INFilter<NNode> filter)

    Gets the current count of child nodes. Has overloads for filtering. x x x

    NList<NNode> GetChildren()
    NList<NNode> GetChildren(INFilter<NNode> filter)

    Gets a new list that contains the child nodes of the node. Has overloads for filtering. x x x

    NNode GetFirstChild()
    NNode GetFirstChild(INFilter<NNode> filter)
    NNode GetFirstChild(NSchema schema)

    Gets the first child of the node, if any - otherwise returns null. Has overloads for filtering. x x x

    NNode GetLastChild()
    NNode GetLastChild(INFilter<NNode> filter)
    NNode GetLastChild(NSchema schema)

    Gets the last child of the node, if any - otherwise returns null. Has overloads for filtering. x x x

    NNode GetChildAt(int index)

    Gets the child at the specified index. Throws an exception if index is out of range. x x x

    int IndexOfChild(NNode node)

    Gets the index of the specified child node in the node children container. Returns -1 if the node is not a child of this node. x x

    bool IsChild(NNode node)

    Determines whether the specified node is a child node of this node. x x x

    NNode GetChild(NChild child)
    NNode GetChild(NChild child, bool createOnDemand)

    Gets the child at the specified child slot. Returns null if the child slot is not yet set to a node instance. If createOnDemand is true, creates a new child node and automatically sets it to the child slot. x

    INIterator<NNode> GetChildrenIterator()

    Gets an iterator that iterates the node children in forward order. x x x

    INIterator<NNode> GetReverseChildrenIterator()

    Gets an iterator that iterates the node children in reverse order. x x x

    Children Modification Methods

    void AddChild(NNode node)

    Adds (appends) a child node x

    void InsertChild(int index, NNode node)

    Inserts a child node at the specified index. x

    void RemoveChild(NNode node)

    Removes the specified node from the node, if the specified node is a child node. x x

    void RemoveChildAt(int index)

    Removes the child node at the specified index. x x

    void RemoveAllChildren()

    Removes all children. x x

    void SetChild(NChild child, NNode node)

    Sets the child node at the specified child slot. x

    void SetChildAt(int index, NNode node)

    Sets(substitutes) the child at the specified index x

    There are two ways in which you can observe children changes:

    • Override OnChildInserted, OnChildRemoved or OnChildrenChanged. - this is the recommended way to monitor for children changes when you author nodes that always need to know when their children changed. The following example demonstrate the technique:

      Override OnChildInserted, OnChildRemoved or OnChildrenChanged
      Copy Code
      public class MyNodeThatKnowsWhenChildrenChanged : NNode
      {
       ...
       protected override void OnChildInserted(NInsertChildData data)
       {
        base.OnChildInserted(data);
        Console.WriteLine("Child inserted");
       }
       protected override void  OnChildRemoved(NRemoveChildData data)
       {
        base.OnChildRemoved(data);
        Console.WriteLine("Child removed");
       }
       
       protected override void  OnChildrenChanged(NChildChangeData data)
       {
        base.OnChildrenChanged(data);
        Console.WriteLine("Child inserted or removed");
       }
       ...
      }
      
    • Subscribe for Children Changed Events - this is the way to observe any node for children changes, without it actually knowing about it. The following example demonstrate the technique:
      Example Title
      Copy Code
      void TestChildrenEvents()
      {
       MyCollectionNode myColNode = new MyCollectionNode();
       myColNode.ChildInserted += new Function<NInsertChildEventArgs>(OnChildInserted);
       myColNode.ChildRemoved += new Function<NRemoveChildEventArgs>(OnChildRemoved);
       myColNode.ChildrenChanged += new Function<NEventArgs>(OnChildrenChanged);
       myColNode.Add(new MyContainerNode());
       myColNode.RemoveAt(0);
       // Output is:
       // Child inserted
       // Child Inserted or Removed
       // Child Removed
       // Child Inserted or Removed
      }
      void OnChildrenChanged(NEventArgs arg1)
      {
       Console.WriteLine("Child Inserted or Removed");
      }
      void OnChildRemoved(NRemoveChildEventArgs arg1)
      {
       Console.WriteLine("Child Removed");
      }
      void OnChildInserted(NInsertChildEventArgs arg1)
      {
       Console.WriteLine("Child inserted");
      }
      
     Parent and Ancestor Nodes

    The parent node is obtained from the ParentNode property. As with node children you can observe the parent change (i.e. when the node itself is added or removed from the children set of another node), by either overriding the OnParentChanged method or by subscribing to the ParentChanged event.

    NNode implements methods that help you query the ancestors chain of a specific node. Following is a brief summary of the most important ones.

    Method Signature Description

    NNode GetFirstCommonAncestor(NNode node)

    Gets the first ancestor, which is an ancestor of the node and the specified node. Returns null if the two nodes do not have a common ancestor.

    NNode GetFirstAncestor(INFilter<NNode> filter)
    NNode GetFirstAncestor(NSchema schema)

    Gets the first node ancestor, which satisfies the specified filter or is of the specified schema.

    NNode GetLastAncestor(INFilter<NNode> filter)
    NNode GetLastAncestor(NSchema schema)

    Gets the last node ancestor, which satisfies the specified filter or is of the specified schema.

    NList<NNode> GetPathFromAncestor(NNode ancestor, bool includeNode, bool includeAncestor)

    Returns the path from the node to the specified ancestor. The returned path has a root-to-leaf order, meaning that ancestor nodes appear before descendant nodes in the list.
     Descendant Nodes
    NNode implements methods that help you query and traverse the descendant nodes that reside in the subtree rooted by a concrete node. Following is a brief summary of the most important ones:
    Method Signature Description

    INIterator<NNode> GetSubtreeIterator()
    INIterator<NNode> GetSubtreeIterator(ENTreeTraversalOrder treeTraversalOrder)
    INIterator<NNode> GetSubtreeIterator(ENTreeTraversalOrder treeTraversalOrder, INFilter<NNode> filter)

    Gets an iterator, which iterates through the node and its descendants in a specific tree traversal order and with optional filtering ability. The children of the currently visited node are visited in forward order (from first to last).

    INIterator<NNode> GetReverseSubtreeIterator()
    INIterator<NNode> GetReverseSubtreeIterator(ENTreeTraversalOrder treeTraversalOrder)
    INIterator<NNode> GetReverseSubtreeIterator(ENTreeTraversalOrder treeTraversalOrder, INFilter<NNode> filter)

    Gets an iterator, which iterates through the node and its descendants in a specific tree traversal order and with optional filtering ability. The children of the currently visited node are visited in reverse order (from last to first).

    NList<NNode> GetDescendants()
    NList<NNode> GetDescendants(INFilter<NNode> filter)

    Gets a list, which contains the descendants of this node. Has overload with filtering ability.

    void AccumulateDescendants(NList<NNode> list)
    void AccumulateDescendants(NList<NNode> list, INFilter<NNode> filter)

    Accumulates the descendants of this node, to the provided node list. Has overload with filtering ability.
    See Also