Details of the Class Framework
This chapter contains background information about the yFiles for HTML class framework. The knowledge presented here is not required in order to use the vast majority of the APIs. Be sure to read Working with yFiles before continuing below.
The concepts presented in this chapter only apply to the use of yFiles for HTML in ECMAScript Level 5.In ES2015 and newer, we recommend using the new language feature of classes and their inheritance concepts, although you could still use the constructs of this chapter as well.
$class
and isInstance
apply to all classes that inherit from yFiles for HTML classes
regardless of the implementation, e.g. the yFiles class framework or the ES2015
class features.
Modules and Namespaces
The yFiles Class Framework provides the function yfiles.lang.module
to encapsulate the logic
of creating a new namespace object, or appending to an existing one. This function creates the specified
namespace if it does not yet exist, and invokes the callback function.
The callback function can then attach functions, types, properties or fields to that object and
they will be available in the specified namespace object.
The return value of the function is the innermost namespace container object.
While not strictly required, using modules helps to reduce boilerplate code and prevent pollution of the global namespace.
Now that you have an understanding of modules, it’s time to learn about the yFiles Class Framework’s type system.
Type System Overview
The yFiles Class Framework uses constructor functions in combination with prototypal inheritance and type augmentation. It is made up of five different types which can be combined to create robust objects that can be type checked and reflected at runtime: classes, interfaces, structures, enumerations, and attributes.
It supports the new
operator as well as the instanceof
operator where possible.
The standard JavaScript instanceof
operator does not work for interfaces and enums in all browser. Therefore,
we recommend to always use the isInstance(object)
static method instead of the instanceof
operator for checking for
interface implementation. Thi method is described below.
Every type defined using the yFiles Class Framework has the following static functions and properties:
- isInstance(object)
- Returns
true
if the object is an instance of the type. - $class
- Provides access to the
yfiles.lang.Class
instance that describes the type and can be used for reflective access and meta-programming. The following section introduces each of the five types that the class framework support.
The static isInstance
method and $class
property should only be used with types defined with the yFiles Class Framework.
We strongly advise to use the instanceof
operator in conjunction with ES2015 classes.
Classes
Classes are the most powerful types in a type system. They act as prototypes for their instances, can inherit from other classes and implement interfaces.
A class defined with the yFiles Class Framework consists of a (possibly) named constructor function with an appropriate prototype. By providing named constructors, you can have more than one constructor function for a class without having to write them differently from normal constructor functions.
The yFiles for HTML API does not make use of the named constructors feature. All constructors in the yFiles API are regular constructors without a specific name.
Classes can have both static and instance functions, fields, and properties.
Every class has an additional static $super
property which points to its parent class.
$super
can be used to invoke an overridden method definition:
All classes in the yFiles Class Framework inherit from at least
yfiles.lang.Object
, which provides useful methods like memberwiseClone
and
getClass
. The memberwiseClone
method
returns a shallow copy of the object, and getClass
returns the
yfiles.lang.Class
that represents the object’s type for meta-programming purposes.
Interfaces
At the most fundamental level, an interface is a description of the functionality of a type and does not come with an implementation for that functionality. yFiles interfaces can optionally provide default implementations for certain functionality that does not need to be implemented explicitly by implementing classes.
A class can implement multiple interfaces. There are rules in place to prevent the “diamond inheritance problem” known from C++. Interface inheritance is explained in more detail in Interface Inheritance.
Interfaces cannot be trivially instantiated, they can only be implemented by classes. With the quick interface definition feature, creating an instance of an anonymous type that implements that interface can be done nevertheless.
Structures
Structures are similar to ordinary classes but mimic
value types, or primitives, like the native Number
, Boolean
, etc.
Technically of course these are still classes and instances are passed by reference, but they use different semantics
than classes and behave differently when cloned using the memberwiseClone
function.
When an instance of a structure is cloned, its values are cloned rather than referenced, so that when a primitive field
of the cloned object is modified, the original object is not changed.
Similarly, when an instance of a regular class is cloned using memberwiseClone
,
all of its structure members are also cloned recursively.
A structure always extends yfiles.lang.Struct
, which extends yfiles.lang.Object
and
can implement any number of interfaces, but a structure cannot be inherited from.
Enumerations
Enumerations are an alternative to static number fields which allow easier usage of an API by grouping related values, making it clear exactly what options are available, and by helping to catch invalid or mistyped values.
The Enum implementation of the yFiles Class Framework does not try to do anything special or smart. It simply contains a number of fields with primitive values, that can be used instead of magic constants or unrelated static fields.
Enumeration types can have additional static members, but since instances are technically just JavaScript numbers, they do not provide specific instance members.
Attributes
Attributes provide meta information about types and their properties. They are essentially classes but their constructor is only called when an attribute is actually used by the class framework, not when it is invoked. This lazy construction allows use of attributes with very little performance hit.
Access to attributes is provided by the reflection API of Class<T>.
The main purpose of attributes is to provide meta information about classes and properties during (de-)serialization of GraphML files.
Unless you need to customize GraphML IO, you probably never need to use Attributes.
Lazy Type Definitions
The types defined by the yFiles Class Framework need references to base classes and implemented interfaces at definition time to arrange the prototype chain, check the type, and reference default implementations for non-abstract interface members.
To prevent circular references during the process of class definitions, it is possible to declare
lazy type definitions.
Type definitions declared as lazy are evaluated at first access to their constructor. They must be contained
in a yfiles.lang.module
block and must have an associated namespace.
Now that you know the basic differences between the five types, let’s take a closer look at how to define and use each type.
Defining a Class in ES5
Classes are created by calling yfiles.lang.Class
as a function, and storing the result in a
variable.
The constructor must be named constructor
. Custom methods can
be defined in the same way.
The following identifiers are reserved and have a special meaning for the yFiles Class Framework:
- constructor
- The constructor function of the class or an object that defines multiple named constructor functions.
- 'default'
- Denotes the default constructor function of a class, when there are multiple named
constructors defined.
Note that the quotation marks are part of the reserved name.
Property in the
constructor
object. - $static
- An object that describes static functions, properties, and fields that should be available on the class, rather than on the instance.
- $clinit
- A static function that is called by the yFiles Class Framework to perform additional
class initialization.
This is an optional property in the
$static
object, which is only supported for lazily defined types. - $meta
- An array, or a function that returns an array, of
yfiles.lang.Attribute
objects. - $extends
- The class from which to inherit.
- $with
- An array of interfaces that the class implements.
- $abstract
- A boolean value that specifies whether the class is abstract and does not define all required methods, fields, or properties.
Static members are not stored in the prototype object, rather they are directly accessible from the class. The yFiles Class Framework adds them to the class’s native JavaScript constructor.
Named constructors are a good alternative to using a single constructor that evaluates the
arguments to conditionally handle multiple types of input. A big benefit of using the yFiles
Class Framework for this type of thing is that there is no
difference in how you write a named or a default/single constructor. If using multiple constructors,
simply mark the default one as 'default' including quotation marks.
Additionally, this
is
set correctly and invoking another constructor has the syntax that you’d expect:
ClassName.ConstructorName.call(this, arguments)
.
From the user’s perspective, named constructors resemble factory methods but with
an explicit new
. This
feature was inspired by Google Dart.
To call the parent class, simply use Classname.$super.methodName.call
.
There are two ways of defining properties, but both syntactic variants do the same thing: create a read-only property and define and implement an accessor. Properties must have at least a setter or a getter, but can, and usually do, have both.
The second, non-native, syntactic form is required when adding yfiles.lang.Attribute
objects
to a class member.
To get or set the value of a property of the parent class, use the following:
Class Inheritance
Inherit from another class by adding an $extends
member to the class definition.
A class inherits all non-static, non-constructor members of the class it extends. That is, subclasses do not automatically get the parent class’s constructors or static members.
If a non-abstract class inherits from an abstract class, it must implement all abstract members.
Interface Inheritance
Interface inheritance allows a class to state that it conforms to a specific interface. In contrast with traditional interface inheritance, it allows the reuse of standard implementations of interface methods.
A class which inherits from an interface with abstract members must implement all of the abstract members or be declared as abstract. Otherwise an error is thrown when the debug library version is used and the behavior is undefined when the production version is used.
If a class inherits from an interface with a non-abstract method and provides a method with the same name, the class implementation always has precedence. The same is true for members that are inherited from a base class, that is, the subclass implementation always has precedence.
To inherit from an interface, use the $with
member of a class definition.
To inherit from multiple interfaces, use the $with
member of a class definition with an array of interface references.
This class states that it implements three interfaces: ICollection, IEnumerable, and IList. It also extends the AbstractList class.
JavaScript only allows for a single prototype value to be tested using the instanceof
operator. Since the prototype is already used for class inheritance, it is not possible to test
whether an object implements a certain interface using the instanceof
operator.
Instead we use the isInstance
method for this purpose.
The tested object must be an instance of at least
yfiles.lang.Object
for this test to work. Also, it is not sufficient to
simply implement all methods defined by the interface; it must inherit from the interface
using $with
. The implementation
does not check for the existence of the members on the instance but relies on type information stored
in the object’s associated yfiles.lang.Class
object. This approach is significantly faster however requires the use
of the class framework. Instances that implement the interface by convention, only, are not considered instances
in the sense of the class framework. If such “duck-typed” objects are passed to the yFiles library, they will not be recognized
correctly and their implementations will not always be called properly.
You can use the isInstance
method on class, enumeration, structure, and annotation types, too.
Lazy Classes
Lazy classes are evaluated when they are first used or referenced. This offers two major benefits:
- Unused types are never evaluated
- The load order of the classes doesn’t matter
Using lazy classes can significantly improve your application’s load time, since most of your files can be loaded in parallel and the amount of code that runs during loading is much smaller, as only module definitions need to be processed.
Lazy classes are evaluated as soon as they are used. In the above example, any access to
myapp.model.Person
triggers evaluation of the class.
After a lazy class has been evaluated and created, the yFiles Class Framework will invoke
the static $clinit
class initialization function, if defined. This callback can be useful
if you need to perform complex calculation for static class members or to reduce the number of dependencies
on other types during construction, which can further help to reduce cyclic references between types.
Defining an Interface
Interfaces are defined using the yfiles.lang.Interface
function.
This example defines an interface with a single, abstract method. The yFiles Class Framework provides
yfiles.lang.Abstract
, yfiles.lang.AbstractMethod
and
yfiles.lang.AbstractProperty
for documentation purposes, but doesn’t differentiate
between them.
The primary difference between an interface in the class framework and most interfaces used by other languages and technologies is that yFiles interfaces can define implementations of the members they require.
In this example, the interface IIterable
requires its implementers to provide an
iterator
method. Every class that implements IIterable
will get
a free forEach
method in return but can optionally define its own optimized version of it.
While interfaces can have static members, these members are never inherited by classes or other interfaces.
Interface Inheritance
An interface can implement any number of other interfaces.
Since interfaces effectively allow for multiple inheritance of member definitions, the primary concern is how to resolve a conflict when a class implements two interfaces that each define the a member with the same name. This problem is known as the diamond inheritance problem.
The question is: which version of the
area
property is used in the Square
class?
The yFiles Class Framework follows a simple rule to resolve this kind of problem: If in doubt, it’s
abstract.
Because IRectangle
and ISquare
both implement IShape
's abstract
area
property, Square
is responsible for defining its own implementation so that there is no ambiguity.
Since it doesn’t, trying to access a Square
instance’s area would result in a runtime error.
The implementation of an interface is always preferred over implementations of its base interfaces.
Thus, if Square
were an interface and had an implementation of area
, then its
implementation would be used instead of any of the ones defined by its base interfaces.
To summarize the inheritance rules of interfaces: in cases where two interfaces with the same member are implemented by a subtype:
- If both method definitions are abstract, then the method is abstract.
- If one method definition is abstract and the other concrete, then the concrete method is used.
- If both method definitions are concrete:
- If the interfaces are implemented at the same level, then the method is abstract.
- If one of the interfaces is a sub-type of the other, then its method definition will be used.
Lazy Interfaces
Interfaces can also be defined as lazy types with the same semantics as the other types, such as being namespaced to a module. They are only created when they’re used. This feature allows for better startup time and helps to prevent cyclic dependencies.
The CookieFactory
, an interface which itself implements the Factory
interface,
is defined when it is first used.
Defining a Structure
Structures are value types. They have the same definition as classes, except that they
always directly extend yfiles.lang.Struct
. To create a new structure type, use
yfiles.lang.Struct
as a function.
Structures are defined just like classes. They have the same semantics and work the same way with one major difference:
If a class or structure is cloned using memberwiseClone
, all members that are structures are
cloned as well.
In the above example, $topLeft
is a Point, which is a structure.
If we clone an instance of Rect
, then the copy will have a cloned
$topLeft
member.
Note that while structures have a built-in clone method, you are responsible for
using it to implement structure semantics. That is, you must clone structures before passing them to a method
or returning them from one.
Structures also have more specialized versions of the equals
and hashCode
methods. Two structure instances with the same values are equal and have the same
hash code. This differs from the yfiles.lang.Object
definitions for equals
and
hashCode
,
which ensure that only identical objects are the same.
Lazy Structures
A lazy structure has the same properties as a lazy class. It must be namespaced
to a yfiles.lang.module
and is evaluated at first use.
Defining an Enumeration Type
Enums are enumerations of flags. They are easier to use than prefixed integer fields and the yFiles Class Framework supports a simple syntax to define such an enumeration type. Their usage makes code more readable and makes it easier to catch typos and invalid values.
All members of an enumeration are automatically static.
In this example, we have defined a shorter name for the undefined
value in JavaScript:
_
. When the value of an enumeration member is undefined, it will be set to the next logical
value. This will only work for integer fields and should not be mixed.
Since most of the time an enumeration value is only a integer value, you can use “bitwise or” and “bitwise and” to create flagged enumerations. In this case, you must define the value of the fields yourself.
The enumeration type also offers a few useful methods that can be used to convert values to strings and strings to values.
The yfiles.lang.Enum.parse
method takes parameters for the enumeration and value, with
an optional third parameter: ignoreCase
.
If this parameter is set to true
, then the strings 'LOW'
, 'Low'
, 'low'
, etc. will all
provide the same value.
The yfiles.lang.Enum.getName
method returns the string matching the specified value.
Lazy Enumerations
Lazy enumerations function similarly to lazy instantiation of other types. They must be namespaced to a module, and provide benefits at startup time and in preventing circular references.
Defining an Attribute
Attributes contain meta information about namespaces, types, and type members. yFiles for HTML uses meta information primarily for reading and writing GraphML file format, but other use cases exist.
yFiles Class Framework attributes are closer to C# attributes than to Java annotations. The major difference between both concepts is that attributes are actual classes which can extend other attributes, implement interfaces and have methods and properties.
This attribute could be used to annotate a data model so that you could generate a form for the model automatically.
Annotating ES2015 classes like this is possible since yFiles for HTML 2.3. It is also possible to annotate ECMAScript Level 5 yFiles classes with Attributes like this:
Even though attributes are class types, they are used like functions, i.e. without the new
keyword. This helps to differentiate them from a normal class constructor, since they behave differently.
While it may look like the previous sample created three instances of our FormTypeAttribute
,
it didn’t create any instances. Attribute constructors are only invoked when attributes are read using reflection.
Lazy Attributes
As with other types, lazy attributes must be namespaced to a module. This attribute will only be constructed when it is first used.
Events
The yFiles Class Framework is complemented by further support that provides convenience functionality around event handling.
Events can be easily subscribed to and unsubscribed from through associated methods for adding and removing listener callbacks. These methods take an event handler function as their parameter.
For example, the QueryItemToolTip
event of class GraphViewerInputMode can be easily subscribed to and unsubscribed
from through the addQueryItemToolTipListener
and removeQueryItemToolTipListener
method, respectively.
In the yFiles for HTML API documentation, the associated methods to add and remove listener callbacks to events are currently listed along with the corresponding event.
By means of the yfiles.lang.delegate
factory method, appropriate event
handler functions can be conveniently registered as implicit closures.
The yfiles.lang.delegate
method wraps a given function together with
a target object which serves as the caller context for when the given function actually
gets invoked.
The following code snippet from the Graph Viewer
tutorial demo application shows how an event handler function is registered as a
closure with the current this
context:
When, in response to the QueryItemToolTip event, the $onQueryItemToolTip
listener callback gets invoked, all uses of this
within the callback’s
body actually access the object that the this
referenced at invocation
time of the yfiles.lang.delegate
factory method.