Introduction

Introduction

The componentManager is a small UMD module that manages instances within your application. The main purpose of the componentManager is to help decouple large scale Backbone applications by letting it handle instantiation and disposal of components depending on different filters.

A common approach when building a Backbone application is to have a router that decides which page views to create when the url changes. Then the page create instances of the components that should be rendered on that page, and the components might create subcomponents and so on. The problem with this approach is that, as the application grows, components and pages easily gets tightly coupled (the page might add eventlisteners to its components, pass data back and forth between instances and the page it self, etc.) and suddenly it is almost impossible to reuse a component in another part of the application.

With the componentManager you can just call the refresh method and it will add the components in the correct place for you. No need to manually create instances on different pages, actually no need to have pages at all.

The componentManager lets you configure componentDefinitions and instanceDefinitions for your application and then creates instances and adds them to the DOM whenever they are needed. All you have to do is call the refresh method with your filter. When a instance matches the filter it will be created and if not (if it previously was created) it will be properly disposed.

In the componentManager each instanceDefinition can have their own defined urlPatterns (routes) and other filters and conditions to decide when to create or dispose instances.

By doing this your application can be smaller and cleaner. It makes it a lot easier to build reusable components that does not get tangled up in each other or the application logic itself. This will also make it easier to scale and maintain your application over time.

To see the different features and filters in action go to the examples page or for a more complex example see the example app.

Dependencies

The componentManager depends on Backbone, Underscore and jQuery. It also plays well with the Vigorjs framework but it does not depend on it.

Dependencies:

Plays well with:

Getting started

The componentManager is a simple way to handle the life cycle of instances of components within your application. By adding a settings object containing definitions of components and instances of those components the componentManager knows when and where to create and add the instances. All you need to do is call the refresh method with a filter that informs the componentManager of the state of the application.

The most common use case would be to hook up the componentManager to the router and call refresh with a url filter every time the url changes. Or to create your own filter model and call refresh on the componentManager whenever any of the filters changes.

When you call refresh with a filter the componentManager will filter out any matching instanceDefinitions and create an instance of the componentDefinition that the instanceDefinition is referencing.

As a part of the filtering process the componentManager checks that the componentArea that the instanceDefinition targets is available, if not the instance will not be created.

See the simple example below (in this example the component will be required from the path 'components/hello-world', too see how to do a AMD or CommonJS setup see the AMD example or CommonJS example).

The componentArea in the markup

<div class="my-app">
  <section class="component-area--main"></section>
</div>

Initialize the componentManager with the needed settings:

define(function (require) {
  'use strict';

  var Backbone = require('backbone'),
      componentManager = require('vigor').componentManager,
      Router = require('./Router');

  componentManager.initialize({
    context: '.my-app',
    componentSettings: {
      components: [{
        id: 'hello-world-component',
        src: 'components/hello-world'
      }],
      instances: [{
        id: 'hello-world-instance',
        componentId: 'hello-world-component',
        targetName: '.component-area--main',
        urlPattern: 'hello-world'
      }]
    }
  });

  new Router();
  Backbone.history.start();
});

Hook up router to call refresh whenever the url updates.

define (function (require) {
  'use strict';
  var
    Backbone = require('backbone'),
    componentManager = require('vigor').componentManager,
    Router = Backbone.Router.extend({
      routes: {
        '*action': '_onAllRoutes',
        '*notFound': '_onAllRoutes'
      },

      _onAllRoutes: function () {
        Vigor.componentManager.refresh({
          url: Backbone.history.fragment
        });
      }
  });
  return Router;
});

Our simple example component (to learn more about components view the Components section below):


define (function (require) {
  'use strict';
  var HelloWorld
      Backbone = require('backbone');

  HelloWorld = Backbone.View.extend({

    render: function () {
      this.$el.html('Hello World!');
    },

    dispose: function () {
      this.remove();
    }

  });

  return HelloWorld;
});

After setting this up your hello-world component will be required in and instantiated whenever you go to the url 'http://yourdevserver/#hello-world'. If you go to some other url that instance will be disposed and removed from the DOM.

To scale this up is basically just to add your layout and expand your settings object with more componentDefinitions and instanceDefinitions. To see a more advanced example see the Example app.

To see it in action go to the hello world example.

Components

The componentManager main purpose is to create and dispose instances of components. In this case a component is typically a View of some sort, either a Backbone.View or a view or class that exposes the interface of a larger component (see the example app for examples of more complex components).

There are some required properties and methods that needs to be exposed and some optional methods that will be called by the componentManager if they exists, see below.

Property Description
$el jQuery object (required) A jQuery object containing a reference to the main DOM element of the component.
render method (required) Typically the render method should update the this.$el element with the rendered state of the component
dispose method (required) The dispose method should clean up and remove the component. Typically it would remove all event listeners, variables and elements used within the component.
preRender method (optional) This method will be called before render if it is defined.
postRender method (optional) This method will be called after render if it is defined.
delegateEvents method (optional)

Components might get reparented if you remove parent DOM elements without disposing the instance and add a new DOM element with the same class and call refresh on the componentManager.

In that case the existing instance would be added to the new DOM element and if the instance have a delegateEvents method it would be called to readd event listeners that was tied to the previous element.

This scenario could in most cases be avoided by not removing componentAreas without first removing the active instances in the componentManager by calling refresh with the new filter.

See delegateEvents on Backbone.Views.

receiveMessage method (optional) This method will be called if you use the postMessageToInstance method on the componentManager. It will receive a message (can be anything) as argument.

A minimal component could be this:

var ExampleComponent = Backbone.View.extend({
  render: function () {
    this.$el.html('hello world');
  },

  dispose: function () {
    this.remove();
  }
});

Or this:

var ExampleComponent = function () {
  this.$el = $('<div class="my-component"/>');
};

ExampleComponent.prototype.render = function () {
  this.$el.html('hello world');
};

ExampleComponent.prototype.dispose = function () {
  this.$el.remove();
  this.$el = undefined;
};

Or this:

var ExampleComponent = Backbone.View.extend({
  template: _.template("<button class='hello-btn'>hello: <%= name %></button>");
  events: {
    "click .hello-btn": '_onHelloBtnClick'
  },

  initialize: function () {
    this.listenTo(this.model, 'change', _.bind(this.render, this));
  },

  preRender: function () {
    this.$el.addClass('transition');
  },

  render: function () {
    this.$el.html(this.template(this.model.toJSON()));
  },

  postRender: function () {
    this.$el.removeClass('transition');
  },

  dispose: function () {
    this.remove();
  },

  _onHelloBtnClick: function () {
    alert('hello');
  }
});

Or anything you like as long as it exposes the required properties and methods above.

It's recommend that you group all files that belong to a component under the same folder, ex like this:

components
│
│─── bar-chart
│    │   BarChartView.js
│    │   BarChartModel.js
│    │   main.js
│    │
│    ├─── css
│    │   │   main.scss
│    │   │   ...
│    │
│    └─── templates
│        bar-chart-template.html
│        │   ...
│
│
│─── calendar
│    │   CalendarView.js
│    │   CalendarModel.js
│    │   main.js
│    │
│    ├─── css
│    │   │   main.scss
│    │   │   ...
│    │
│    └─── templates
│        calendar-template.html
│        │   ...

Component Areas

Component areas are the DOM elements where instances of components will be placed. The component areas can be any element but they have to have a class that matches the class defined in your instanceDefinitions targetName property plus the targetPrefix ex. component-area--main where component-area is the default prefix.

The prefix can be changed to anything you like by setting the targetPrefix property on the settings object to the string you would like to use as a prefix.

See example below.

settings = {
  targetPrefix: 'my-component-area',
  componentSettings: {
    components: [
      {
        id: 'my-component',
        src: 'components/my-component'
      }
    ],
    instances: [
      {
        id: 'instance-1',
        componentId: 'my-component',
        targetName: '.my-component-area--main',
        urlPattern: 'foo/:bar'
      }
    ]
  }
}

componentManager.initialize(settings);

or using the alternative structure it would look like this:

settings = {
  targetPrefix: 'my-component-area',
  componentSettings: {
    components: [
      {
        id: 'my-component',
        src: 'components/my-component'
      }
    ],
    targets: {
      main: [
        {
          id: 'instance-1',
          componentId: 'my-component',
          urlPattern: 'foo/:bar'
        }
      ]
    }
  }
}

componentManager.initialize(settings);

In the examples above the prefix is set to 'my-component-area' which means that all DOM elements that should receive instances by the componentManager should have class names that starts with 'my-component-area', ex: class="my-component-area--main" or class="my-component-area--sidebar" etc.

<div class="my-component-area--main"></div>

Component areas does not have to be empty, they can contain elements that are not part of any instantiated components. If the order of these elements in relation to the created instance elements is important they should have an data-order attribute. Instances in the component manager gets the order attribute by setting the order property to the desired value.

See example below:

settings = {
  targetPrefix: 'my-component-area',
  componentSettings: {
    components: [
      {
        id: 'my-component',
        src: 'components/my-component'
      }
    ],
    instances: [
      {
        id: 'instance-1',
        componentId: 'my-component',
        targetName: '.my-component-area--main',
        order: 3,
        urlPattern: 'foo/:bar'
      }
    ]
  }
}

componentManager.initialize(settings);

Markup

<div class="my-component-area--main">
  <div class="im-not-a-component im-a-static-element" data-order="1"></div>
  <div class="im-not-a-component im-a-static-element" data-order="2"></div>
  <!-- instance-1 will end up here  -->
  <div class="im-not-a-component im-a-static-element" data-order="4"></div>
</div>

Note that if multiple elements have the same order value they will end up after each other in the order they was added.

ComponentDefinitions

ComponentDefinitions or componentDefinitionModels are the models that contains the definition of each component created by the componentManager. It stores a reference to the class to create instances from and also the conditions (if any) that should apply to create any instances of that class.

To define your componentDefinitions you add your componentDefinition objects to the components array in the componentSettings.

The only required properties for a componentDefinition is id and src. But a componentDefinition could also contain the default properties args, conditions and maxShowCount. All properties are undefined by default. Se the description for each below:

Example of an componentDefinition object.

componentDefinition = {
  id: 'my-component',
  src: 'components/my-component',
  args: {
    myArg: 'myArgValue'
  },
  conditions: ['isLoggedIn', 'hasNewMessage'],
  maxShowCount: 3
}

ComponentDefinition Properties

Property Description
id String (required) The id property is required. It should be a uniqe identifier for the componentDefinition and it should be a string. InstanceDefinitions will reference this id to know what class to create the instance from.
src String / Function (required)

The src property is also required and can be either a string or a constructor function. If it is a string it should either be a url (ex: 'http://www.google.com'), a path that can be required by a AMD or CommonJS module loader (ex: 'components/my-component') or a namespace path to the class starting from the window object, leaving out the window object it self (ex: src: 'app.components.Chart').

If you are using a AMD ocr CommonJS module loader the string will always be required unless its a url. It will not try to find the class on the window object even if you send in a string like 'app.components.Chart'.

If the string is a url (ex. 'http://www.google.com') the component manager will use the IframeComponent as a class for any instanceDefinition referencing this componentDefinition.

args Object

The args property is an object containing any key value pairs you like. When an instanceDefinition reference this componentDefinition that instanceDefinitions args will extend this args object, it will then be passed as an argument to the created instance.

This means that all instanceDefinitions referencing a componentDefinition with an args object will get that object passed to its instance upon instantiation.

Each instanceDefinitions args object may override properties on the componentDefinitions args object.

conditions String / Array / Function

A condition for a componentDefinition or instanceDefinition should be a function returning true or false. One or multiple conditions can be used to help determine if an instance of the component should be created or not.

Instead of a function you may also use a string that will be used as a key for a condition registered in the conditions property of the componentSettings object (or conditions added using the addConditions method).

You can mix both of these methods and pass an array containing functions or strings or both. All conditions will have to return true to have the instance created.

If the instanceDefinition have conditions of its own both the conditions of the componentDefinition and the instanceDefinition will have to return true for the instance to be created.

Note that conditions defined on the componentDefinition will apply to all instances of that component.

maxShowCount Number The property maxShowCount should be a number if defined. If used it will limit the number of times a instance of that component may be created. For an example you could set it to 1 if you want to display a component only one time - even if other filters pass.

Example

Here is an example of an componentDefinition:

  {
    id: 'my-chart', //a unique string
    src: 'components/chart', // path to be required
    args: {
      type: 'bar-chart' // arguments to pass to instance
    },
    conditions: ['correct-width', function (..) {}], // conditions for when to allow instance to be created
    maxShowCount: 1 // instances of this component may only be created/shown once
  }

and this is how it would look in the settings object:

settings = {
  componentSettings: {
    conditions: {
      ...
    },
    components: [
      {
        id: 'my-chart'
        src: 'components/chart'
        args: {
          type: 'bar-chart'
        }
        conditions: ['correct-width', function (..) {}]
        maxShowCount: 1
      }
    ],
    instances: [
      ...
    ]
  }
}

Custom Properties

In addition to the default properties you can add any properties you like to a componentDefinition. Custom properties can then be used to refine the filter and target specific instanceDefinitions belonging to the componentDefinition that has the custom property. The custom properties would then also have to be used when creating the filter which would be passed to the refresh method. See example below.

componentSettings: {
  components: [
    {
      id: 'my-component',
      src: 'components/chart',
      myCustomProperty: 'componentVal'
    }
  ],
  instances: [
    {
      id: 'my-instance',
      componentId: 'my-component',
      targetName: 'body'
    }
  ]
}

In the example above the custom property myCustomProperty is set on the componentDefinition with the value 'componentVal'. The filter below would create a new instance of the component 'my-component' using the information from the instanceDefinition 'my-instance'.

componentManager.refresh({
  myCustomProperty: 'componentVal'
});

Custom properties on the componentDefinition may be overridden by custom properties on a intanceDefinition that belongs to that componentDefinition.

InstanceDefinitions

InstanceDefinitions or instanceDefinitionModels defines an instance of a componentDefinition. That means that one componentDefinition may have multiple instanceDefinitions. The connection between them is done with an id reference from each instanceDefinition to its componentDefinition's id. Each instanceDefinition have to have the property componentId with the id of the componentDefinition (which holds the class to create the instance from).

To define your instanceDefinitions you add your instanceDefinition objects to the instance array in the componentSettings or use the alternative structure.

The only required properties for a instanceDefinition is id and componentId, but there are many more properties which can be used to pass arguments, specify instance order and behavior and of course properties to help out with filtering. See each property and their descriptions below:

Example of an instanceDefinition object.

instanceDefinition = {
  id: 'my-instance-definition',
  componentId: 'my-component-definition-id',
  args: {
    myArg: 'myArgValue'
  },
  order: 1,
  targetName: '.component-area--header',
  reInstantiate: true,

  filterString: 'my-instance',
  includeIfFilterStringMatches: 'lang=en_GB',
  excludeIfFilterStringMatches: 'lang=sv_SE',
  conditions: ['isLoggedIn'],
  maxShowCount: 3,
  urlPattern: 'global'
}

InstanceDefinition Properties

Public properties

These properties are used to decide what component to create the instance from, where to add it and what arguments to pass to it. See the descriptions for details.

Property Description
id String (required) The id property is required. It should be a unique identifier for the instanceDefinition and it should be a string.
componentId String (required)

The componentId property is required. It should be the uniqe identifier for the componentDefinition to create instances from and it should be a string.

This property links one or multiple instanceDefinitions to a componentDefinition.

args Object

The args property is an object containing any key value pairs you like. This args object will be merged to the args object on the componentDefinition (if any) and override any properties that exists in both objects.

The merged args object will then be passed as an argument to the created instance constructor.

order Number / 'top' / 'bottom'

The order property should be a Number (int) ex: order: 4 or the string 'top' or 'bottom'.

The order property specifies in what order to add instances to the DOM. The order property is also read back from the DOM when adding instances so it will order instances around elements that is not handled by the componentManager as long as they have a data-order="" attribute set on the element.

If for example you specify the order to 4 on your instance definition and you have a static element already in the DOM with the data attribute data-order="3" your instance will be added after that element.

If you have set order to 'top' the element will be added first in the targeted component-area. If you instead set it to 'bottom' it will be added last in the targeted component-area.

See the example: Reorder components for more information.

targetName String / jQuery object (required - depending on structure in the settingsObject)

If the targetName is a string is a string it should be a class selector like '.component-area--sidebar' and it should have the prefix that you defined in your settings object (default prefix is 'component-area'). If the prefix is not present it will be added for you so if you set the targetName to '.header' it will be changed to be '.component-area--header'. You would of course have to add the class 'component-area--header' to your markup your self.

The targetName property could also be a jquey object like $('.my-element')

The targetName is required if you use the default setup with an instances array in your componentSettings object (not using the alternative structure).

The targetName property is not needed if you are using the alternative sturcture for your componentSettings object since it will be parsed from the object keys.

reInstantiate Boolean

The reInstantiate flag is a boolean which defaults to false. Setting this flag to true will cause the instance to be reInstantiated when matching two different filters after each other.

If you for an example pass a filter with the url property set to 'foo/1' and your instanceDefinition have the urlPattern 'foo/:id' your component would pass the filter and be instantiated and added to the DOM. If you then do another refresh with the url set to 'foo/2' the default behavior would be not to reInstantiate the instance since it's already created, rendered and added to the DOM. But with this flag set to true it will force the instance to be recreated and readded whenever the filter change (and it passes the filter).

To see this in action see the Filter by url example.

Filter related properties on the instanceDefinition

These properties are used to decide if the instance passes the filter or not.

Property Description
filterString String The filterString property is a string that you can match against the regexp you define in your filter object (by adding your regexp to one of these filter properties: includeIfMatch, excludeIfMatch, hasToMatch, cantMatch).
includeIfFilterStringMatches String / Regexp The includeIfFilterStringMatches property is a string or a regexp that will be matched against the filterString defined on the filter object. If the regexp matches the filterString the instance will be created. If includeIfFilterStringMatches is defined and does not match the filterString the instanceDefinition will not pass the filter.
excludeIfFilterStringMatches String / Regexp The excludeIfFilterStringMatches property is a string or a regexp that will be matched against the filterString defined on the filter object. If the regexp matches filterString in the filter object the instance will be excluded.
conditions Array / Function / String

A condition for a componentDefinition or instanceDefinition should be a function returning true or false. One or multiple conditions can be used to help determine if an instance of the component should be created or not.

Instead of a function you may also use a string that will be used as a key for a condition registered in the conditions property of the componentSettings object (or conditions added using the addConditions method).

You can mix both of these methods and pass an array containing functions or strings or both. All conditions will have to return true to have the instance created.

If the componentDefinition that the instanceDefinition references have conditions of its own both the conditions of the instanceDefinition and the componentDefinition will have to return true for the instance to be created.

excludeIfFilterStringMatches String / Regexp The excludeIfFilterStringMatches property is a string or a regexp that will be matched against the filterString defined on the filter object. If the regexp matches filterString in the filter object the instance will be excluded.
maxShowCount Number

The property maxShowCount should be a number if defined. The instanceDefinitions maxShowCount overrides the componentDefinitions maxShowCount property.

If used it will limit the number of times a instance of that component may be created. For an example you could set it to 1 if you want to display a component only one time - even if other filters pass.

urlPattern String / Array

The urlPattern property should be a string in the form of a Backbone url pattern / route or an array containing multiple url patterns.

This pattern will be used when filtering with the url property on the filter object that is passed to the refresh method. If your instanceDefinitions urlPattern matches the url (and all other filters pass) your instance will be created.

By passing multiple urlPatterns you can have the same instance active across multiple urls without reinstantiation. For an example: if you set the urlPattern to ['home', 'products/:id'] your instance would be created once when matching 'home' and then still keep the same instance active when navigating to any page that matches the 'products/:id' pattern (products/1, products/2 etc.)

You can also set the urlPattern to 'global' to have it match any url. This is useful if you want an instance of your component to always stay on page, like a header or a main menu.

See the Filter by url example.

Custom Properties

In addition to the default properties you can add any properties you like to a instanceDefinition. These custom properties can then be used to refine the filter and target specific instanceDefinitions. The custom properties would then also have to be used when creating the filter that would be passed to the refresh method. See example below.

Custom properties on a instanceDefinition can override custom properties on it's componentDefinition.

componentSettings: {
  components: [
    {
      id: 'my-component',
      src: 'components/chart',
      myCustomProperty: 'componentVal'
    }
  ],
  instances: [
    {
      id: 'my-instance-1',
      componentId: 'my-component',
      targetName: 'body'
      myCustomProperty: 'instanceVal'
    }
    {
      id: 'my-instance-2',
      componentId: 'my-component',
      targetName: 'body'
    }
  ]
}

In the example above the custom property myCustomProperty is set on the componentDefinition with the value 'componentVal'. It's then overridden by the first instance which sets the value to 'instanceVal'. So by using the filter below the second instance ('my-instance-2') would be created.

componentManager.refresh({
  myCustomProperty: 'componentVal'
});

And this filter would create an instance of the first instanceDefinition. If the custom property on the componentDefinition would not have been overridden by the first instanceDefinition both instances would have been created by this filter.

componentManager.refresh({
  myCustomProperty: 'instanceVal'
});

See the section 'Custom properties' on componentDefinitions for more information and examples.

Filter

The componentManager works like a funnel, you start by defining components (componentDefinitions) and then a list of instances (instanceDefinitions) of those components. Each componentDefinition and each instanceDefinition may have different properties (ex. condition methods, filterString, showCount, urlPatterns etc.) that will be used to decide if it makes it through the funnel.

The filter object can contain the following properties:

Property Description
url String (optional)

The url property can be any valid url fragment (hash part of the url - whats returned by Backbone.history.fragment). It will be matched against the urlPattern property on any instanceDefinition that has it defined. Ex: the url 'articles/2010/12/1' would match the urlPattern: 'articles/:section(/:subsection)(/:id)'.

See the Filter by url examples.

filterString String (optional)

The filterString can be any string and it is intended to be used together with the instanceDefinition properties includeIfFilterStringMatches (string / regexp) and excludeIfFilterStringMatches (string / regexp).

By passing a filterString together with your filter each instanceDefinition that has includeIfFilterStringMatches will check if that string or regular expression matches the filterString in the filter. If it matches the instance will be created (assuming all other filters passes), if it does not match it will not be created.

The excludeIfFilterStringMatches works the opposite way, if it the string or regular expression matches the filterString in the filter the instance will be excluded - even if other filters passes.

See the Filter by string examples.

includeIfMatch String / Regexp (optional)

The includeIfMatch property of the filter should be a string or a regular expression and it is intended to use on filterStrings defined on instanceDefinitions (note that this is not the same filterString as the one described above).

If a instanceDefiniton has a filterString and that string matches the string or regexp defined in the includeIfMatch property of the filter a instance will be created and added to the DOM.

This filter property also allows the filterString on instanceDefinitions to be undefined. If they are undefined they will still pass this filter.

See the Filter by string examples.

excludeIfMatch String / Regexp (optional)

The excludeIfMatch property of the filter should be a string or a regular expression and it is intended to use on filterStrings defined on instanceDefinitions.

If a instanceDefiniton has a filterString and that string matches the string or regexp defined in the excludeIfMatch property of the filter a instance will be excluded and will not be added to the DOM.

This filter property also allows the filterString on instanceDefinitions to be undefined. If they are undefined they will still pass this filter.

This is intended to use in combination with other filters, ex: if a instanceDefinition has a urlPattern that passes but a filterString that says that it is in a specific language (say filterString: 'lang=en_GB') and you want to exclude that instance because the user is using another language. That could then be achieved by setting excludeIfMatch to 'lang=en_GB'.

See the Filter by string examples.

hasToMatch String / Regexp (optional)

This filter property works in the same way as includeIfMatch, you set it to a string or regular expression that should match the filterString on one or more instanceDefinitions.

The difference between hasToMatch and includeIfMatch is that this property does not allow the filterString on instanceDefinitions to be undefined. If they are undefined they will fail this filter.

See the Filter by string examples.

cantMatch String / Regexp (optional)

This filter property works in the same way as excludeIfMatch, you set it to a string or regular expression that should match the filterString on one or more instanceDefinitions, if it matches the instancDefiniton will be excluded.

This difference between cantMatch and excludeIfMatch is that this filter property does not allow the filterString on instanceDefinitions to be undefined. If they are undefined they will fail this filter (and in this case not be excluded).

See the Filter by string examples.

options Object (optional)

The options object can contain five different properties: add, remove, merge, invert and forceFilterStringMatching (all options have boolean values).

//defaults:
  options: {
    add: true,
    remove: true,
    merge: true,
    invert: false,
    forceFilterStringMatching: false
  }

The add property determines if matching instances should be added to the DOM or not.

The remove property determines if non matching instances should be removed from the DOM or not.

As an example: By setting add to false and remove to true and then call refresh with your filter the componentManager will remove instances that does not match the filter but it will not add instances that does match the filter.

And of course the opposite would happen if you set add to true and remove to false.

The merge property determines if updates to the instanceDefinition should be allowed or not. Ex if you change the order property on a instanceDefinition and set merge to false the change will be ignored.

The invert property will (if set to true) create and add instances of all instanceDefinitions that does not match the filter (the opposite of the default behavior).

The forceFilterStringMatching property will (if set to true) make all string filters be exclusive, instanceDefinitions that does not match the filter and instanceDefinitons that has an undefined filterString will not be created. Only instanceDefinitions with a direct match on the string filter will be created (even though other filters may pass).

See the Filter options for more examples.

To actually filter on those properties you call the refresh method and pass a filter object. Calling the method without the filter will create all instances at once - assuming that their targets are available.

The filtering process is inclusive, it always tries to include instanceDefinitions unless some filter fail. That means that if a instanceDefinition does not have the necessary property to match a certain filter it will, by default, still pass the filter (ex if you filter using the url property and a instanceDefinition does not have a urlPattern the url filter would be ignored and the instance would be created).

Note that all properties in the filter are optional and combinable in any way.

Take a look at the different examples below:

In the example below the the instance would be created since the instanceDefinitions urlPattern does match the url.

var componentSettings, filter;

componentSettings = {
  components: [
    {
      id: 'hello-world-component',
      src: 'components/hello-world'
    }
  ],
  instances: [
    {
      id: 'hello-world-instance',
      componentId: 'hello-world-component',
      targetName: 'body',
      urlPattern: 'foo/:id'
    }
  ]
}

filter = {
  url: 'foo/1'
};

componentManager.initialize(componentSettings);
componentManager.refresh(filter);

In the example below the the instance would not be created since the instanceDefinitions urlPattern does not match the url.

var componentSettings, filter;

componentSettings = {
  components: [
    {
      id: 'hello-world-component',
      src: 'components/hello-world'
    }
  ],
  instances: [
    {
      id: 'hello-world-instance',
      componentId: 'hello-world-component',
      targetName: 'body',
      urlPattern: 'foo/:id'
    }
  ]
}

filter = {
  url: 'bar/1'
};

componentManager.initialize(componentSettings);
componentManager.refresh(filter);

In the example below the the instance would still be created since the instanceDefinitions does not have a urlPattern defined and the only filter property defined is the url.

var componentSettings, filter;

componentSettings = {
  components: [
    {
      id: 'hello-world-component',
      src: 'components/hello-world'
    }
  ],
  instances: [
    {
      id: 'hello-world-instance',
      componentId: 'hello-world-component',
      targetName: 'body'
    }
  ]
}

filter = {
  url: 'foo/1'
};

componentManager.initialize(componentSettings);
componentManager.refresh(filter);

Custom properties

In addition to the default filter properties you can filter on any properties you like, these custom properties will be matched against custom properties defined on either componentDefinitions or instanceDefinitions (using _.isMatch). This could be useful if you for an example want to group instances together without using any of the other available filters.

To use custom filter properties its just to add them to the filter:

componentManager.refresh({
  myCustomProperty: 'componentVal'
});

This would then be matched with the same custom property on either a componentDefinition or a instanceDefinition.

See the 'Custom properties' sections under componentDefinitions and instanceDefinitions for more information and examples.

Settings

Settings

To get started with the componentManager you need to setup the settings object which you will pass to the initialize function - typically during bootstrap of your application.

There are a couple of different ways to structure the settings object and the most straight forward setup has the following structure (example below is using the default values):

settings = {
  context: 'body',
  componentClassName: 'vigor-component',
  targetPrefix: 'component-area',
  listenForMessages: false,
  whitelistedOrigins: 'http://localhost:3000',
  componentSettings: {
    conditions: {},
    components: [],
    instances: []
  }
}

The settings object can contain the five properties above (see Other settings for a specification of these properties) and the componentSettings object. The componentSettings object can contain the conditions object, the components array and the instances array. None of these are mandatory either but without components and instances the componentManager wont do much (they can be added on the fly later if you want/need a dynamic setup).

The conditions object is optional but if you use it it should contain any methods that you like to use to help filter out instances. These methods should return true or false. To use the conditions you reference the methods key in the conditions object from a componentDefinition or an instanceDefinitions conditions array.

The components array should contain one or multiple componentDefinitions and the instances array (or targets object - see alternative structure below) should contain one or multiple instanceDefinitions.

And here is an example of how it could look with some content:

settings = {
  context: '.my-app',
  componentClassName: 'my-component',
  targetPrefix: 'my-component-area',
  componentSettings: {
    conditions: {
      isValWithinLimit: function (filter, args) {
        var limit = 400;
        return args.val < limit;
      }
    },

    components: [
      {
        id: 'my-component',
        src: MyComponent
      },
      {
        id: 'my-second-component',
        src: MySecondComponent
      }
    ],

    instances: [
      {
        id: 'instance-1',
        componentId: 'my-component',
        targetName: '.my-component-area--header',
        urlPattern: 'foo/:bar',
        order: 1,
        args: {
          val: this.getVal()
        }
      },
      {
        id: 'instance-2',
        componentId: 'my-second-component',
        targetName: '.my-component-area--main',
        urlPattern: 'bar/:baz(/:qux)'
      }
    ]
  }
}

componentManager.initialize(settings);

Alternative structure

If you like to group your instances under their targets that is also possible by using the structure below. This structure does not allow you to pass the target selector for each instance your self which might be good if you are using this as a way for third party users to add components to your application (ex ads).

settings = {
  context: 'body',
  componentClassName: 'vigor-component',
  targetPrefix: 'component-area',
  listenForMessages: false,
  componentSettings: {
    conditions: {},
    components: [],
    targets: {}
  }
}

And here is an example of how this could look with some content:

settings = {
  context: '.my-app',
  componentClassName: 'my-component',
  targetPrefix: 'my-component-area',
  componentSettings: {
    conditions: {
      isValWithinLimit: function (filter, args) {
        var limit = 400;
        return args.val < limit;
      }
    },

    components: [
      {
        id: 'my-component',
        src: MyComponent
      },
      {
        id: 'my-second-component',
        src: MySecondComponent
      }
    ],
    targets: {
      header: [
        {
          id: 'instance-1',
          componentId: 'my-component',
          urlPattern: 'foo/:bar',
          order: 1,
          args: {
            val: this.getVal()
          }
        }
      ]
      main: [
        {
          id: 'instance-2',
          componentId: 'my-second-component',
          urlPattern: 'bar/:baz(/:qux)'
        }
      ]
    }
  }
}

componentManager.initialize(settings);

In this case each of the target keys would be used as a part the selector to use for all of the instanceDefinitions within that array. The other part of the selector would be the targetPrefix so in the examples above any instanceDefiniton that would be part of the array for "header" would have the targetName set to "component-area--header", for "main" it would be "component-area--main" and so on.

Skip defaults

If you don't want to change the defaults for context, componentClassName, targetPrefix and listenForMessages you pass in only the componentSettings part of the settings object:

componentSettings: {
  conditions: {},
  components: [],
  instances: []
}

and if you are not using any conditions you can skip that as well:

componentSettings: {
  components: [],
  instances: []
}

Conditions

Conditions are methods that you either set on a componentDefinition, an instanceDefinition or in the conditions object within the componentSettings object (see settings). These methods should return true or false and.

Condition methods will be passed the active filter and the args object on the instanceDefinition or on the componentDefinition depending on where the condition method is used.

If a condition is set on the componentDefinition and it returns false no instances of that componentDefinition will be created. If it instead is set on an instanceDefinition and it returns false only that instanceDefinition will not be created.

The conditions object in the componentSettings is to be used when you want to reuse the same method as a condition for multiple componentDefinitions or instanceDefinitions. You then add your condition methods to that object and reference it by its key when defining your componentDefinitions or instanceDefinitions.

See example below:

settings = {
  componentSettings: {
    conditions: {
      isLoggedIn: function (filter, args) {
        return args.user.isLoggedIn();
      },
      hasNewMessage: function (filter, args) {
        return args.user.hasNewMessage();
      }
    },
    components: [
      {
        id: 'user-profile-component',
        src: 'components/user-profile'
        conditions: 'isLoggedIn',
        args: {
          user: app.userModel;
        }
      },
      {
        id: 'message-alert-component',
        src: 'components/message-alert',
        conditions: ['isLoggedIn', 'hasNewMessage'],
        args: {
          user: app.userModel;
        }
      }
    ],
    instances: [
      {
        id: 'user-profile-instance',
        componentId: 'user-profile-component',
        targetName: 'body',
        urlPattern: 'global'
      },
      {
        id: 'message-alert-instance',
        componentId: 'message-alert-component',
        targetName: 'main',
        urlPattern: 'global'
      }
    ]
  }
}

In this example there are two methods defined in the conditions object: isLoggedIn and hasNewMessage. Both componentDefinition uses the 'isLoggedIn' condition which means that all instances of those two components will check to see if the user is logged in before being instantiated, if not they will not be created.

The in addition to the 'isLoggedIn' condition the 'message-alert-component' uses the 'hasNewMessage' condition and will therefore only be created if the user hasNewMessage method returns true.

Both instanceDefinitions ('user-profile-instance' and 'message-alert-instance') could in turn have more conditions in their conditions property. The value of this property could be a string (the key of a method registered in the conditions object), or a function or an array with both strings and functions.

See the componentDefinition and the instanceDefinition specifications. For more examples see the conditions example.

Other settings

The settings object can contain five properties except for the componentSettings object: context, componentClassName, targetPrefix, listenForMessages and whitelistedOrigins. See specifications and example below.

Property Description
context String / jQuery object

The context property of the settings object should be either a element selector as a string (ex. '#my-id' or '.my-class') or a jQuery object (ex $('.my-element')). The element will be used as context for the componentManager and all DOM related actions will be kept within that context.

The context defaults to 'body'.

componentClassName String

The componentClassName should be a string and it will be used as a class on each instance created by the componentManager.

The componentClassName defaults to 'vigor-component'.

targetPrefix String

The targetPrefix should be a string and it should prefix all component-areas that will receive instances by the componentManager. If you set your targetPrefix to be 'my-prefix' your component areas should have class names like 'my-prefix--header', 'my-prefix--main' etc.

The targetPrefix defaults to 'component-area'.

listenForMessages Boolean

The listenForMessages property is intended to be used when working with IframeComponents and cross-origin communication using the postMessage method.

By setting the listenForMessages to true the componentManager will start listening for incoming messages.

The listenForMessages defaults to false.

whitelistedOrigins String / Array

The whitelistedOrigins property is intended to be used when working with IframeComponents and cross-origin communication using the postMessage method.

Only messages sent from origins that are registered in the whitelistedOrigins array (or string if you only allow communication from one origin) will be picked up by the componentManager.

In addition to setting the whitelistedOrigins property each message sent with the postMessage method must have the property 'recipient' set to 'vigorjs.componentmanager' to be forwarded to instances.

The whitelistedOrigins defaults to http://localhost:3000.

Example

settings = {
  context: '.my-app',
  componentClassName: 'my-component',
  targetPrefix: 'my-component-area',
  listenForMessages: true,
  componentSettings: {
    conditions: {...},
    components: [...],
    instances: [...]
  }
}

Public methods

initalize

The componentManagers initialize method registers componentDefinitons and instanceDefinitions and should be called with the settings object before you can start use the componentManager.

Example (using the alternative structure):

settings = {
  componentSettings: {
    components: [
      {
        id: 'my-component',
        src: 'components/my-component'
      }
    ],
    targets: {
      main: [
        {
          id: 'instance-1',
          componentId: 'my-component',
          urlPattern: 'foo/:bar'
        }
      ]
    }
  }
}

componentManager.initialize(settings);

Initialize returns the componentManager instance.

updateSettings

The updateSettings method updates the componentManager with new settings after initialization. It should be called with the settings object.

Example (using the alternative structure):

settings = {
  componentSettings: {
    components: [
      {
        id: 'my-component',
        src: 'components/my-component'
      }
    ],
    targets: {
      main: [
        {
          id: 'instance-1',
          componentId: 'my-component',
          urlPattern: 'foo/:bar'
        }
      ]
    }
  }
}

componentManager.updateSettings(settings);

refresh

The refresh method can be called with or without a filter.

When calling refresh with a filter all instances that doesn't match that filter will be disposed and removed from the DOM and all instances that does match the filter will be added.

If calling the refresh method without a filter all instances will be created and added to the DOM (assuming their component-areas are available).

Example:

componentManager.updateSettings({
  url: 'foo/1'
  options: {
    remove: false
  }
});

The refresh method returns a promise that will be resolved (after any asynchronous components has been loaded) with an object containing:

componentManager.refresh(filter).then(function (returnData) {});

// The returned data will contain the following properties:
{
  filter: {...}, // The current filter (object)
  activeInstances: [...], // all active instances (array)
  activeInstanceDefinitions: [...], // all activeInstanceDefinitions (array)
  lastChangedInstances: [...], // the last changed instances (array)
  lastChangedInstanceDefinitions: [...], // the last changed instanceDefinitions (array)
}

serialize

The serialize method will return the current state of the componentManagers settings object as a string that then can be read back by the parse method.

The serialized string will not contain any applied filters but it will contain the original settings and any conditions, componentDefinitions, instanceDefinitions or other settings that have been changed/added/removed on the fly since initialization.

var serializedSettings = componentManager.serialize();

Returns the current state of the componentManagers settings object as a string.

See the order/reorder example.

parse

The parse method takes two parameters: a serialized settings object (created by calling componentManager.serialize()) and a boolean to decide if it should update the current settings or just return a parsed settings object.

If you set the updateSetting boolean to true, it will update the current instance of the componentManager with the parsed settings object created from the serialized string (it will return the settings object). If it is set to false (it defaults to false) it will just return an object containing the parsed settings.

var serializedSettings = componentManager.serialize();
    updateSettings = true,
    parsedSettings = componentManager.parse(serializedSettings, updateSettings);

Returns the parsed settings object.

See the order/reorder example.

clear

The clear method removes all registered conditions, componentDefinitions and instanceDefinitions. It will also clear any active filters and restore componentClassName, targetPrefix to their default values. If a context has been registered then that will be cleared out as well.

When clearing the componentManager all active instances will be disposed.

Returns the componentManager instance.

dispose

The dispose method calls clear which clears out all data and stored settings. In addition it will unset all internal models and collections it will also call removeListeners which removes all eventlisteners.

When disposing the componentManager all active instances will also be disposed.

addListeners

The addListeners method will wire up the componentManager to start listening for changes on conditions, componentDefinitions, instanceDefinitions and act upon those changes. It will also start triggering its own events.

This method will be called when initializing the componentManager, so you do not need to run this method unless you previously have removed the event listeners using removeListeners.

The addlisteners method returns the componentManager instance.

addConditions

The addConditions method takes an object containing the condition methods to run and a silent flag that determines if the change should trigger an event or not (the silent flag defaults to false). The passed object will be merged with the conditions object registered during initialization.

To run these method you reference their keys in either the componentDefinitions or instanceDefinitions conditions property.

Each condition method will receive the active filter and the args object of the componentDefinition or instanceDefinition if it is defined.

Example:

var silent = true,
    conditions = {
      isLoggedIn: function (filter, args) {
        return args.user.isLoggedIn();
      },
      hasNewMessage: function (filter, args) {
        return args.user.hasNewMessage();
      }
    }

componentManager.addConditions(conditions, silent);

To use the methods reference them in the componentDefinitions or instanceDefinitions like below:

  componentSettings: {
    components: [
      {
        id: 'my-component',
        src: 'components/my-component'
      }
    ],

    instances: [
      {
        id: 'instance-1',
        componentId: 'my-component',
        targetName: 'body',
        urlPattern: 'foo/:bar',
        conditions: ['isLoggedIn', 'hasNewMessage'],
        args: {
          user: app.userModel
        }
      }
    ]
  }

For more info see conditions documentation and the conditions example.

The addConditions method returns the componentManager instance.

addComponentDefinitions

The addComponentDefinitions method is used to add componentDefinitions on the fly after initialization. It takes an componentDefinition object or an array of componentDefinition objects.

When adding new componentDefinitions the componentManager will immediately do refresh with the currently applied filter to add any new instances to the DOM.

componentManager.addComponentDefinitions([
  {
    id: 'my-first-component',
    src: 'components/my-first-component'
  },
  {
    id: 'my-second-component',
    src: 'components/my-second-component',
    conditions: 'isLoggedIn'
  }
]);

addInstanceDefinitions

The addInstanceDefinitions method is used to add instanceDefinitions on the fly after initialization. It takes an instanceDefinition object or an array of instanceDefinition objects.

When adding new instanceDefinitions the componentManager will immediately do refresh with the currently applied filter to add any new instances to the DOM.

componentManager.addInstanceDefinitions([
  {
    id: 'instance-1',
    componentId: 'my-first-component',
    targetName: '.my-component-area--header',
    urlPattern: 'foo/:bar'
  },
  {
    id: 'instance-2',
    componentId: 'my-second-component',
    targetName: '.my-component-area--header',
    urlPattern: 'bar/:foo'
  }
]);

updateComponentDefinitions

The updateComponentDefinitions method is used to update existing componentDefinitions on the fly after initialization. It actually calls addComponentDefinitions so it works exactly the same, if you pass an object with an id that is already registered in the componentManager it will update that object with the properties in the passed object.

When updating componentDefinitions the componentManager will immediately do refresh with the currently applied filter to add/readd any changed instances to the DOM.

componentManager.updateComponentDefinitions([
  {
    id: 'my-first-component',
    src: 'components/my-first-component'
  },
  {
    id: 'my-second-component',
    src: 'components/my-second-component',
    conditions: 'isLoggedIn'
  }
]);

updateInstanceDefinitions

The updateInstanceDefinitions method is used to update existing instanceDefinitions on the fly after initialization. It actually calls addInstanceDefinitions so it works exactly the same, if you pass an object with an id that is already registered in the componentManager it will update that object with the properties in the passed object.

When updating instanceDefinitions the componentManager will immediately do refresh with the currently applied filter to add/readd any changed instances to the DOM.

componentManager.updateInstanceDefinitions([
  {
    id: 'instance-1',
    componentId: 'my-first-component',
    targetName: '.my-component-area--header',
    urlPattern: 'foo/:bar'
  },
  {
    id: 'instance-2',
    componentId: 'my-second-component',
    targetName: '.my-component-area--header',
    urlPattern: 'bar/:foo'
  }
]);

removeComponentDefinition

The removeComponentDefinition method takes a componentDefinition id or an array with component ids as argument. It will remove the componentDefinition with the passed id and all instenceDefinitions that are referencing that componentDefinition.

componentManager.removeComponentDefinition('my-component');

The removeComponentDefinition method returns the componentManager instance.

removeInstanceDefinition

The removeInstanceDefinition method takes a instanceDefintion id or an array with instanceDefintion ids as argument. It will remove the instanceDefintion with the passed id.

componentManager.removeInstanceDefinition('my-instance');

The removeInstanceDefinition method returns the componentManager instance.

removeListeners

The removeListeners method will remove all event listeners used in the componentManager, it will also stop the componentManager triggering events.

The removeListeners method returns the componentManager instance.

setContext

The setContext method takes a DOM element selector or a jQuery object as first argument. The element with the passed selector will be used as context, if no argument is passed it will default to use the body as context.

When updating the context on the fly the componentManager will dispose all active instances and try to recreate and add them within the new context.

componentManager.setContext($('.my-app'));

or

componentManager.setContext('.my-app');

The setContext method returns the componentManager instance.

setComponentClassName

The setComponentClassName method sets the class name (or class names) that will be added to each instance created by the componentManager. It takes a string (the class name to use - without a period) as an argument. If multiple classes should be added separate them with a space like this: "my-first-class-name my-second-class-name".

This method will be called internally during initialization to update the componentClassName from the componentClassName property in the settings ojbect (if it is set). You usually do not have to call this method after initialization unless you intend to change the class names on the fly, in that case the class names will be swapped immediately on all active instances.

componentManager.setComponentClassName('my-component');

The componentClassName defaults to 'vigor-component' and it returns the componentManager instance.

setTargetPrefix

The setTargetPrefix method takes a string (the prefix to use - without a period) as an argument.

The setTargetPrefix method sets the class prefix that will be used to prefix the targetName property on instanceDefinitions with. The prefixed targetName is the element class name that will be used to find the DOM elements where to add instances (componentAreas).

The structure of the class name that is needed on componentAreas is {{prefix}}--{{name}}, see example below:

componentManager.setComponentClassName('my-component-area');

componentManager.addInstanceDefinition({
  id: 'my-instance',
  componentId: 'my-component',
  targetName: 'main'
})

And the markup would look like this with the prefixed class:

<div class="my-component-area--main"><div>

In the example above the prefix is set to 'my-component-area' and the name of the component area is 'main'. When using the method setTargetPrefix to change the targetPrefix the componentManager will try to move all active instances that has been added to component areas to component ares with the new prefix, if there are no such component areas the instances will be disposed.

This method will be called internally during initialization to update the targetPrefix from the targetPrefix property in the settings ojbect (if it is set). You usually do not have to call this method after initialization unless you intend to change the targetPrefix on the fly.

The targetPrefix defaults to 'component-area' and the method returns the componentManager instance.

setWhitelistedOrigins

The setWhitelistedOrigins method sets the whitelisted origin (or origins) that will be allowed to send messages to instances created by the componentManager. It takes a string (the origin to use) or an array of origins as an argument.

This method will be called internally during initialization to update the whitelistedOrigins from the whitelistedOrigins property in the settings ojbect (if it is set). You usually do not have to call this method after initialization unless you intend to change the whitelisted origins on the fly.

componentManager.setWhitelistedOrigins('http://myorigin.com');

or

componentManager.setWhitelistedOrigins(['http://myorigin.com', 'http://mysecondorigin.com']);

The whitelistedOrigins defaults to 'http://localhost:3000' and it returns the componentManager instance.

getContext

Returns the context of the componentManager as a jQuery object. The context is set either by setting the context property of the settings object or by running the setContext method.

var $context = componentManager.getContext();

getComponentClassName

Returns the componentClassName of the componentManager. The componentClassName is set either by setting the componentClassName property of the settings object or by running the setComponentClassName method.

var componentClassName = componentManager.getComponentClassName();

getTargetPrefix

Returns the targetPrefix of the componentManager. The targetPrefix is set either by setting the targetPrefix property of the settings object or by running the setTargetPrefix method.

var targetPrefix = componentManager.getTargetPrefix();

getActiveFilter

Returns the currently applied filter of the componentManager. The filter is updated by running the refresh method with filter object.

var currentlyAppliedFilter,
filter = {
  url: my/current/route,
  includeIfMatch: 'lang=en_GB'
}

componentManager.refresh(filter);

currentlyAppliedFilter = componentManager.getFilter();

// The currentlyAppliedFilter is the same as the filter.

getConditions

Returns the condition object from the settings object. If any conditions have been added after initialization those conditions will also be returned in the same object. Conditions can be added either by adding them to the conditions object in the settings object or by using the addConditions method.

See conditions for more information about how to use conditions in the componentManager.

var conditions = componentManager.getConditions();

getComponentDefinitionById

The getComponentDefinitionById method takes a component id (string) as argument and it returns the componentDefinition with the passed id.

var componentDefinition = componentManager.getComponentDefinitionById('my-component-id');

getInstanceDefinitionById

The getInstanceDefinitionById method takes a instance id (string) as argument and it returns the instanceDefinition with the passed id.

var instanceDefinition = componentManager.getInstanceDefinitionById('my-component-id');

getComponentDefinitions

The getComponentDefinitions method returns an array with all componentDefinitions registered in the componentManager.

var componentDefinitions = componentManager.getComponentDefinitions();

getInstanceDefinitions

The getInstanceDefinitions method returns an array with all instanceDefinitions registered in the componentManager.

var instanceDefinitions = componentManager.getInstanceDefinitions();

getActiveInstances

The getActiveInstances method returns an array with all instances (instantiated components) currently added to the DOM.

var instances = componentManager.getActiveInstances();

getActiveInstanceById

The getActiveInstanceById method takes a instanceDefinition id (string) as argument and returns its instance (instantiated component) if it is currently added to the DOM.

var instance = componentManager.getActiveInstanceById('my-instance-definition-id');

postMessageToInstance

The postMessageToInstance takes an instanceDefinition id as first argument and a message (can be anything) as a second argument. The message will be forwarded to the the receiveMessage method on the instance of the instanceDefinition matching the passed instanceDefinition id (the instance must in the DOM).

// The example component with the receiveMessage method.
var MyComponent = Backbone.View.extend({
  render: function () {...},
  dispose: function () {...},
  receiveMessage: function (message) {
    console.log(message);
  }
});

// Register the component and one instance of that component
componentSettings = {
  components: [{
    id: 'my-component',
    src: MyComponent
  }],
  instances: [{
    id: 'my-instance',
    componentId: 'my-component',
    targetName: 'body'
  }]
}

// Initialize the componentManager with the componentSettings object
componentManager.initialize(componentSettings);

// Add the instance to the DOM
componentManager.refresh();

// Post a message to the instance
var instanceDefinitionId = 'my-instance',
    message = {
      im: 'a message'
    };

componentManager.postMessageToInstance(instanceDefinitionId, message);

Classes

IframeComponent

By setting a url as src attribute on a instanceDefinition the componentManager will create an instance of the IframeComponent class to load the url in an iframe.

The IframemComponent is exposed on the Vigor object so it's easy to extend it and create custom IframeComponents if additional logic is needed.

The IframeComponent extends Backbone.View and have the following default attributes (the Backbone.View attributes property):

attributes: {
  seamless: 'seamless',
  scrolling: no,
  border: 0,
  frameborder: 0
}

If you pass a iframeAttributes object in the args object passed to the instanceDefinition those properties will be merged into the attributes of the IframeComponent class and added to the DOM element. See example below.

componentManager.initialize({
  components: [{
    id: 'my-component-definition',
    src: 'http://www.google.com'
  }],
  instances: [{
    id: 'my-instance-definition',
    componentId: 'my-component-definition',
    args: {
      iframeAttributes: {
        width: 400
      }
    },
    targetName: 'body'
  }]
});

componentManager.refresh();

This will add an IframeComponent instance with the following iframe markup:

<iframe
  seamless="seamless"
  scrolling="false"
  border="0"
  frameborder="0"
  width="400"
  class="vigor-component--iframe
  vigor-component"
  src="http://www.google.com">
</iframe>

The IframeComponent exposes the public property targetOrigin which defaults to http://localhost:7070 which you override by passing the desired targetOrigin value in the args object (see the targetOrigin documentation):

componentManager.initialize({
  components: [{
    id: 'my-component-definition',
    src: 'http://www.google.com'
  }],
  instances: [{
    id: 'my-instance-definition',
    componentId: 'my-component-definition',
    args: {
      targetOrigin: 'http://www.mydomain.com',
      iframeAttributes: {
        width: 400
      }
    },
    targetName: 'body'
  }]
});

componentManager.refresh();

Cross origin message to a instance

To send messages to instances cross origin using the postMessage api you need to set the origin in the whitelistedOrigins property of the componentManager. Each message needs to have an 'id' property with the id of the instance which the message should be forwarded to. It also needs the 'message' property containing the message to forward and the 'recipient' property set to 'vigorjs.componentmanager'.

See example below.

From within the iframed content:

var data = {
  recipient: 'vigorjs.componentmanager',
  id: 'an-instance-id'
  message: 'the message to forward'
},
targetOrigin = 'http://localhost:3000';
parent.postMessage(data, targetOrigin);

The targetOrigin needs to be registered within the whitelistedOrigins. To see this in action view the IframeComponent examples: IframeComponent example

The IframeComponent class exposes the following public methods:

Property Description
initialize The initialize method will call the addListeners method and set the this.src property if it was passed during instantiation.
addListeners The addListeners method will add a listener for 'onload' on the iframe and call onIframeLoaded as a callback when the iframe is finished loading.
removeListeners The removeListeners method will remove the 'onload' listener.
render The render method will set the src attribute on the iframe and start loading it's content. It returns the instance for chainability.
dispose Dispose will call removeListeners and remove to remove event listeners and remove the element from the DOM.
postMessageToIframe This method will forward a message from the IframeComponent (Backbone.View) class into the contentWindow of the iframe using the postMessage api. It will also pass along the targetOrigin property of the IframeComponent.
receiveMessage The default implementation is a noop
onIframeLoaded The default implementation is a noop

See the IframeComponent example.

Events

ComponentDefinition Events

The componentManager triggers events when adding, removing or changing componentDefinitions. To use these events listen for either component-add, component-remove or component-change on the componentManager instance. The callback will get the affected componentDefinition and an array with all registered componentDefinitions as arguments.

componentManager.on('component-add', function (addedComponentDefinition, allComponentDefinitions) {});

componentManager.on('component-change', function (changedComponentDefinition, allComponentDefinitions) {});

componentManager.on('component-remove', function (removedComponentDefinition, allComponentDefinitions) {});

// or

componentManager.on(componentManager.EVENTS.COMPONENT_ADD, function (addedComponentDefinition, allComponentDefinitions) {});

componentManager.on(componentManager.EVENTS.COMPONENT_CHANGE, function (changedComponentDefinition, allComponentDefinitions) {});

componentManager.on(componentManager.EVENTS.COMPONENT_REMOVE, function (removedComponentDefinition, allComponentDefinitions) {});

InstanceDefinition Events

The componentManager triggers events when adding, removing or changing instanceDefinitions. To use these events listen for either instance-add, instance-remove or instance-change on the componentManager instance. The callback will get the affected instanceDefinition and an array with all registered instanceDefinitions as arguments.

componentManager.on('instance-add', function (addedInstanceDefinition, allInstanceDefinitions) {});

componentManager.on('instance-change', function (changedInstanceDefinition, allInstanceDefinitions) {});

componentManager.on('instance-remove', function (removedInstanceDefinition, allInstanceDefinitions) {});

// or

componentManager.on(componentManager.EVENTS.INSTANCE_ADD, function (addedInstanceDefinition, allInstanceDefinitions) {});

componentManager.on(componentManager.EVENTS.INSTANCE_CHANGE, function (changedInstanceDefinition, allInstanceDefinitions) {});

componentManager.on(componentManager.EVENTS.INSTANCE_REMOVE, function (removedInstanceDefinition, allInstanceDefinitions) {});

Active instance events

The componentManager triggers events when adding, removing or changing active instances (instances that are currently being added, removed or changed by the componentManager in the DOM). To use these events listen for either add, remove or change on the componentManager instance. The callback will get the affected component instance and an array with all active instances as arguments.

componentManager.on('add', function (addedInstance, allActiveInstances) {});

componentManager.on('change', function (changedInstance, allActiveInstances) {});

componentManager.on('remove', function (removedInstance, allActiveInstances) {});

// or

componentManager.on(componentManager.EVENTS.ADD, function (addedInstance, allActiveInstances) {});

componentManager.on(componentManager.EVENTS.CHANGE, function (changedInstance, allActiveInstances) {});

componentManager.on(componentManager.EVENTS.REMOVE, function (removedInstance, allActiveInstances) {});