Skip to content

brunschgi/terrificjs

Repository files navigation

Build Status

Terrific JavaScript Framework

What is TerrificJS

TerrificJS is a neat, clever piece of code that allows you to modularize your frontend code by solely relying on naming conventions. So simple, so effective! Its your perfect JS buddy if you are working with a «modularization philosophy» like Atomic Design in mind.

Btw: It's productively being used on many high traffic sites and applications – e.g. freitag.com, swisscom.ch, sbb.ch, frontify.com & many many more.

Content

Installation

Install the package as a bower component

bower install terrific

ES5 & ES6 Promise

Terrific depends on an es5-shim for older browsers. kriskowal's es5-shim provides everything required. Moreover it uses ES6 Promises. jakearchibald's es6-promise does the trick.

If you have installed terrific as a bower component, these dependencies are already included.

Examples

You can find some examples on Codepen

Quickstart

Making your Terrific application up and running is a simple 3 step process.

  1. Create your modules
  2. Annotate your markup
  3. Boostrap your application

1. Create your modules

Create your modules by using T.createModule({ … })

T.Module.Foo = T.createModule({
	start: function(resolve) {
	  // your logic goes here

	  resolve();
	}
});

2. Annotate your markup

Drop terrific.js to your page and annotate your markup by using the data-t-name attribute, eg. for the molecule foo`

<!DOCTYPE html>
<html>
<head></head>
<body>
	<div data-t-name="Foo"></div>
	<script src="bower_components/terrific/dist/terrific.min.js"></script>
</body>
</html>

3. Bootstrap your application

Bootstrap your application (at the end of the page or when the document is ready)

var application = new T.Application();
application.registerModules();
application.start();

Usage

Modules

Modules are the real stars of your TerrificJS application. All of your JavaScript code will find its place in one of the modules. In regard to this fact you will find yourself spending most of your time writing modules.

The module API is very easy and consists of 2 lifecycle methods – start() and stop()

T.Module.Foo = T.createModule({
	start: function(resolve) {
		// your logic goes here -> executed on application.start()

		// use this._ctx – contains the DOM Element with the `data-t-name` attribute – to encapsulate your logic;
		// e.g. $(this._ctx).find('.js-foo`)

		// use this._sandbox to access the shared – among all modules – `Sandbox` instance;
		// e.g. this._sandbox.getConfig();

		// use this_events – contains an EventEmitter instance – to communicate with the «outside», e.g. other modules;
		// e.g. this._events.emit('foo', …);

		resolve();
	},
	stop: function() {
		// optional -> tidy up your module. Called on application.stop()

		// e.g. this._events.disconnect(); -> disconnect from the event dispatcher
	}
});

To get your module instantiated with the correct this._ctx you have to annotate your markup with the data-t-name attribute.

<div data-t-name="Foo"></div>

Decorators

Decorators allow you to extend the functionality of your modules.

Example

Create Module

var T.Module.Foo = T.createModule({
    value: 'foo',
    start : function (resolve, reject) {
         resolve()
    },
    foo: function() {
       return 'foo';
    }
});

Create Decorator

T.Module.Foo.Bar = T.createDecorator({
     /* override property */
     value: 'bar',
     /* extended (decorated) lifecycle method */
     start : function (resolve, reject) {
          this._parent.start(resolve, reject);
     },
     /* new method */
     bar : function () {
         return "bar";
     },
     /* extended (decorated) method */
     foo : function () {
         var value = this._parent.foo();
         return 'bar-'+ value;
     }
});

Usage

var module = application.registerModule(document.createElement('div'), 'Foo', ['Bar']);

module.bar(); // -> bar
module.foo(); // -> bar-foo
module.value; // -> bar (as we are decorating and not inheriting from the module, the property gets overridden)
module.start(); // -> executes the start from the decorator and the module

Basically the this._parent property allows you to call all methods from the decorated module. Mention: as we are decorating and not inheriting from the module, all properties gets overridden directly on the instance.

To get your module decorated by the proper decorator you have to annotate your markup with the data-t-decorator attribute.

<div data-t-name="Foo" data-t-decorator="Bar"></div>

EventEmitter

The EventEmitter allows your module to communicate with the outside (e.g. other modules). To provide proper module encapsulation, every module comes with an own EventEmitter instance – bound on this._events. All emitted events are being dispatched over the Sandbox to the other EventEmitter instances.

The EventEmitter API is strongly related to the NodeJS EventEmitter API. The following methods are supported

emit(event, ...)

Emits an event with variable option args.

on(event, fn) / addListener(event, fn)

Registers an event handler fn.

once(event, fn)

Registers a single-shot event handler fn, removed immediately after it is invoked the first time.

off(event, fn) / removeListener(event, fn) / removeAllListeners(event, fn)

  • Pass event and fn to remove a listener.
  • Pass event to remove all listeners on that event.
  • Pass nothing to remove all listeners on all events.

listeners(event)

Returns an array of callbacks, or an empty array.

hasListeners(event)

Checks if this emitter has event handlers.

Example

T.Module.Foo = T.createModule({
	start: function(resolve) {
		this._events.on('foo', function() {
			// do something
		});

		resolve();
	}
});

Bootstrap Application

The bootstrap kickstarts the engine of your Application and saves you from tedious work by wiring up your components automatically. Place the bootstrap code at the end of your page (or when the document is ready).

var application = new T.Application(); // creates a new application context
application.registerModules(); // registers all – eventually decorated –  modules by using the `data-t-name` and `data-t-decorator` attributes.
application.start(); // starts the application by calling `start()` on each registered module instance

This is the most basic form of the application bootstrap. Optionally you can extend the bootstrap and tailor it to your needs.

Specific application context

Create a new application for a specific part of the page

var application = new T.Application(document.getElementById('my-app'));
application.registerModules(); // only considers elements inside the <div id="my-app"></div>

Alternatively you could also do

var application = new T.Application();
application.registerModules(document.getElementById('my-app')); // only considers elements inside the <div id="my-app"></div>

Global config

Pass in a global configuration object (accessible over the shared Sandbox instance).

var application = new T.Application({ foo: 'bar', bar : 'foo' });

Register module manually

Register the module Foo – decorated with the decorator Bar – on the DOM element with id="foo".

var application = new T.Application();
application.registerModule(document.getElementById('foo'), 'Foo', ['Bar']);

Lifecycle Events

Lifecycle events offers the possibility to listen and react on some application internals by using EventEmitter. Supported lifecycle events

  • t.register.start => emitted when application.registerModules() is started
  • t.missing with params ctx, name, decorators => emitted for each module that cannot be found
  • t.register.end => emitted when application.registerModules() is ended

=> The above 3 could e.g. be used to collect all missing modules and lazy load them on demand

  • t.start => emitted when the modules are started
  • t.sync => emitted when the start method of all modules has been run (replacement of the former after hook)
  • t.stop => emitted when the modules are stopped
  • t.unregister.start => emitted when application.unregisterModules() is started
  • t.unregister.end => emitted when application.unregisterModules() is ended

Example

Listen to the t.sync lifecycle event to make sure that all modules have added their listeners before emitting an event

T.Module.Foo = T.createModule({
	start: function (resolve) {
		this._events.on('t.sync', this.sync.bind(this));

		resolve();
	},
	sync: function () {
		this._events.emit('foo');
	}
});

T.Module.Bar = T.createModule({
	start: function (resolve) {
		this._events.on('foo', function() {
			// do something
		});

		resolve();
	}
});

Sandbox

The Sandbox instance is shared among all modules and allows them to communicate with the application. Every module can access the sandbox over the this._sandbox property.

Supported methods

addModules(ctx)

Add modules – contained in the ctx DOM Element – to the application

removeModules(ctx)

  • ctx == DOM Element: Remove all modules – contained in the ctx DOM Element – from the application
  • ctx == Module collection (return value from addModules): Remove given modules from the application

getModuleById(id)

Returns the module instance for the given id (id = value of data-t-id attribute)

getConfig()

Returns the global config

getConfigParam(name)

Returns the appropriate config param

Advanced Usage

Change T.Module Namespace

The application bootstrap looks for module and decorator definitions in the T.Module namespace, e.g. T.Module.Foo. The namespace can be changed either for individual or for all modules.

Individual module

Create module in different namespace, e.g. App.Component.

App.Component.Foo = T.createModule({
	start: function (resolve) {
		

		resolve();
	}
});

Annotate your markup by using the data-t-namespace attribute

<div data-t-name="Foo" data-t-namespace="App.Component"></div>

All modules

Its quite tedious to blow up your markup by adding the data-t-namespace attribute to each single module .

Change the default namespace in the global config.

var application = new T.Application({ namespace: 'App.Component' });

Use EventEmitter outside modules

The EventEmitter can also be used to communicate with modules from non-module context, e.g. from the application bootstrap.

var application = new T.Application();

var emitter = new T.EventEmitter(application._sandbox);
emitter.on('t.sync', function() {
	emitter.emit('foo');
});

application.registerModules();
application.start();

Build your own TerrificJS

  • Install Node.js
  • Open a terminal
  • Make sure gulp is available globally npm install -g gulp
  • Run npm install
  • Run bower install
  • Run gulp

You will get the following release artifacts

  • dist/docs/ – generated API documentation
  • dist/terrific.js – the full release
  • dist/terrific.min.js – the minified release for production use
  • dist/terrific.min.js.map – sourcemaps
  • terrific.d.ts and dist/es6-promise.d.ts – typescript definition

TerrificJS makes it easy for you to change and test your own TerrificJS build.

  • Run gulp watch and change any source you like

To test your build with PhantomJS

  • Run npm test

To test your build in a real browser environment

  • Make sure karma-cli is available globally npm install -g karma-cli
  • Run karma start (comes with Chrome launcher)