soma.js

Show me some slides

or

Get to the site

What is soma.js?

soma.js is a javascript framework.

It is a lightweight framework that has been created to build loosely-coupled applications.

In other words, soma.js is a tool to prevent the future, be prepared, increase the scalability and maintainability of your application while it grows.

What can I do with it?

Pretty much anything that can be written in javascript.

soma.js can be used to create browser-based applications, server-side applications with node.js, mobile applications, and so on.

What is loose-coupling?

Loose-coupling is the "Principle of Least Knowledge", also called the Law of Demeter.

Put simply, it is a guideline to follow. The entities in your application (objects, functions) should not know or "talk" directly to each other.

Following this guideline will make your application more maintainable and adaptable since objects are less dependent. Ideally, entities can be changed without reworking others.

Too much theory?

Almost done, the code arrives very soon!

What does soma.js do for me?

Not much, and that's a good thing!

You choose your own libraries, as usual. Beside that, soma.js provides tools to communicate and manage dependencies in order to keep your code decoupled.

soma.js doesn't dictate. You will not have to use weird syntax or follow special ways of coding. You will be just writing plain javascript as you are used to.

soma.js core elements

soma.js is composed of different parts:

  • Dependency injection
  • Facade pattern
  • Observer pattern
  • Command pattern
  • Mediator pattern
  • Templates (plugin)

Let's see some code now!

Create an application instance

This is the first step to use soma.js.

The only moment you will have to extend a function.

var MyApplication = soma.Application.extend({
  init: function() {
    // do something
  }
});
var app = new MyApplication();

Create an entity

The next step could be creating a model that holds data, a view that represents a DOM element or anything that might fit your need.

var MyWidget = function() {
  // build my view
}
var MyModel = function() {
  // manage my data
}

In any case, only plain javascript is needed, which will make this entity highly re-usable.

How it feels

don't call us, we'll call you

This concept is the core of soma.js, when you need something, ask for it. The framework will provide the necessary dependencies.

Wouldn't it be great to say: "I need this object in that function", and just add a named variable to get it?

Get something

Consider a simple config object for your application.

var Config = {
  language: 'en',
  width: 960
}

To get it somewhere else, just ask for it using its "name". A name defined by yourself, let's say in that case the name "config".

Here are the two ways to ask for the config object.

var MyModel = function(config) {

}
var MyModel = function() {
  this.config = null;
}

Injection with infuse.js

infuse.js is an dependency injection library that has been built for soma.js.

In order for the framework to know what you are asking for, you need to define a "mapping rule". In other words, to name an object, a function, a string, or anything else.

var Config = {
  language: 'en',
  width: 960
}
var MyApplication = soma.Application.extend({
  init: function() {
    this.injector.mapValue('config', Config);
  }
});
var app = new MyApplication();

Full example

Now that the Config object has been named with the name "config", the framework will inject the object when it finds this name. Here is the full example.

// create a config object
var Config = {
  language: 'en',
  width: 960
};
// create a model that will receive the config object
var Model = function(config) {
  console.log(config.language);
};
// create an application
var MyApplication = soma.Application.extend({
  init: function() {
    // create a mapping rule for the config object
    this.injector.mapValue('config', Config);
    // instantiate the model
    this.createInstance(Model);
  }
});
// instantiate the application
var app = new MyApplication();

Why injection?

Dependency injection has been used in many languages.

Just to name a few, injection will reduce boilerplate code, increase readability, decouple objects, benefit of lazy instantiation and make your code much easier to test using mocking objects.

Access

The different parts of the framework can be accessed in the application instance, or using their injection name in other entities.

var MyApplication = soma.Application.extend({
  init: function() {
    this.injector;
    this.dispatcher;
    this.mediators;
    this.commands;
    this.instance;
  }
});
var Model = function(injector, dispatcher, mediators, commands, instance) {

};

Creation

Very early in your application you will want to create other entities, for example: models to hold your data or views to display a UI.

They are different ways to create objects in the framework:

  • Using plain javascript
  • Using the injector
  • Asking for a dependency
  • Creating mediators
  • Creating templates

Create objects using plain javascript

Nothing new here, you can create objects using plain javascript but you will not benefit from injection.

var Model = function() {

};
var myModel = new Model();

If you really need to create the object yourself, you can still inject your dependencies later.

this.injector.inject(myModel);

Create objects using the injector

Usually objects are created using their "names", but there are two ways of doing it yourself using the injector.

Using the object itself.

var Model = function(dependency) {

};
var myModel = this.injector.createInstance(Model);

Using an injection name if it has one.

var Model = function(dependency) {

};
this.injector.mapClass("model", Model, true);
var myModel = this.injector.getValue("model");

See the infuse.js documentation for more information.

Create objects by "asking"

Asking for a dependency will create a new instance, or return one already created (mapping rule option).

Get a new Config instance every time you ask for one.

var Config = function() {

};
this.injector.mapClass("config", Config);
var Model = function(config) {
  // the Config has been instantiated as I asked for one
};

Get the same Config instance every time you ask for one

this.injector.mapClass("config", Config, true);

See the infuse.js documentation for more information.

Create objects using a mediator

A Mediator is a pattern used to reduce coupling between objects. In short, it is a function that represents something else, such as a DOM Element or another function.

Mediators can be created using the "mediators" property of the framework instance (injectable), and will fully benefit of injection.

The first parameter is the mediator function itself, and the second one, the object it represents.

var Mediator = function(target, model) {
  // target is a DOM Element in this example
  // model could be another dependency
};
var element = document.getElementById('#element');
var mediator = this.mediators.create(Mediator, element);

Create objects using a template

Templates can be created to manipulate DOM Element and will also benefit of injection.

soma-template is an optional DOM manipulation template engine (not string-based) that can be used as a plugin for soma.js.

var Template = function(template, scope, element, model) {
  scope.title = "My Title";
  template.render();
};
var MyApplication = soma.Application.extend({
  init: function() {
    var element = document.getElementById('#element');
    this.createTemplate(Template, element);
  }
});

See the soma-template site for more information.

Communicating

Communication can be done using the objects themselves but you can also use the Observer pattern to strongly decouple your application.

soma-events has been implemented in soma.js, a "pub-sub" library that is using the W3C event system.

See the soma-events documentation for more information.

Events

The dispatcher property (injectable) is used to dispatch and listen to events.

var MyApplication = soma.Application.extend({
  init: function() {
    this.dispatcher.dispatch("some-event");
  }
});
var MyService = function(dispatcher) {
  dispatcher.dispatch("some-event", parameters);
};
var MyView = function(dispatcher) {
  dispatcher.addEventListener("some-event", function(event) {
    console.log(event.type, event.params);
  });
};

See the soma-events documentation for more information.

Commands

Create and use Commands is another way of decoupling an application. A command is a throw-away function that will be executed, triggered by an event.

It is another way of saying "execute this piece of code every time I dispatch this event".

var Command = function(model) {
  this.execute = function(event) {
    // execute gets automatically called (executed) by the framework
    // benefits of injection, a model is injected as an example
  }
};
this.commands.add("some-event", Command);
var MyView = function(dispatcher) {
  dispatcher.dispatch("some-event");
};

Flow control

One of the benefits of using commands and events is being able to control the application. A command can be interrupted by another entity if needed, using the same event workflow (preventDefault).

var Command = function() {
  this.execute = function(event, model) {
    // will not be executed
  }
};
this.commands.add("some-event", Command);
var MyView = function(dispatcher) {
  // dispatch a cancelable event
  dispatcher.dispatch("some-event", false, true);
};
var MyModel = function() {
  dispatcher.addEventListener("some-event", function(event) {
    // interrupt the command for some reasons
    event.preventDefault();
  });
};

Tools

Probably any library can be used in soma.js, here are some recommended libraries for some crucial tasks that are not handled by the framework.

I've seen enough, get me to the site.