Chapter 9. Architectural Concepts

Table of Contents

Look-up Mechanism
Benefits
Look-up Chaining
Customizing Look-up Behavior
Binding Data to Graph Elements
Mapping Data to Graph Elements
User Tags
Client-Server Communication and User Tags

This chapter introduces some concepts used with the yFiles FLEX types. These include purpose-specific, i.e., graph model-related topics as well as practical programming aspects.

Look-up Mechanism

Interface ILookup lays the foundation for a lean yet powerful look-up mechanism that is used with a number of yFiles FLEX types. This look-up mechanism provides the means that allow for context-aware dynamic behavior on the instance level. It is heavily used for the inner workings of the yFiles FLEX framework. Among the types that implement ILookup are the central graph structure types, i.e., in particular IGraph, and also the model item types INode, IEdge, IPort, etc.

Benefits

Compared to classic object-oriented concepts, the look-up idiom enables a dynamism that would be hard (or at least tedious) to achieve using either subclassing or interface implementations. In particular, it can be used to easily add new "aspects" to the model items in a graph without having to subclass and implement yet another set of interfaces.

Also, the behavior of model items can be modified at runtime by simply changing their look-up to return either customized logic for some requested type, or the default, or nothing at all. Example 9.1, “Dynamically changing the behavior of model items” presents this use-case building on the general support for look-up customization described below.

The same technique can also be observed in the section called “Customizing User Interaction Behavior”, which discusses customization of user interaction behavior by decorating the look-up of model items. More importantly, this section also lists the type requests that are supported by model item types in the context of user gesture handling.

Example 9.1. Dynamically changing the behavior of model items

function enableCustomLookupBehavior( graph:IGraph, enable:Boolean, 
           customLookupChainLink:IContextLookupChainLink ):void {
  // Get the graph's ILookupDecorator that enables manipulation of all look-up 
  // operations for the contained graph elements.
  var decorator:ILookupDecorator = 
    graph.lookup( ILookupDecorator ) as ILookupDecorator;
  if( null != decorator ) {
    // Test whether decoration of look-up operations initiated on type INode is 
    // supported.
    if( decorator.canDecorate( INode ) ) {
      if( enable ) {
        // Prepend a custom look-up provider.
        decorator.addLookup( INode, customLookupChainLink );
      } else {
        // Remove the custom look-up provide.
        decorator.removeLookup( INode, customLookupChainLink );
      }
    }
  }
}

API Excerpt 9.1, “Look-up method from interface ILookup” shows the single method defined by ILookup. The class that is given as the parameter is used by the object that is queried as the key in a look-up operation. The result of this operation is either an instance of the given class or null.

API Excerpt 9.1. Look-up method from interface ILookup

// Provides the means to query the current object (this) for an instance of the 
// given class.
lookup(type:Class):Object

Classes that implement the ILookup interface typically support only a set of specific classes with their look-up operations. In particular those classes are supported that provide well-defined aspects of the object that is queried. For example, when a node is resized or moved, its corresponding INode implementation is queried via the lookup method to return an IReshapeable instance, which can then be used to change either or both of the node's location and bounds.

Interface IContextLookup defines a similar method (see API Excerpt 9.2, “Look-up method from interface IContextLookup”) which can be used to provide identical functionality for classes that do not implement ILookup.

API Excerpt 9.2. Look-up method from interface IContextLookup

// Provides the means to query 'item' for an instance of the given class.
lookupForItem(item:Object, type:Class):Object

The default implementations of all graph model items, including the default IGraph implementation, also implement the IMutableLookup interface. Thus, the implementations returned by the lookup method of model items can be changed by clients through registerLookup():

API Excerpt 9.3. Registering look-up implementations using interface IMutableLookup

// Provides the means to register an instance of the given type.
function registerLookup(type:Class, instance:Object):void;

Example 9.2. Equivalent ways of querying the look-up

Look-up Chaining

Look-up chaining describes the technique of combining an arbitrary number of look-up providers in a daisy-chain manner: implementations of interface IContextLookupChainLink are linked together via their setNext() method.

Whenever such a look-up chain is queried for a specific type and a look-up provider would fail to return a proper instance, instead of returning null, it delegates to its successor in the look-up chain. Thus, the successor's look-up facility is queried for the given type, and so on until either a look-up provider is able to satisfy the request or the last element in the look-up chain returns null.

Figure 9.1, “Hierarchy of look-up types used with look-up chaining” shows the hierarchy of types that are central for look-up chaining, including, in particular abstract class AbstractContextLookupChainLink.

Figure 9.1. Hierarchy of look-up types used with look-up chaining

Hierarchy of look-up types used with look-up chaining.

AbstractContextLookupChainLink offers a default implementation for the lookup() method that simply delegates any calls to the next look-up provider in the chain. Descending types that provide their own implementation in order to achieve customized look-up behavior can still use this default implementation as a fallback. For example, a typical override could look like that shown in Example 9.3, “Using the Lookup implementation of AbstractContextLookupChainLink as a fallback”.

Example 9.3. Using the Lookup implementation of AbstractContextLookupChainLink as a fallback

override public function lookupForItem(item:Object, type:Class):Object {
  // Only handle requests for ISelectionPaintables and 
  // ensure that the queried item is an INode
  if( item is INode && type == ISelectionPaintable ) {
    return new CustomNodeSelectionPaintable( INode( item ).layout );
  }
  // If the requested type was not handled here, use the super type 
  // as a fallback lookup provider.
  return super.lookupForItem( item, type );
}

Customizing Look-up Behavior

Look-up chaining paves the way for convenient manipulation of look-up operations, respectively their return values. By simply prepending a look-up provider to an existing look-up chain, default behavior can be easily customized: a prepended chain link element is able to handle specific types while letting pass those that it is not interested in. For the type requests it handles, it can return modified instances that provide custom logic.

Interface ILookupDecorator defines the functionality that enables manipulating look-up chains related to given types. API Excerpt 9.4, “Methods from interface ILookupDecorator” lists the methods of ILookupDecorator that can be used to modify a type's look-up chain.

API Excerpt 9.4. Methods from interface ILookupDecorator

// Determine whether the given type's look-up chain can be decorated.
canDecorate( type:Class ):Boolean 

// Add/remove a chain link to/from the given type's look-up chain.
addLookup( type:Class, lookup:IContextLookupChainLink ):void 
removeLookup( type:Class, lookup:IContextLookupChainLink ):void 

The utility class LookupDecorator enables convenient decoration of look-up chains on top of a given ILookupDecorator. Example 9.4, “Instantiating LookupDecorator using a given ILookupDecorator” presents how to create a LookupDecorator instance for the nodes in a graph.

Example 9.4. Instantiating LookupDecorator using a given ILookupDecorator

// 'graph' is of type com.yworks.graph.model.IGraph.

var decorator:ILookupDecorator = graph.lookup(ILookupDecorator) as ILookupDecorator;
var handleDecorator:LookupDecorator =
    new LookupDecorator(INode, IHandleProvider, decorator, true, false);

API Excerpt 9.5, “LookupDecorator methods for convenient decoration of a look-up chain” shows the methods from class LookupDecorator that enable instant modifications to the look-up chain of the decorated type.

API Excerpt 9.5. LookupDecorator methods for convenient decoration of a look-up chain

// Always returning 'null' when the type is requested.
function hideImplementation( predicate:Function=null ):IContextLookupChainLink

// Always returning 'instance' when the type is requested.
function setInstance( instance:Object, predicate:Function=null ):IContextLookupChainLink

// The chain link returns a factory when the type is requested.
function setFactory( factory:Function,
                    predicate:Function=null ):IContextLookupChainLink

// The chain link returns a factory that wraps the underlying
// implementation when the type is requested.
function setImplementationWrapper(
                wrapperFactory:Function,
                predicate:Function=null ):IContextLookupChainLink

Most prominently, class DefaultGraph, which presents the default graph structure implementation, uses look-up chaining and supports the ILookupDecorator type with its Lookup method. The actual ILookupDecorator instance that is returned upon a respective invocation, enables decorating all look-up requests for types INode, IEdge, IPort, ILabel, and IBend. In the context of the queried DefaultGraph object, this means that all its contained nodes, edges, ports, labels (both node and edge labels), and bends are available for decoration, i.e., any of their behavior can be easily modified.

Example 9.5, “Look-up chain modification using class GraphDecorator” shows an excerpt from the LookupDecoratorDemo application where the GraphDecorator class is used to conveniently decorate the look-up chain of nodes.

Example 9.5. Look-up chain modification using class GraphDecorator

// 'graph' is of type com.yworks.graph.model.IGraph.

// Get the graph decorator instance from the graph.
var graphDecorator:GraphDecorator =
                    graph.lookup(GraphDecorator) as GraphDecorator;

// Add decorations dynamically to the nodes that will be created.
var nodeDecorator:NodeDecorator = graphDecorator.nodeDecorator;

// Wrap the existing position handler into a custom implementation.
nodeDecorator.positionHandlerDecorator.setImplementationWrapper(
  function(node:INode, wrapped:IPositionHandler):Object {
    return new RectangleShadowPositionHandler(wrapped, node.layout);
  });

// Use a factory method to create a custom highlight paintable installer
// for a given node.
nodeDecorator.highlightDecorator.setFactory(
  function(node:INode):IHighlightPaintableInstaller {
    return new AnimatedNodeHighlightInstaller(node.layout);
  });

// Let the given size constraints provider instance be returned
// for all nodes.
nodeDecorator.sizeConstraintsProviderDecorator.setInstance(
            new SizeConstraintProvider(new ImmutableSize(15,15),
                                        new ImmutableSize(50,50),
                                        null));

The LookupDecoratorDemo application shows how to use the various item specific decorators available by the GraphDecorator to change different aspects of visual appearance and user interaction. Also the GridDemo uses decorators to customize the positioning of nodes and bends. The CustomStyle demo application shows how the selection paintable of nodes can be customized by adding a lookup chain link using the services of ILookupDecorator.