or
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.
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.
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.
Almost done, the code arrives very soon!
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 is composed of different parts:
Let's see some code now!
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();
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.
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?
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;
}
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();
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();
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.
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) {
};
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:
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);
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.
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.
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);
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.
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.
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.
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");
};
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();
});
};
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.