Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

JS4EMF/Developer Guide

< JS4EMF
Revision as of 17:39, 8 June 2011 by Hal.ntnu.no (Talk | contribs) (EObject methods)

Javascript for EMF

Javascript for EMF lets you use EMF objects in Javascript code, as if they were native Javascript objects. Here's a short list of nice features:

Each of these is covered in the sections below. But, first we give a small introduction to the Javascript object model.

The Javascript object model

In most object-oriented languages, the behavior (attributes and methods) of an object is determined by the class, from which it is instantiated. Both attributes and methods are defined in the class, and the instances created from a class will have a value for each defined attribute and the methods you may call are those defined in the class. If inheritance is supported, a class can be seen as the composition of its own attributes and those defined in its superclasses.

In Javascript, there is no classes and class-based instantiation. Instead you create empty objects and dynamically add properties to them. Methods are just properties with function values. While properties usually have simple Java-like names, they are in general String or integer keys. The dot notation is used for simple names, e.g. obj.simpleName, while brackets ([]) is used for the general case, e.g. obj[1] or obj['strange name'].

To create two objects with similar behavior, you simply add the same properties and methods to them. Alternatively, a mechanism for inheriting behavior from other objects is provided, by means of an object's prototype. If you try to access the value of a property that has not (yet) been added to an object, the property of the object's prototype is used instead. Hence, if two empty objects share the same prototype, they will effectively have the same default property values and methods. Note that since the prototype is a normal object with its own prototype, there really is a prototype chain providing default property values and methods. By carefully constructing partly shared prototype chains, you can get an effect similar to Java's single inheritance class hierarchy. This is what we do in our Javascript support for EObjects. The general prototype chain mechanism is, however, a lot more flexible than Java's inheritance, since the prototype chain and prototype objects may be modified at any point. It can also be very confusing, and messing with the prototype structure requires great care.

Note that properties that are not handled by the EMF-specific mechanisms described below, are handled by Rhino's default behavior for Java objects.

Using Ecore attributes and references as Javascript properties

When EObjects are used as Javascript objects, the attributes (EAttribute) and references (EReference) may be referred to as properties, using the dot notation. This means that the Javascript code used for accessing them, may be similar to its Java counterpart. E.g. to access the name attribute of obj, you write obj.name. However, you may also use the bracket notation, as in obj['name']. Since features in Ecore must have Java-like names, the bracket notation is typically used for arrays or when you compute the the property name as in obj[useFullName ? 'fullName' : 'name'].

A second kind of Javascript property is supported as a convenience. It is fairly common for an EObject to contain other EObjects, where the contained EObjects have a 'name' attribute that is unique among siblings. E.g. Ecore EPackages contain EClasses, EClasses contain EStructuralFeatures and EOperations contain EParameters. In such a case, a contained EObject may be retrieved using the name prefixed by '$' as the property. E.g. anObj.$aName will retrieve the EObject named 'aName' contained in anObj. This makes it easier to navigate in some EObject structures, particularly the Ecore meta-object structure. Note that this is limited to getting the object as a property, not setting it.

Adding Javascript functions as methods to EClasses

While the annotation mechanism allows implementing Ecore operations, it is not very convenient, since you must edit them individually using the property sheet and not as a whole in a code editor. Therefore we support implementing all the operations of Ecore classes as Javascript functions in separate .js files, one for each EClass. The .js file for an EClass is located by construction a URI from the EClass' and using the URIConverter of the ResourceSet that loaded the EClass, so the location is configurable. Typically, it will be placed in the folder and have the same name as the EClass with a .js file extension.

The .js file for an EClass will define the set of Javascript functions that will be callable as methods of EObjects of that EClass. Hence, the this keyword will refer to instances of that EClass inside the body of each function. The same technique is used for defining methods on existing classes, like EObject, EClass and EList (see below). In the core plugin there are .js files for EObject, EClass, EEList etc. that define methods for (instances of) these classes. E.g. in EClass.js the create methods is defined as follows:

function create() {
	var factory = this.ePackage.eFactoryInstance;
	var eObject = factory.create(this);
	return eObject;
}

So to create an instance of a class named 'C' in package 'p', you can write $p.$C.create();.

Adding Javascript functions to individual EObjects

Built-in Javascript methods

Many of the existing Ecore classes have been extended with methods that make them easier to use for scripting. Below you'll find a list of the supported classes and their methods. Note that these methods are in addition to those provided by Rhinos default handling of Java objects. Hence, you may call eClass() to get an EObject's EClass, even though eClass() isnt in the list below.

EObject methods

  • __(): returns the contents of this EObject
  • _(): returns the container of this EObject
  • isA(eClass): returns true of this is an instance of eClass, false otherwise
  • copy(): returns a copy of this using EcoreUtil.copy
  • findContainer(predicate): returns the first container satisfying predicate

EClassifier methods

function asPredicate() { var eClassifier = this; return function (arg) { return eClassifier.isInstance(arg); }; }

EClass methods

function create(initArgs) { var factory = this.ePackage.eFactoryInstance; var eObject = factory.create(this); if (initargs != undefined) { eObject.initialize(initArgs); } return eObject; }

EDataType

function create(string) { var factory = this.ePackage.eFactoryInstance; var object = factory.createFromString(this, string); return object; }

function toString(object) { var factory = this.ePackage.eFactoryInstance; var string = factory.convertToString(this, object); return string; }

Resource

  • __(): returns the contents of this resource
  • _(): returns the resource set of this resource

ResourceSet

EList (and List)

EMap (and Map)

Listening to change notifications and calling event-handling methods

In many applications, a lot of behavior is implemented be means of change notifications and handlers (called adapters in Ecore). EObjects have built-in support for notifications and we provide support for handling change notifications with Javascript code. There are two ways of attaching handlers:

  1. You explicitly attach a specific handler to a specific notifier by means of the adaptTo(notifier, handler) method. This is an EMF-specific Javascript method that may be called in Javascript code.
  2. You attach a single handler to an EObject hierarchy that makes the EObject handle its own notifications. This is achieved by means of JavascriptSupport.supportNotifications method, which must be called in Java code, typically after loading a resource.

In the first case, a specific handler is used for handling notifications on some other source object, while in the second case the object will handle notifications with itself as the source. But which method will be called to handle the notification? In both cases, the name of the method to be called is constructed from the pattern 'on'<eventType><featureName>, where eventType is one of 'Set', 'Unset', 'Add', 'Remove', 'AddMany', 'RemoveMany', and 'Move', and featureName is the name of the changed feature. E.g. if the notification resulted from setting an attribute named 'date', the 'onSetDate' method will be called to handle the notification. If the method doesn't exist, it tries again with eventType set to 'Change', e.g. 'onChangeDate'.

Dependency recording and databinding

To support Databinding, there is support for recording all read accesses to EMF properties during the execution of Javascript code and reacting to changes to those properties. Suppose you 1) computed some values based on the state of some EObjects and 2) change the state of some other EObjects. You would like to redo step 2) when the values from step 1) changes. This may be done by recording the dependencies during step 1), attaching listeners to the appropriate objects and redo both steps (while re-recording dependencies) when the appropriate change notifications are received.

Back to the top