Tiny single purpose view engine, help componentize your javascript. Chemical will help you make super performant UI apps.

No abstractions, limit sugar. Keep your self close to the browser, but create compostable, and reusable UIs.

Compatible with any template engine that accepts a model, and returns HTML. Use precompiled templates for blazing performance.

Compatible with AMD, CJS, or can be found at c$ if no module loader is used.

Compatible with Google Closure Compiler

Features

Get Started

npm install psychic --save-dev

Basics

Chemical tries to be ultra fast by limiting the actual representation from the declarative api, to rendered DOM elements.

We generate HTML based on your outer Control's components. Events are wired to a single rendered container, utilizing a state machine like Redux - we can achieve some crazy performance.

Extend any component at runtime using our declarative API, or leverage defining prototypes for parse time performance improvements!

Chemical helps you manage events. We rely on native event implementations, but provide a small amount of sugar to abstract on them for you. Including condensing events to a single rendered root, detaching and reattaching at render time!

Chemical has a full stack of lifeCycle events! You can listen for events that range from afterRender, to beforeUpdate.

It's amazing we fit all of this into less than 2kb of compressed space! For an additional 1kb you can bring our state machine with! That's only 3kb gzipped to get similar functionality as ReactJS + Redux!

And yes, it is universal JS compatible, and views can be rendered on the server.

Usage

Generic Hello World

var Component = require('psychic');
 
var component = new Component({
    template: function(data) { return data.message; },
    message: 'Hello World'
});
 
component.renderInto(document.body);

Compose components

var Component = require('psychic');
 
var component = new Component({
    components: [
        new Component({
            template: function(){ return 'Hello World';}
        })
    ]
});
 
component.renderInto(document.body);

Another way to do hello world, we provide a default template

var Component = require('psychic');
 
var component = new Component({
    innerHTML: 'Hello World'
});
 
component.renderInto(document.body);

We render components into this innerHTML for you. We collapse the representation of the DOM, and exploit the super fast innerHTML implementations developed over the years.

var Component = require('psychic');
 
var component = new Component({
    template: function(data) {
        return '<chrome>' + data.innerHTML + '</chrome>';
    },
    components: [
        new Component({
            innerHTML: 'hello world'
        })
    ]
});
 
component.renderInto(document.body);

API

Chemical currently has two core blocks to build with, control and component. A control is used when you expect a component to render. It adds a bit of extra decoration to a base component. The API is small and consists of these calls:

API EXAMPLES

var Component = require('psychic').component;

// mix 
// mixes data into 
// the component root  
component.mix(data);
 
 
// component hash 
// provides a quick and easy way to 
// access named components. 
// 
// components are only hashed on render 
// and not at contruction time 
var component = new Component({
        name: 'myComponent'
});
component.render();
component.$.myComponent;
 
 
// guid 
// 
// instanced components will be provided with a guid for eventing 
// this guid changes on page reload, but persists while the application 
// is loaded 
component.guid
 
// listen 
// 
// listen for browser events. Events are wired against outer container 
// nodes. Only the root rendered control will be listening, and delegating  
// to target child nodes when appropriate.  
// 
// listeners are tore down before every re-render. This is to encourage the 
// developer to think sparingly about event usage within the view library 
component.listen('click', function(e){
        console.log('this component was clicked!');
});
 
// listeners should be inserted using afterRender. 
var component = new Component({
        afterRender: function() {
                this.listen('click', function(){});
        }
});
 
// listeners attach to the parent node 
var component = new Component({
        components: [
            var component = new Component({
               afterRender: function() {
                   // the root rendered parent will own the click 
                   // the click will be delegated to this handler 
                   this.listen('click', function(){});
               }
            });   
        ]
});
 
 
/// dispatch 
/// 
/// dispatches event that listeners are waiting to receive 
/// dispatch is throttled to 1 call per 80ms. Plan accordingly. The best use of this 
/// is to request the state machine to draw the view.  
/// this does not influence native event dispatches, only those sent you .dispatch 
/// 
component.listen('myevent', function(e){ console.log(e.data); });
component.dispatch('myevent', data);
 
/// destroy 
/// 
/// Tears down the component, child components, event handlers, but does not 
/// destroy the instance.  
component.destroy();
var Control = require('psychic').control;`
/// update
///
/// updates the component's internal rendering
/// if passed data, will also call .mix, before re-rendering.
///
/// if .update is called on a parent that node will be replaced
/// in the DOM.
 
// with no data, just ask for a re-render, and reinsertion
// into the DOM if has a represented node.
control.update();
 
// send data to use before rendering and DOM swap
control.update({foo: 'bar'});
 
 
/// renderInto
///
/// Renders a control into a DOM target. 
control.renderInto(document.body);
 
 
/// render
///
/// Renders the control with the currently bound data set.
/// Great for rendering on the server. 
/// We also provide a view engine that does this lifting for you.
control.render();

Declarative Syntax API

Components are mainly composed in Chemical using the declarative syntax. This allows natural composition that most developers are familiar with in view libraries.

// the base default declarative syntax for a control object 
 
var definition = {
    name: 'myComponent', // optional name for reference hashing 
    tag: 'div', // root node type, default is DIV 
    attributes: { // any DOM attribute to append to root node 
         'class': 'red',
         'data-foo': 'bar'
    },
    afterRender: function(){}, // LifeCycle hooks 
    components: [ // composition block, add components for reuse 
        new Component()
    ],
    template: function(data) {
        // the template provides the innerHTML structure of the component 
        // If a components block is defined on this component, data.innerHTML  
        // will be populated, so you can template chrome around your components 
        return 'Hello World'
    },
    innerHTML: '', // a simple string that is returned to the template, the default template renders this 
    myCustomFunction: function(){} // Methods you want to travel with the component 
};
 
// instance a new component by passing it 
// the definition 
var control = new Control(definition);

Imperative Syntax API

Sometimes we want to extend components for reusability. We also want to get some performance gains when defining new components. Chemical leans heavily on prototypal inheritance to get some nice performance from our paradigm.

var Control = require('psychic').control;
 
module.exports = function(data) {
        // do some custom stuff here 
        Control.call(this, data);
};

LifeCycle

Chemical provides component LifeCycle events. wip

Here is a list of all the LifeCycle events that can be defined

Mounting

Chemical allows you to render components on the server, and then mount them client side. It requires little to no effort on your part. Simply give your components a name.

var Control = require('psychic').control;
 
module.exports = new Control({
    name: 'myMountableComponent',
    components: [
        new Control({
            name: 'myButton',
            template: <button>Click Me</button>,
            handlers: {
                'click': function(e) {
                    alert('I was clicked!');
                }
            }
        })
    ]
});

Using a State Store / State Machine

It is preferential to use a State Store to maintain the state of your component. Utilizing the FLUX or one way data flow model, we can increase reliability and performance of our UI.