documentationfor yFiles for HTML 2.6

Service Locator Pattern: Lookup

yFiles for HTML implements the service locator pattern in many of its core interfaces and elements. The main advantage of this pattern in the context of yFiles for HTML is that it shifts the responsibility of providing the logic to deal with an element from the operating part to the element itself. Main benefits of using this pattern are:

  • Flexibility: Logic to deal with something can be better changed; not one central piece that deals with everything but the central piece asks the items to deal with for how to deal with them.
  • Scalability: Adding new functionality does not necessarily need changes to existing code.
  • Simplification: Each part is smaller, simpler and therefore easier to comprehend and maintain.

But there are also disadvantages to this pattern: For one, it is not intuitive which objects an item returns in its lookup. Luckily, it is rare that you really need to resort to lookup for obtaining objects. There are almost always other means to get the information you need. The service locator pattern in yFiles for HTML is primarily meant to be an anchor for injecting your own implementations while keeping the interfaces short and avoiding the need to derive classes. On the other hand, it is hard to guess what the framework expects from an object to return in different situations just by looking at the available API. Using the service locator mechanism requires careful study of the available documentation, both in the API documentation and the Developer’s Guide.

This chapter covers an advanced topic. For most use-cases, the mechanism described in chapter Decorating Graph Elements can be used. It is recommended to read this chapter first, if you are not already familiar with the topic.

One example where developers need to retrieve an object via lookup is when retrieving information from an IInputModeContext as shown below in the example Example for a lookup.

One example where the documentation describes the use of the service locator pattern for a kind of interaction is section Customizing Resizing Nodes in this document, which lists the interfaces that are queried via lookup during resize operations.

Using Lookup

The main interface that represents the service locator pattern in yFiles for HTML is ILookup. GraphComponent, Graph, INode, IEdge etc. all implement this interface and thereby provide a number of objects that are mainly used by the framework to retrieve the information required to deal with the element in question.

The ILookup interface defines just a single method: lookup(type: Class): Object. This method is called to query the ILookup implementor for an instance of the given type. The ILookup implementor then returns this instance or null if there is no such instance.

A quite common use case where a developer has to query an ILookup implementor is to query an IInputModeContext’s lookup in a user interaction related method for input mode related types. Example for a lookup shows a very common example: The call asks the IInputModeContext for an implementation of the SnapContext class. The method returns an object of the requested type, or null if not available.

Example for a lookup
const snapContext = inputModeContext.lookup(SnapContext.$class)

The Lookup Chain

All lookups of classes that implement the ILookup interface can be customized via decoration.

The primary lookup decoration mechanism relies on the usage of the IContextLookupChainLink interface, which is closely related to ILookup, but extends the concept by two aspects:

  1. Adds lookup capabilities to any item
  2. Adds the ability to chain lookups, which means that if the first lookup does not satisfy the request, then the next lookup in the chain will be called, until the end of the chain is reached.

The decoration of a lookup is then a simple matter of adding a chain link to the start of the lookup chain. This can be done using the ILookupDecorator interface, which you can obtain from all items that implement ILookup in yFiles for HTML via lookup.

The most important method defined on this interface is: addLookup(t: Class, lookup: IContextLookupChainLink)

This method takes an IContextLookupChainLink and adds it to the existing lookup chain of the given class. When the lookup mechanism of an object of this class is used to query something, the last added IContextLookupChainLink is called first.

IContextLookupChainLink implementations can be created using factory methods defined on the interface, there is no need to implement the interface by yourself.

createContextLookupChainLink(callback: LookupCallback): IContextLookupChainLink
Uses an IContextLookup as the logic for the lookup call, which additionally provides the item instance on which the lookup call happened as context in the contextLookup method.
addingLookupChainLink(lookup: ILookup): IContextLookupChainLink
Queries the given ILookup for an instance of the queried type. Proceeds in the chain if the lookup returns null.
addingLookupChainLink(instance: Object): IContextLookupChainLink
addingLookupChainLink(type: Class, instance: Object): IContextLookupChainLink
Provides a single element for the lookup call.
factoryLookupChainLink<TContext,TResult>(contextType: Class<TContext>, resultType: Class<TResult>, factory: Factory<TContext, TResult>): IContextLookupChainLink
Answers the lookup call by using the provided factory to create an instance of the queried type.
hidingLookupChainLink(type: Class): IContextLookupChainLink
Returns null for the given type and therefore ends the lookup chain at this point to hide whatever may have been returned in the remaining lookup chain.

For most common interfaces that can be decorated for graph elements, the more convenient LookupDecorator<TDecoratedType,TInterface> in combination with the GraphDecorator should be used. This is explained in detail in chapter Decorating Graph Elements.

Examples for Lookup Decoration

To give a clearer picture, we will give some examples for usages of these lookups in the customization process. For a start, we will begin with an INodeSizeConstraintProvider for which an INode’s lookup is queried during interactive node resizing.

Let’s assume that we want to create a custom INodeSizeConstraintProvider and place it into the lookup of all nodes. In the first example A regular IContextLookupChainLink that uses an IContextLookup, we use the general approach that uses an IContextLookup as argument.

The code example above uses the lookup decoration approach for demonstration purposes. The same result could be achieved using the more straight-forward sizeConstraintProviderDecorator.

The first step is to obtain the ILookupDecorator from the graph as mentioned in section The Lookup Chain. Once we have the decorator, we can use the addLookup(t: Class, lookup: IContextLookupChainLink) method to add a chain link to the lookup chain. The type we want to decorate is INode, so the first argument is the class object of INode. The second argument is our custom IContextLookupChainLink using an IContextLookup.

The contextLookup(item: Object, type: Class) method of the IContextLookup interface has an additional item object parameter which acts as the context of the lookup. In this example, we want to restrict node resizing to half the node’s original size at minimum and twice its original size as maximum. For this use case we need the current node (which is the item parameter) to retrieve its original size. (Note: since we registered the decorator for INodes we can safely assume that item is of type INode).

The second parameter is the type that is being queried in the lookup. We want to return our new constraint provider when a INodeSizeConstraintProvider is queried, so we create and return it if the queried type equals the INodeSizeConstraintProvider type.

Since our IContextLookupChainLink has been added last, it will be queried first when looking up the INodeSizeConstraintProvider type for a node. Our custom provider is returned, then.

Usually, the IContextLookup interface is meant to handle more complex lookup. For instance, we could cover more objects that we might want to return, or we could return objects dependent on the item context parameter. Decorating the lookup with only one object for a certain type is actually one of the most simple and common use cases for decorating lookups. For this use case, one of the addingLookupChainLink methods are more suitable. These implementations of IContextLookupChainLink are meant to add a single element to a lookup chain.

Again, let’s assume that we want to insert a custom INodeSizeConstraintProvider, stored in a variable constraintProvider into the lookup of nodes. The example Adding single elements to the lookup chain shows how this can be accomplished with the aforementioned methods.

There are three overloads of addingLookupChainLink that have a slightly different usage but the commonality is that the resulting IContextLookupChainLink simply returns the given instance if applicable and otherwise passes the call to the next chain link in the lookup chain.

Since it is a very common use case to decorate the lookup of an item by adding a single element, there is an even more concise method defined on the ILookupDecorator interface: addConstant<TContext,TResult>. See Adding a constant to the lookup chain for how to use it in the context of the previous examples.

Adding a constant to the lookup chain
decorator.addConstant(INode.$class, INodeSizeConstraintProvider.$class, constraintProvider)

If your lookup decoration is highly dependent on the actual instance of the type that is being decorated, an IContextLookupChainLink that is backed by a factory is the way to go. The factoryLookupChainLink<TContext,TResult> method takes a function as well as the expected and resulting type. The function takes the object that the lookup is being performed on and is expected to create an object of the expected type.

Factory IContextLookupChainLink illustrates an example usage taken from one of our demos. In the example, the lookup of the graph is being decorated for INode which is queried for IPortCandidateProvider. The mechanism of IPortCandidateProvider is explained in the Creating Edges section.

In this example we want to create an IPortCandidateProvider for the given node dependent on the color of the node, which is stored in its tag property. The node parameter of the portCandidateProviderFactory function is the object instance the lookup is called upon. Similar to the mechanism shown in Adding a constant to the lookup chain, the add<TContext,TResult> convenience method defined on ILookupDecorator can be used to achieve the same effect.

Another common use case is removing existing items from the lookup. If you added them yourself you can store a reference to the IContextLookupChainLink and call ILookupDecorator.removeLookup later on. However, most of the time you don’t have the exact lookup chain link at hand that is responsible for returning a specific object when queried. In those cases you can add another IContextLookupChainLink that effectively hides the rest of the chain by returning null.

The Hiding IContextLookupChainLink example shows the usage of the hidingLookupChainLink factory method to create such an IContextLookupChainLink. In the example, hiding is used to remove the visual appearance for selected edges. For information on the visualization of selection, focus, and highlighting, have a look at the Model Manager section.