ecore.js

What is it

Ecorejs brings Ecore metamodeling to Javascript. It implements the Ecore model in Javascript and includes JSON and XMI (XML Metadata Interchange format) writers and parsers for the Browser and Node.js.

Install

Browser

Download Ecore.js from dist/ folder, and include it in your html along with underscore.js.

<script src="underscore.js"></script>
<script src="ecore.js"></script>

Alternatively you can use the dependency manager Bower to install Ecore.js in your project.

bower install ecore

Node

Ecore.js is available on npm and can be use as a Node module. To install it simply use the following command from your terminal:

npm install ecore

Importing Ecore.js in a Node module is done as follow:

var Ecore = require('ecore');

Getting Started

In this short guide, you will learn how to create an Ecore model with ecore.js, how to create instances of it, what is the concept of resources and how to use them. You will also learn how to serialize resources into JSON and XMI and how to use events to listen to changes made on models and instances.

Install

The first step is to install Ecore.js.

If you intend to use it in the Browser, download ecore.js from the download links given above, and include it in your html file. You will also need to include underscore.js as ecore.js depends on it. If you want to have XMI support, you'll also need to include sax-js.

<script src="underscore.js"></script>
<script src="sax.js"></script> <!-- sax is needed for XMI support -->
<script src="ecore.js"></script>

Alternatively you can use the dependency manager Bower to install Ecore.js in your project.

bower install ecore

If you want to use ecore.js on the server side, e.g. with Node.js, simply use npm and install the ecore package.

> npm install ecore

var Ecore = require('ecore');

Models

In Ecore, models are made of packages (EPackage), classifiers (EClass, EDataType, EEnum) and structural features (EAttribute, EReference). A package contains the classifiers while these latter are made of strcutural features.

var LibraryPackage = Ecore.EPackage.create({
    name: 'library',
    nsPrefix: 'library',
    nsURI: 'http://www.example.org/library'
});

The example chosen is a model of library, adapted from a classic EMF example. This model represents the concept of Book, Writer and Library and their relations.

The following code shows how to create the EClass Library.

var Library = Ecore.EClass.create({
    name: 'Library',
    eStructuralFeatures: [
        Ecore.EAttribute.create({ name: 'name', eType: Ecore.EString });
    ]
});

The Library object is an EObject having for eClass Ecore.EClass. This latter can be accessed via the property eClass.

Library.eClass // -> Ecore.EClass

EObjects have getter and setter methods that allow access and modifications to their values. We can for example change the value of the property name of Library like this:

Library.get('name'); // -> Library
Library.set('name', 'Foo');
Library.get('name'); // -> Foo

Note that features with an upper bound > 1 return an EList and cannot be set.

Library.get('eStructuralFeatures'); // -> EList([ EAttribute(name) ])
Library.get('eStructuralFeatures').add(aFeature);
Library.get('eStructuralFeatures').at(0); // -> EAttribute(name)
Library.get('eStructuralFeatures').remove(aFeature);
Library.get('eStructuralFeatures').array() // -> [EAttribute(name)]

// Note that derived features return an array, not an EList.
Library.get('eAllAttributes'); // -> [EAttribute(name)]

We can now create the remaining classes of our model.

var Item = Ecore.EClass.create({
    name: 'Item'
    abstract: true,
    eStructuralFeatures: [
        { eClass: Ecore.EAttribute, name: 'publicationDate', eType: Ecore.EDate }
    ]
});

var BookCategory = Ecore.EEnum.create({
    name: 'BookCategory',
    eLiterals: [
        Ecore.EEnumLiteral.create({ literal: 'Mystery', value: 0 }),
        Ecore.EEnumLiteral.create({ literal: 'Science Fiction', value: 1 }),
        Ecore.EEnumLiteral.create({ literal: 'Biography', value: 2 })
    ]
});

var Book = Ecore.EClass.create({
    name: 'Book',
    eSuperTypes: [ Item ],
    eStructuralFeatures: [
        Ecore.EAttribute.create({ name: 'title', eType: Ecore.EString }),
        Ecore.EAttribute.create({ name: 'pages', eType: Ecore.EInt }),
        Ecore.EAttribute.create({ name: 'category', eType: BookCategory })
    ]
});

var Writer = Ecore.EClass.create({
    name: 'Writer',
    eStructuralFeatures: [
        Ecore.EAttribute.create({ name: 'name', eType: Ecore.EString })
    ]
});

The remaining part of our model is about relationships between classes. In Ecore, a relationship is modeled with a EReference.

var LibraryItems = Ecore.EReference.create({
    name: 'items',
    upperBound: -1,
    containment: true,
    eType: Item
});

var LibraryWriters = Ecore.EReference.create({
    name: 'writers',
    upperBound: -1,
    containment: true,
    eType: Writer
});

var BookAuthor = Ecore.EReference.create({
    name: 'authors',
    upperBound: -1,
    eType: Writer
});

And we add those references to their respective classes.

Library.get('eStructuralFeatures')
    .add(LibraryItems)
    .add(LibraryWriters);

Book.get('eStructuralFeatures').add(BookAuthor);

Resources

Resources are containers for models and their instances. They are created from a ResourceSet and are identified by a URI.

var resourceSet = Ecore.ResourceSet.create();
var resource = resourceSet.create({ uri: '/library' });

Every EObjects must be contained in a Resource, whether directly or via their parent EObject. For example, when adding LibraryPackage in a Resource, all it's contained EObject (the classifiers) will be also contained by the resource.

resource.get('contents').add(LibraryPackage);
LibraryPackage.eResource(); // -> resource

Loading resources from the server by doing an ajax call is done via load. The method takes for parameter a success callback and an error callback.

resource.load(function(resource) {
    console.log('success loading', resource);
}, function(e) {
    console.log('error', e);
});

Fetching the content of a ResourceSet from the server is done via fetch. This will trigger a change event when done.

resourceSet.fetch();

Instances

You can now create instances of Library and Book by calling the method create.

var myLibrary = Library.create({ name: 'My Library' });
var emfBook = Book.create();

myLibrary.get('items').add(emfBook);

You can access the istance properties via the methods get and set like this.

emfBook.set('title', 'EMF Modeling Framework');
emfBook.get('title'); // -> EMF Modeling Framework

Note also that underscore collections functions are available on EList, so you can use each, map, reduce, etc., functions on EList.

myLibrary.get('books').map(function(book) { return book.get('title'); });

Events

Every EObject and EList are given the ability to bind and trigger custom named events. Defaults events are triggered when an EObject value is set or modified (change event) or when an element is added or removed from an EList (add and remove events). Events are also available on Resource and ResourceSet.

To bind a callback to an EObject, call the method on. The callback will be invoked whenver the event is fired. Note that events can be namespaces, for example the event change:title will only be fire when the attribute title is modified.

myLibrary.on('change:title', function(changed) {
    console.log('title changed');
});

var onAddBook = function(changed) {
    console.log('book added');
};
myLibrary.on('add:items', onAddBook);

Triggering events is done by calling trigger.

myLibrary.trigger('add remove');

Events can be removed by calling off.

myLibrary.off('add:items', onAddBook);

Working with JSON

JSON is the default serialization format supported by ecore.js. The JSON format is described here and looks like this:

{
    "eClass" : "/library#//Library",
    "name" : "myLibrary",
    "items" : [
        {
            "eClass": "/library#//Book",
            "title": "EMF Modeling Framework"
        }
    ]
}

Getting the JSON representation of a Resource can be done by invoking Resource.to.

resource.to() // -> JSON object

Parsing a JSON object into a Resource can be done via parse.

resource.parse(data);

Working with XMI

XMI support is provided in ecore.js, but it is limited and does not support all the XMI specification. This support requires sax.js.

Getting the XMI representation of a Resource is also done via the method to. This time you'll need to indicate that you want to obtain XMI by adding Ecore.XMI as parameter.

resource.to(Ecore.XMI, true); // returns the XMI string

Parsing XMI is done via the parse method.

resource.parse(data, Ecore.XMI); // data being a string containing the XMI.