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

Difference between revisions of "JS4EMF/Developer Guide"

(EMF Javascript support)
Line 1: Line 1:
= Javascript for EMF =
+
= 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:  
 
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:  
  
*You may access attributes, references and operations as Javascript properties, with the . and [] notations.  
+
*[[#Using Ecore attributes and references as Javascript properties|You may access attributes, references and operations as Javascript properties, with the . and [] notations.]]
*You may extend your EClasses and even individual EObjects with new methods.  
+
*[[#Adding Javascript functions as methods to EClasses|You may extend your EClasses and even individual EObjects with new methods.]]
*Built-in EMF classes, like EObject, EClass, EList etc., have been extended with methods that make them more practical for scripting.  
+
*[[#Built-in Javascript methods|Built-in EMF classes, like EObject, EClass, EList etc., have been extended with methods that make them more practical for scripting.]]
*Change notifications may trigger Javascript methods.  
+
*[[#Listening to change notifications and calling event-handling methods|Change notifications may trigger Javascript methods.]]
*Dependency recording (record all read accesses to EObject features), e.g. to support generic databinding.
+
*[[#Dependency recording and databinding|Dependency recording (record all read accesses to EObject features), e.g. to support generic databinding.]]
  
 
Each of these is covered in the sections below. But, first we give a small introduction to the Javascript object model.  
 
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 ==
+
== 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 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.  
Line 21: Line 21:
 
Note that properties that are not handled by the EMF-specific mechanisms described below, are handled by Rhino's default behavior for Java objects.  
 
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 ==
+
==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 <code>obj.name</code>. However, you may also use the bracket notation, as in <code>obj['name']</code>. 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 <code>obj[useFullName&nbsp;? 'fullName'&nbsp;: 'name']</code>.  
 
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 <code>obj.name</code>. However, you may also use the bracket notation, as in <code>obj['name']</code>. 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 <code>obj[useFullName&nbsp;? 'fullName'&nbsp;: 'name']</code>.  
Line 27: Line 27:
 
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. <code>anObj.$aName</code> 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.  
 
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. <code>anObj.$aName</code> 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 Ecore classes (EClass) and individual objects (EObject) ==
+
==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.  
 
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.  
Line 40: Line 40:
 
So to create an instance of a class named 'C' in package 'p', you can write <code>$p.$C.create();</code>.  
 
So to create an instance of a class named 'C' in package 'p', you can write <code>$p.$C.create();</code>.  
  
== [[|]]Built-in Javascript methods ==
+
==Adding Javascript functions to individual EObjects==
  
== [[|]]Listening to change notifications and calling event-handling methods ==
+
==Built-in Javascript methods==
 +
 
 +
==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:  
 
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:  
Line 51: Line 53:
 
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'&lt;eventType&gt;&lt;featureName&gt;, 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'.  
 
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'&lt;eventType&gt;&lt;featureName&gt;, 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'.  
  
== [[|]]Databinding, by means of dependency recording ==
+
==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.
 
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.

Revision as of 17:20, 8 June 2011

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 Ecore's meta-classes. In the folder /src/org/eclipse/e4/emf/ecore/javascriptEClass.js in the org.eclipse.e4.emf.javascript plugin there are .js files for EClass, EDataType, EEList and EObject 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

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