documentationfor yFiles for HTML 2.6

Interfaces

Interfaces declare what functionality a type provides. A type or class is said to implement a certain interface if it provides all the functionality that is defined in that interface. Types can implement any number of interfaces, but they can only ever extend one other class at the maximum. This is similar to the behavior of interfaces in languages like Java, C#, and TypeScript.

In yFiles for HTML however interfaces can also carry additional functionality that can be mixed-in into classes without them having to implement that behavior themselves. This feature is known as “mixins” or “trait inheritance” from other languages and is similar to what Java programmers can do with default implementations in interfaces and what C# programmers can achieve with extension methods that work on interfaces.

This means that for a class to successfully implement an interface it needs to provide all the implementations of the instance methods and instance properties that are declared as abstract by the interface. For all non-abstract interface members the default implementation of the functionality available on the interface can be reused. For the consumers of an API this means that the available functionality on a type is a lot more than just the implementations that the type directly provides.

instanceof Checks for Interfaces

Checking whether an object instance implements a given interface can be done with the static isInstance(object) method that is automatically provided by each interface type.

The following example checks whether an IModelItem is an INode or an IEdge.

// item is an IModelItem

if (item instanceof INode) {
  console.log('I am a node')
} else if (item instanceof IEdge) {
  console.log('I am an edge')
}

The standard JavaScript instanceof operator does not work for interfaces and enums in all browser. Therefore, we recommend to always use the isInstance(object) method instead of the instanceof operator for checking for interface implementation.

Implementing Interfaces

For this “mixin” and trait-inheritance feature to work as well as to properly declare the interface implementation to the class framework with the JavaScript type system, the yFiles for HTML class framework needs to be used.

Depending on whether you are authoring your JavaScript code using TypeScript, ES2015 or ECMAScript Level 5, different options exist.

For ES2015 implementing an interface so that it correctly works with the yFiles for HTML API is easy:

Implementing a yFiles interface using JS class syntax
class CustomHitTestable extends BaseClass(IHitTestable) {
  isHit(context, location) {
    return location.x > 25
  }
}

This is very similar to the way TypeScript works:

Implementing a yFiles interface using TypeScript
import {
  BaseClass,
  IVisualCreator,
  IRenderContext,
  Visual,
  SvgVisual
} from 'yfiles';

class CustomVisualCreator extends BaseClass(IVisualCreator) {

  createVisual(context: IRenderContext): Visual {
    return new SvgVisual(document.createElementNS('http://www.w3.org/2000/svg', 'g'))
  }

  updateVisual(context: IRenderContext, oldVisual: Visual): Visual {
    return oldVisual
  }
}

Alternatively, many interfaces provide static create methods, that allow for quick anonymous interface implementation.

Using the yFiles class framework, interfaces can be implemented like this:

Implementing a yFiles interface using the class framework
const CustomHitTestable = yfiles.lang.Class('CustomHitTestable', {
  $with: yfiles.input.IHitTestable,

  isHit: function (context, location) {
    return location.x > 25
  }
})

Class and BaseClass both take as arguments an optional base class and any number of interfaces. When using JavaScript, Class and BaseClass are interchangeable, whereas in TypeScript only BaseClass can be used. The base class argument may only reference a yFiles for HTML class or a class that inherits from such a class using prototypal inheritance.

For more detailed background information about interfaces in the class framework, please see the section about interfaces.

With pure ECMAScript Level 5 the same can be achieved in a little more verbose manner:

Implementing a yFiles interface using pure ES5
function CustomVisualCreator() {}

CustomVisualCreator.prototype = Object.create(yfiles.lang.Class(yfiles.view.IVisualCreator).prototype)
CustomVisualCreator.prototype.constructor = CustomVisualCreator
CustomVisualCreator.prototype.createVisual = function (context) {
  return new yfiles.view.SvgVisual(document.createElementNS('http://www.w3.org/2000/svg', 'g'))
}
CustomVisualCreator.prototype.updateVisual = function (context, oldVisual) {
  return oldVisual
}

Quick Interface Implementation

For cases where a class declaration is not required and all that is needed is an instance that implements a certain interface, there is a short-hand syntax available that works for all levels and variants of JavaScript. If the interface only contains a single abstract method, the function definition for that method can be directly passed to the interface type if invoked as a constructor:

Quick simple interface implementation in ES2015
const customHitTestable = new IHitTestable((context, location) => location.x > 25)
Quick simple interface implementation in ECMAScript Level 5
const customHitTestable = new IHitTestable(function (context, location) {
  return location.x > 25
})

If the interface contains more than one abstract member or abstract properties, an options argument has to be used instead.

The options argument needs to contain exactly the abstract members of the interface. No member may be omitted and no additional members can be added. While additional custom members can later be added directly on the instance, this is discouraged.

Quick interface implementation in ES2015
const customVisualCreator = new IVisualCreator({
  createVisual(context) {
    return new SvgVisual(document.createElementNS('http://www.w3.org/2000/svg', 'g'))
  },
  updateVisual(context, oldVisual) {
    return oldVisual
  }
})
Quick interface implementation in ECMAScript Level 5
const customVisualCreator = new IVisualCreator({
  createVisual: function (context) {
    return new SvgVisual(document.createElementNS('http://www.w3.org/2000/svg', 'g'))
  },
  updateVisual: function (context, oldVisual) {
    return oldVisual
  }
})

Quick Interface Implementation in TypeScript

The TypeScript .d.ts file doesn’t contain typings that allow quick interface implementations via the interface constructor. They still work at runtime, but the TypeScript compiler will produce an error nonetheless. Because of that, many interfaces provide static create methods, which accept the same arguments as the interface constructors. Whether an interface provides such a create method can be seen in the documentation viewer or in the TypeScript .d.ts file.

Quick interface implementation in TypeScript
const customHitTestable = IHitTestable.create((context, location) => location.x > 25)

const customVisualCreator = IVisualCreator.create({
  createVisual(context: IRenderContext): Visual {
    return new SvgVisual(document.createElementNS("http://www.w3.org/2000/svg", "g"))
  },
  updateVisual(context: IRenderContext, oldVisual: Visual): Visual {
    return oldVisual
  }
})

Extending Classes and Implementing Interfaces

Sometimes it is necessary to both extend from an existing API class and at the same time add new interfaces to the newly created subclass.

This can be done using a variant of the techniques shown in Implementing Interfaces:

For ES2015 extending a class and implementing an interface so that it correctly works with the yFiles for HTML API looks like this:

Complex inheritance case using ES2015
class CustomLayout extends BaseClass(LayoutStageBase, ILayerer, ISequencer) {
  constructor() {
    super(new HierarchicLayout())
    let hl = super.coreLayout
    // assign custom instances for the sequencer and layerer implemented directly by this type
    hl.hierarchicLayoutCore.sequencer = this
    hl.hierarchicLayoutCore.layerer = this
  }

  applyLayout(graph) {
    super.applyLayoutCore(graph)
  }

  assignLayers(graph, layers, layoutDataProvider) {
    // this implementation just delegates to another implementation
    new BFSLayerer().assignLayers(graph, layers, layoutDataProvider)
  }

  sequenceNodeLayers(graph, layers, layoutDataProvider, itemFactory) {
    // this implementation is a void implementation of the interface - no sequencing will be performed.
  }
}

This is very similar to the way TypeScript works:

Complex inheritance case using TypeScript
import {
  BaseClass,
  LayoutStageBase,
  LayoutGraph,
  ILayerer,
  ISequencer,
  HierarchicLayout,
  ILayers,
  ILayoutDataProvider,
  BFSLayerer,
  IItemFactory
} from 'yfiles';

class CustomLayout
  extends BaseClass(LayoutStageBase, ILayerer, ISequencer) {

  constructor() {
    super(new HierarchicLayout())
    let hl = super.coreLayout as HierarchicLayout
    // assign custom instances for the sequencer and layerer implemented directly by this type
    hl.hierarchicLayoutCore.sequencer = this
    hl.hierarchicLayoutCore.layerer = this
  }

  applyLayout(graph: LayoutGraph): void {
    super.applyLayoutCore(graph)
  }

  assignLayers(
    graph: LayoutGraph,
    layers: ILayers,
    layoutDataProvider: ILayoutDataProvider
): void {
    // this implementation just delegates to another implementation
    new BFSLayerer().assignLayers(graph, layers, layoutDataProvider)
  }

  sequenceNodeLayers(
    graph: LayoutGraph,
    layers: ILayers,
    ldp: ILayoutDataProvider,
    itemFactory: IItemFactory
): void {
    // this implementation is a void implementation of the interface - no sequencing will be performed.
  }
}

Using the yFiles class framework, interfaces can be implemented like this:

Complex inheritance case using the yFiles class framework
const CustomLayout = yfiles.lang.Class('CustomLayout', {
  $extends: yfiles.layout.LayoutStageBase,
  $with: [yfiles.hierarchic.ILayerer, yfiles.hierarchic.ISequencer],

  constructor: function () {
    yfiles.layout.LayoutStageBase.call(this, new yfiles.hierarchic.HierarchicLayout())
    const hl = this.coreLayout
    // assign custom instances for the sequencer and layerer implemented directly by this type
    hl.hierarchicLayoutCore.sequencer = this
    hl.hierarchicLayoutCore.layerer = this
  },

  applyLayout: function (graph) {
    yfiles.layout.LayoutStageBase.prototype.applyLayoutCore.call(this, graph)
  },

  assignLayers: function (graph, layers, layoutDataProvider) {
    // this implementation just delegates to another implementation
    new yfiles.hierarchic.HierarchicLayout().assignLayers(graph, layers, layoutDataProvider)
  },

  sequenceNodeLayers: function (graph, layers, layoutDataProvider, itemFactory) {
    // this implementation is a void implementation of the interface - no sequencing will be performed.
  }
})

For more detailed background information about interfaces in the class framework, please see the section about interfaces.

With pure ECMAScript Level 5 the same can be achieved in a more verbose manner:

Complex inheritance case using ECMAScript Level 5
function CustomLayout() {
  yfiles.layout.LayoutStageBase.call(this, new yfiles.hierarchic.HierarchicLayout())
  const hl = this.coreLayout
  // assign custom instances for the sequencer and layerer implemented directly by this type
  hl.hierarchicLayoutCore.sequencer = this
  hl.hierarchicLayoutCore.layerer = this
}

CustomLayout.prototype = Object.create(
  yfiles.lang.Class(yfiles.layout.LayoutStageBase, yfiles.hierarchic.ILayerer, yfiles.hierarchic.ISequencer).prototype
)

CustomLayout.prototype.constructor = CustomLayout
CustomLayout.prototype.applyLayout = function (graph) {
  yfiles.layout.LayoutStageBase.prototype.applyLayoutCore.call(this, graph)
}

CustomLayout.prototype.assignLayers = function (graph, layers, layoutDataProvider) {
  // this implementation just delegates to another implementation
  new yfiles.hierarchic.BFSLayerer().assignLayers(graph, layers, layoutDataProvider)
}

CustomLayout.prototype.sequenceNodeLayers = function (graph, layers, layoutDataProvider, itemFactory) {
  // this implementation is a void implementation of the interface - no sequencing will be performed.
}