SomaCore is a lightweight AS3 MVC event-based framework that provides a very flexible structure for your Flash, Flex and AIR projects.

The main addition in this version 2 is Dependency Injection, but it is up to you how you want to use SomaCore because injection is optional.

Information

SomaCore Main Page (tutorials, demos, source)
Repository SomaCore (github)
Repository Demos (github)
Repository Plugins (github)
Documentation

Don’t want to read, just show me some code!

SomaCore Hello World
SomaCore CafeTownSend (Flex)
Twitter Search demo (pure as3)

SomaCore is both a registry and dependency injection framework

As a base, SomaCore is a registry framework, much like PureMVC. The framework makes you able to add and remove elements to the framework instance to be able to access them from anywhere (such as addView, getView, addWire, getWire, addModel, getModel, addCommand, and so on).

Enable injection

You are now able to use dependency injection. It can be enabled by sending an ISomaInjector class to the constructor of the Soma class:

public class SomaApplication extends Soma implements ISoma {
    public function SomaApplication(stage:Stage) {
        super(stage, SomaInjector);
    }
}

The injector is accessible from framework instances, wires and commands using the property injector.

Here is a quick example:

injector.mapToInstance(MyModel, new MyModel());
injector.mapSingleton(MyWire);
injector.getInstance(MyWire);
package  {
    import com.soma.core.wire.Wire;
    import com.soma.core.interfaces.IWire;
    
    public class MyWire extends Wire implements IWire {

        [Inject]
        public var model:MyModel;
        
        override public function initialize():void {
            // called when the wire has been registered to the framework
            trace(model, " is injected");
        }
        
        override public function dispose():void {
            // called when the wire has been removed from the framework
        }
        
    }
}

Inspirations

Before going further, I have to give some credits.

When I first tried Robotlegs, one part strikes me as it was solving a problem I had for a while: The mediator system: being able to build your application and add mediators regardless of any kind of structure and very easily. This workflow was so nice that it made me rethink a lot of things.

SomaCore has some capabilities that made me build the framework and I wasn’t ready to give them up. But I felt that this mediator workflow in SomaCore would just be amazing and a great addition.

I built these tools and frameworks to make my developer life easier, I then share them to other developers. If my work makes them enjoy their coding, that’s good enough for me.

So before asking myself too many questions, I reproduced the mediator system in SomaCore. It is now very similar to Robotlegs but it can also be used without enabling injection. That is the first part of the credits: a big one to Robotlegs that has been an amazing inspiration to SomaCore, as big as PureMVC has been for the version 1.

The second credit would go to the library SwiftSuspenders, the same one that Robolegs is using to handle its dependency injection. It is very lightweight and has good performance.

Mediators

Here is how it works, you can map a view to another class (the mediator), and every time this “mapped view” will be added to a display list, the mediator class will be instantiated with a reference to this view. The mediator class will also be destroyed when the view is removed from the display list. Everything is automatic and handled by the framework.

In SomaCore the Mediator class is extending the Wire class which gives access to everything in the framework.

Mapping a view (example in a wire):

mediator.mapView(MySprite, MySpriteMediator);

The mediator get instantiated when the view is added to the display list

addChild(new MySprite());

Mediator example:

package {
    import com.soma.core.interfaces.IMediator;
    import com.soma.core.mediator.Mediator;
    public class MySpriteMediator extends Mediator implements IMediator {
        
        [Inject]
        public var view:MySprite;
        
        override public function initialize():void {
            // called when the mediator has been created and registered to the framework
            trace(view == viewComponent);
        }
        
        override public function dispose():void {
            // called when the mediator has been destroyed by the framework
        }
        
    }
}

From version 1 to version 2

Only minor changes:

1. initialize and dispose methods in wires and models are now public.

package  {
    import com.soma.core.interfaces.IModel;
    import com.soma.core.model.Model;
    public class ModelExample extends Model implements IModel {
        override public function initialize():void {
            // called when the model has been registered to the framework
        }
        override public function dispose():void {
            // called when the model has been removed from the framework
        }
    }
}

2. the framework instance property has changed from soma to instance.

package  {
    import com.soma.core.wire.Wire;
    import com.soma.core.interfaces.IWire;
    public class WireExample extends Wire implements IWire {
        override protected function initialize():void {
            // called when the wire has been registered to the framework
            trace("my framework instance:", instance)
        }
        override protected function dispose():void {
            // called when the wire has been removed from the framework
        }
    }
}

3. a public setup method is available in your framework instance.

var app:ISoma = new Soma();
app.setup(stage, SomaInjector);

4. an initialize and start protected methods have been added in the Soma instance.

package  {
    import com.soma.core.Soma;
    import com.soma.core.interfaces.ISoma;
    import com.soma.core.di.SomaInjector;
    import flash.display.Stage;
    public class SomaApplication extends Soma implements ISoma {
        public function SomaApplication(stage:Stage) {
            super(stage, SomaInjector);
        }
        override protected function initialize():void {
            
        }
        override protected function registerCommands():void {
            
        }
        override protected function registerModels():void {
            
        }
        override protected function registerViews():void {
            
        }
        override protected function registerWires():void {
            
        }
        override protected function registerPlugins():void {
            
        }
        override protected function start():void {
            
        }
    }
}

SomaCore features

Here are some features that might makes you enjoy the framework.

Stage as single event dispatcher

SomaCore is using a single event dispatcher: the stage, and a system of event interception to handles its commands system. As the commands in SomaCore are normal built-in events with the property bubbles set to true, this makes you able to dispatch commands straight from a view removing potential layers (like mediators) to handles communications from the views to the framework.

This also makes you able to communicate between different modules (or swf files) as they all share the same stage.

Seamless commands and events integration

The commands in SomaCore are normal built-in events and the framework has a seamless integration with the flash event system. Seamless meaning that the built-in syntax is in use: dispatchEvent, addEventListener, and so on.

Register a command:

addCommand(MyEvent.DO_SOMETHING, MyCommand);

Dispatch a command from a framework elements (wires, commands, models, etc) or views:

dispatchEvent(new MyEvent(MyEvent.DO_SOMETHING));

Monitoring commands from wires, mediators, or models:

addEventListener(MyEvent.DO_SOMETHING, myEventHandler);

Commands monitoring

This capability is probably one of the most important to me. SomaCore makes you able to listen to commands from any wires and mediators, but also makes you able to stop the execution of any command (if the event is set as cancelable) using event.preventDefault().

package  {
    import com.soma.core.wire.Wire;
    import com.soma.core.interfaces.IWire;
    public class WireExample extends Wire implements IWire {
        override public function initialize():void {
            // called when the wire has been registered to the framework
            addEventListener(MyEvent.DO_SOMETHING, myEventHandler);
        }
        private function myEventHandler(event:MyEvent):void {
            // the command MyEvent.DO_SOMETHING has been dispatched
            // I can monitor it here (possible from any wires and mediators)
            // for some reasons, I decide to stop the execution of this command:
            event.preventDefault();
        }
        override public function dispose():void {
            // called when the wire has been removed from the framework
            removeEventListener(MyEvent.DO_SOMETHING, myEventHandler);
        }
    }
}

Easy way to map a mediator to a view

This is the workflow I reproduced from Robotlegs. It allows you to build you application, let’s say… complicated layouts, structure, tons of views, and so on. But at any point, if the framework needs to talk to a specific view, you can easily map a mediator to this view:

mediator.mapView(MyView, MyViewMediator);

For the other way around: a view needs to talk to the framework, mediators are not necessarily needed as you can dispatch commands straight from views in SomaCore. The commands are just normal events. The only requirement is that the view needs to be on the stage (added to a display list, after a ADDED_TO_STAGE event) in order for the stage to get the command.

Flexibility (injection, not injection, or both)

As the injection is optional in the framework, you are free to code the way you like, in the registry way like PureMVC way or in the injection way like Robotlegs, or even using both ways in the same time.

For some reasons, you might want to avoid injection, describeType, getElementByName, not comfortable yet with the concept, portability, optimal performance for games, etc. This is still possible with SomaCore.

Here are the built-in registry methods without injection:

addWire(MyWire.NAME, new MyWire());
getWire(MyWire.NAME);
addModel(MyModel.NAME, new MyModel());
getModel(MyModel.NAME);
addView(MY_SPRITE_NAME, new Sprite());
getView(MY_SPRITE_NAME);

With injection, you might do something like:

injector.mapSingleton(MyModel);
injector.createInstance(MyWire);
// MyWire injectee class
[Inject]
public var model:MyModel;

You can also use both:

var wire:MyWire = new MyWire();
injector.mapToInstance(MyWire, wire);
addWire(MyWire.NAME, wire);

Making you able to get the wire back this 2 ways:

[Inject]
public var wire:MyWire;
getWire(MyWire.NAME)

Plugins system

An easy way to add plugins to the framework, the SomaDebugger is the first one but I have plans to write others.

Debugger

The SomaDebugger especially built to work with SomaCore (even if it can be used without).

protected function registerPlugins():void {
    createPlugin(SomaDebugger, new SomaDebuggerVO(this, SomaDebugger.NAME_DEFAULT, getCommands(), true, false));
}

Lightweight

SomaCore is lightweight, here are the size compiled of the framework:

9.46 KB (9 693 bytes) – injector not enabled
14.83 KB (15 185 bytes) – injector enabled

Next steps

I’ll make new documents for code examples, best practices, and so on to help you get started.
Maybe a javascript version as a developer has made an amazing port of SomaCore.

If you’re looking for more information about the dependency injection part before I write documents, check the README file from the injection library: SwiftSuspenders.

Try SomaCore out, it is really easy to get started with. You’ll feel at home very quickly with its flexibility and well integrated command system with built-in events.

Share and Enjoy!