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"

m (JS4EMF moved to JS4EMF/Developer Guide: The content was more technical. JS4EMF should be more of an overview.)
(EMF Javascript support)
Line 1: Line 1:
=EMF Javascript support=
+
= Javascript for EMF =
The EMF Javascript support is based on Mozilla'a Javascript implementation (Rhino) and lets you use EMF objects in Javascript code. Besides Javascript's existing support for using EMF objects as native Java objects, there is EMF-specific support for
+
* using Ecore attributes (EAttribute) and references (EReference) as Javascript properties, with the . and [] notations,
+
* implementing Ecore operations (EOperation) with Javascript code
+
* adding Javascript functions as methods to Ecore classes (EClass) and individual objects (EObject)
+
* listening to change notifications and calling event-handling methods
+
* databinding, by means of dependency recording
+
  
Each of these is covered in the sections below. But, first we give a small introduction to the Javascript object model.
+
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:
  
==The Javascript object model==
+
*You may access attributes, references and operations as Javascript properties, with the . and [] notations.  
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.
+
*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.
 +
*Change notifications may trigger Javascript methods.  
 +
*Dependency recording (record all read accesses to EObject features), e.g. to support generic databinding.
  
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 (<code>&#91;&#93;</code>) is used for the general case, e.g. <code>obj&#91;1&#93;</code> or <code>obj&#91;'strange name'&#93;</code>.
+
Each of these is covered in the sections below. But, first we give a small introduction to the Javascript object model.  
  
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 each prototype objects may be modified at any point. It can also be very confusing, and messing with the prototype structure requires great care.
+
== The Javascript object model ==
  
Note that properties that are not handled by the EMF-specific mechanisms described below, default the Rhino's default behavior for Java objects.
+
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.  
  
==Using Ecore attributes and references as Javascript properties==
+
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 (<code>[]</code>) is used for the general case, e.g. <code>obj[1]</code> or <code>obj['strange 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&#91;'name'&#93;</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&#91;useFullName ? 'fullName' : 'name'&#93;</code>.
+
  
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.
+
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.  
  
==Implementing Ecore operations (EOperation) with Javascript code==
+
Note that properties that are not handled by the EMF-specific mechanisms described below, are handled by Rhino's default behavior for Java objects.  
In Ecore, methods are modelled using operations (EOperation). Since Javascript methods are properties with function values, operations are returned as such. This only has meaning for implemented operations, and this is supported by annotating the EOperation with Javascript code that defines the corresponding function. We use Ecore's existing EAnnotation mechanism, so this may be done using the default Ecore editor support.
+
  
E.g. suppose you have a class with a name attribute and an operation named 'getSuffixedName'. The method takes a parameter named 'suffix', which should return the 'name' attribute with the suffix appended. You first add an annotation with the source (URI) set to http://www.eclipse.org/e4/emf/ecore/javascript/operationBody. On this annotation you define a detail entry with key as 'js' and value as <code>return this.name + suffix;</code>. this.name will retrieve the name attribute and suffix will refer to the parameter name. When the method is called, the 'js' entry of the annotation will be evaluated as (the body of) a Javascript function definition, returned as a function value and applied to the argument.
+
== [[|]]Using Ecore attributes and references as Javascript properties ==
  
==Adding Javascript functions as methods to Ecore classes (EClass) and individual objects (EObject)==
+
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>.  
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:
+
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.
<pre>
+
 
function create() {
+
== [[|]]Adding Javascript functions as methods to Ecore classes (EClass) and individual objects (EObject) ==
 +
 
 +
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:  
 +
<pre>function create() {
 
var factory = this.ePackage.eFactoryInstance;
 
var factory = this.ePackage.eFactoryInstance;
 
var eObject = factory.create(this);
 
var eObject = factory.create(this);
 
return eObject;
 
return eObject;
 
}
 
}
</pre>
+
</pre>  
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 ==
 +
 
 +
== [[|]]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:
 +
 
 +
#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.
 +
#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'&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 ==
  
==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:
 
# 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.
 
# 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'.
 
 
==Databinding, by means of dependency recording==
 
 
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:13, 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:

  • 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.
  • 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.
  • 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.

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 Ecore classes (EClass) and individual objects (EObject)

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();.

[[|]]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'.

[[|]]Databinding, by means of dependency recording

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