1.3.0 • Published 8 years ago

flask-container v1.3.0

Weekly downloads
21
License
-
Repository
-
Last release
8 years ago

Build Status

Introduction

Flask is a tiny yet powerful IOC Container for your projects based on javascript, including nodejs. It resolves dependencies for you automaticly and improves your development flow tidying up your code.

It offers support for services, singletons, parameters and much more... let's dive in!

Index

Installation

Bower

bower install flask-container --save

Npm

npm install flask-container --save

Manually

git clone https://github.com/Aferz/flask

And reference it in your html:

<script src="path/of/my/libs/flask/dist/flask.min.js"></script>

Parameters

Parameters are simple values that are resolved for the container. It can store any kind of value.

.parameter(alias, value)

It registers a parameter into the container:

  var flask = new Flask(); // Creates our awesome container instance
  
  flask.parameter('heroe', 'Superman');

.value(alias)

Returns registered parameter out the container:

  var heroe = flask.value('heroe'); // Superman

Such impressive, isn't it? Let's step it further. With Flask you can inject parameters by reference just enclosing the name of one registered parameter between the symbol %:

  flask.parameter('heroe', 'Superman');
  flask.parameter('heroe2', '%heroe%');
  flask.parameter('heroe3', '%heroe2%');
  
  var heroe = flask.value('heroe3'); // Superman

Not bad. By the way, the order of registering doesn't matter.

Services & Singletons

Services are a fancy word to describe super cool reusable objects. That's all. Flask helps you building this objects for you and resolving its dependencies. This way you allways know your objects are created in the same way, making your code less complex, more secure and reliable.

Important note: all services are created with the new keyword. Don't say I didn't tell you.

.service(alias, definition [, args = []])

Register a service. Returns new instance on every call:

  function Villain(weapon) {
    this.weapon = weapon;
  }

  var heroe = flask.service('Villain', Villain, ['lazer']);

Now we can create a Villain instance when we need it.

.singleton(alias, definition [, args = []])

Of course, there is only one superman so we'll use a singleton for this case. A singleton returns the same instance once resolved for first time.

  function Superman(superPower1, superPower2) {
    this.superPower1 = superPower1;
    this.superPower2 = superPower2;
  }

  var heroe = flask.singleton('Superman', Superman, ['xRayVision', 'fly']);

.make(alias)

It's time to create some Villains and Heroes

  var villain1 = flask.make('Villain'); // Villain instance
  var villain2 = flask.make('Villain'); // Another Villain instance
  
  console.log(villain1 === villain2) // false
  
  var superman1 = flask.make('Superman'); // Superman instance
  var superman2 = flask.make('Superman'); // The same Superman instance
  
  console.log(superman1 === superman2) // true, I promise

Flask can resolve any dependency either strings, booleans, objects, functions, numbers ... even services using @, parameters using % and tags using #:

  function Superman(Batman, superPower) {
    this.Batman = Batman;
    this.superPower = superPower;
  }
  
  function Batman(superPower) {
    this.superPower = superPower
  }
  
  flask.parameter('superPower.superman', 'X-rays');
  flask.parameter('superPower.batman', 'Rich');
  
  flask.service('Superman', Superman, ['@Batman@', '%superPower.superman%']);
  flask.service('Batman', Batman, ['%superPower.batman%']);
  
  var superman = flask.make('Superman');
  
  console.log(superman.Batman); // Instance of Batman
  console.log(superman.superPower); // X-Rays
  console.log(superman.Batman.superPower); // Rich

This function support infinite nested dependencies. (Not really, javascript will crash when it reach the maximum call stack size.)

Note: Don't try to inject a service into a parameter with '@' syntax, it just won't work and it'll return the string. That's not the purpose of parameters.

Tagging

Tagging it's the way Flask offers to classify and resolve multiple primitive types, services, parameters or even tags at once.

.tag(alias, services)

  function Superman() {}
  function WonderWoman() {}
  function Batman() {}
  
  flask.parameter('Aquaman', 'Ocean'); // Dummy parameter, ok
  flask.service('Superman', Superman);
  flask.service('WonderWoman', WonderWoman);
  flask.service('Batman', Batman);
  
  flask.tag('JusticeLeague', ['@Superman@', '@WonderWoman@', '@Batman@', '%Aquaman%']);

.tagged(alias)

With this method we'll retrieve the registered tag out the container:

  var justiceLeague = flask.tagged('JusticeLeague')
  
  console.log(justiceLeague[0]); // Instance of Superman
  console.log(justiceLeague[1]); // Instance of WonderWoman
  console.log(justiceLeague[2]); // Instance of Batman
  console.log(justiceLeague[3]); // 'Ocean'

Of course, Flask offers support for resolving tags by reference:

  flask.tag('JusticeLeague', ['Superman', 'WonderWoman']);
  flask.tag('Avengers', ['IronMan', 'CaptainAmerica']);
  flask.tag('Superheroes', ['#JusticeLeague#', '#Avengers#']);
  
  var superHeroes = flask.tagged('Superheroes');
  console.log(justiceLeague[0][0]); // Superman
  console.log(justiceLeague[0][1]); // WonderWoman
  console.log(justiceLeague[1][0]); // IronMan
  console.log(justiceLeague[1][1]); // CaptainAmerica

Note: Don't try to inject a tag into a parameter with '#' syntax, it just won't work and it'll return the string. That's not the purpose of parameters.

Decorators

Flask offers functionality for decorate your services/singletons and parameters. A decorator its to add functionality to an object without touch its original source.

.decorate(alias, definition)

This is the proper way to register decorators and it can be used on two ways:

Applied globally

  flask.decorate(function (instance, flask) {
    // this will be executed before instance is returned for EVERY service/parameter resolved
  });

Applied only for specified service or parameter

  flask.decorate('ServiceA', function (instance, flask) {
    // this will be executed before instance is returned ONLY for ServiceA
  });

Note: Decorators are executed in order they were registered.

Calling & Wrapping

Sometimes we'll need to call or create a function injecting directly any dependency. Flask give us two methods that will help us on this task.

.call(definition, dependencies, context = null)

This method will call given function injecting specified dependencies as second argument. Let see an example:

  function SaveTheWorld(Superman, Batman) {
    console.log(Superman);
    console.log(Batman);
  }

  flask.service('Superman', Superman);
  flask.service('Batman', Batman);
  flask.call(SaveTheWorld, ['@Superman@' '@Batman@']); // Function will be inmediately called
  
  // Log: Instance of Superman
  // Log: Instance of Batman

As you can see, this method will call inmediately the passed definition, but ... What if you just want to resolve the arguments of the definition and store the result function in a variable to call it later? Flask to the rescue again:

.wrap(definition, dependencies, context = null)

  // We will use the previous example
  var resolvedFunc = flask.wrap(SaveTheWorld, ['@Superman@' '@Batman@']);
  
  // Function hasn't been called automaticly

  resolvedFunc()
 
  // Log: Instance of Superman
  // Log: Instance of Batman

Note: Both, call and wrap accept a third parameter to bind a context into the given function.

Eventing

Wouldn't be nice if you could listen events emitted by Flask? Well, i have good news for you. There are two ways when registering listeners:

Globally

  flask.<eventName>(function (instance, flask) {
    // this will be executed before instance is returned for EVERY service/parameter resolved
  });

Applied only for specified service or parameter

  flask.<eventName>('ServiceA', function (instance, flask) {
    // this will be executed before instance is returned ONLY for ServiceA
  });

Here we have a list of currently supported events:

.onResolved(alias, handler)

This method will register a listener that will be fired after service/parameter is resolved and instantiated:

  flask.onResolved(function (instance, flask) {
    // this will be executed before instance is returned for EVERY service/parameter resolved
  });

Config

We can modify the internals of our Flask container through configuration variables. Here is a list of variables that can be modified:

NameDefault valueWhat it does
serviceDelimiter@It delimites the reference of a service when resolving arguments injection.
parameterDelimiter%It delimites the reference of a parameter when resolving arguments injection.
parameterDelimiter%It delimites the reference of a parameter when resolving arguments injection.

.setConfigValue(key, value)

Set a config value:

  flask.setConfigValue('serviceDelimiter', '#');

.cfg(key)

Get config value by its key:

  var serviceDelimiter = flask.cfg('serviceDelimiter'); // '@'

Setting up Flask on bootstrap

In order to mantain good separation of concerns, Flask can be configured during its instantiation using a configuration object. This will allow to have our dependencies in one place (or many, with diferent config files). Let's see a full example.

  function serviceA () {}
  function serviceB () {}
  function serviceC () {}
  function aliasCListener () {}
  function globalListener () {}
  function param3Decorator () {}
  function aliasADecorator () {}
  function globalDecorator () {}

  var config = {
    config: {
      serviceDelimiter: '#',
      paramDelimiter: '~'
    },
    parameters: {
      param1: 'Parameter 1',
      param2: {
        value: '%param1%',
        tags: ['tag1']
      },
      param3: {
        value: 'Parameter 3',
        decorators: [param3Decorator]
      }
    },
    services: {
      aliasA: {
        service: serviceA,
        arguments: ['@aliasB@'],
        tags: 'tag1',
        decorators: [aliasADecorator]
      },
      aliasB: {
        service: serviceB,
        singleton: true,
        tags: ['tag1', 'tag2']
      },
      aliasC: {
        service: serviceC,
        listeners: {
            resolved: [aliasCListener]
          }
      }
    },
    listeners: {
      resolved: [globalListener]
    },
    decorators: [globalDecorator]
  }

Merging different Flask configuration objects

(WIP)

Contributing

Consider contributing opening an issue or opening a pull request.

License

Flask is open-sourced software licensed under the MIT license

Special thanks

To Symfony and its IOC Container, that it obviously inspired me to build this.

1.3.0

8 years ago

1.2.0

8 years ago

1.1.1

8 years ago

1.1.0

8 years ago

1.0.3

8 years ago

1.0.2

8 years ago

1.0.1

8 years ago

1.0.0

8 years ago