Skip to content

Components

Jason Strimpel edited this page Sep 3, 2014 · 10 revisions

A Component is an encapsulation of business logic that is comprised of CSS, HTML, and JavaScript. The end result of a component action is the rendering of markup.

Directory Structure

The components directory contains a sub directory for each component. Underneath each component directory there is a views directory, and optional client and server directories.

|- components
    |- component_name
        |- views
           index.hbs
           index.js (optional)
        |- client (optional)
        |- server (optional)
        controller.js (optional)

Component Parts

The minimum requirement for a component is an index template file. controller.js and index.js are optional, and should only used if necessary.

Controller

controller.js contains the component’s business logic. This is where actions are defined and values such as models and collections are added to the component instance context.

Templates

Out of the box Lazo supports Handlebars.js and Underscore.js micro templates. By default Lazo assumes Handlebars. To use a micro template the view object property templateEngine is set to "micro". Lazo also has an interface for supporting other template engines that return a string, which can be defined by your application.

Templates are passed a context object that contains a reference to the component context, assets, and any view property that is not a function.

Views

Lazo views are encapsulations of rendering logic and UI behavior. They are extended Backbone views that run on the client and server with defined life cycles. The hook point for executing DOM is afterRender. This method executes on first page load after the views have been bound to the DOM and every time a view is rendered. View instances have a preoprty that contains reference to the controller that instantiated them, ctl.

CSS

Lazo automatically loads component CSS after application CSS has been loaded. CSS should be placed in the client directory or a sub directory of client.

CSS can be scoped to a component by using the component name attribute selector as follows:

[lazo-cmp-name="component_name"] .some-class {
    color: #333;
}

Other JavaScript

Other JavaScript can be loaded for component by adding it to a view or controller define statement as a dependency. The baseUrl is the application root, so paths can be relative to it or they can be relative to the module which is listing them as a dependency.

// include JavaScript in a view
define(['lazoView', 'l!./client/superWidget'], function (LazoView, SuperWidget) {

    'use strict';

    return LazoView.extend({
        afterRender: function () {
            this.widget = new SuperWidget(this.el);
        }
    });

});

// include JavaScript in a controller
define(['lazoCtl', 'components/cmp_name/util'], function (LazoCtl, util) {

    'use strict';

    return LazoCtl.extend({
        index: function (options) {
            this.ctx.params = util.doSomething(this.ctx.params);
            options.success('index');
        }
    });

});

The l! is a custom loader that noop's client code of the server and server code on the client. It does this by examining the path for directory names server, client, and node_modules.

Action

A component action is a method on the controller that can be mapped to a route or executed by calling the controller navigate method. The default action method is index.

Arguments

A component action method is passed a set of options, which contain success and error callback functions.

The success callback accepts a single argument, which is the name of the template to be returned. If a view class with the same name is found it will be bound to the markup that template generates in the DOM.

The error callback accepts a single argument, an error object or a string. The best practice is to pass an error object. This will trigger a 500 error response and call the 500 error template.

Calling an Action

Actions can be mapped to routes. In which case they are automatically executed for you when the route is matched. Actions can also be executed by calling the controller navigate method.

Context

The component context is an object that persists data for the life of the component instance. It is used as the mechanism to maintain state between the client and server for a given page response or controller navigation.

Serialization

When the first page load is rendered on the server the context tree for the page is serialized for rehydration on the client. The context tree is comprised of the context objects for each component in the page request. However, only specific properties are serialized for each component context. Otherwise the entire object graph would have to be serialized. The following sections describe the properties that are serialized.

Models and Collections

When models and collections are loaded they can optionally be added to the context object to ensure that they are persisted across environments for a page life cycle. The reserved names in the context object are models and collections.

define(['lazoCtl'], function (LazoCtl) {

    'use strict';

    return LazoCtl.extend({

        index: function (options) {
            var self = this;

            this.loadModel('model_name', {
                success: function (model) {
                    // this will be presisted between environments
                    self.ctx.models.any_name_you_like = model;
                    options.success('index');
                }
            });
        }

    });

});
Parameters

Parameters are persisted.

Shared Data

If you would like to persist data that is accessible by any component this can be done by using the context shared data API.

// inside of a controller method
this.ctx.setSharedData('some_key', 'a_value');

// somewhere else in any controller
this.ctx.getSharedData('some_key'); // returns 'a_value'

Controller Navigation

A controller has the ability to navigate to a different view/template by calling an action. This will render the new output for the component inside of the component container and instantiate the associated view if one exists. This does not change the URL in the browser.

// in a view instance in response to a user action
// callbacks are optional
this.ctl.navigate('action_name', {
    success: function () {
        console.log('SUCCESS!!!')
    }
});

Composition

A component can have child components. Child components are added using the addChild method. Children are added to component container. A component container can contain more than one component. Components in a container are rendered in the order they are added.

define(['lazoCtl'], function (LazoCtl) {

    'use strict';

    return LazoCtl.extend({

        index: function (options) {
            var self = this;

            this.addChild('container_name', 'component_name', {
                // query and path parameter are only passed to the root controller
                // or the controller for a layout body
                ctx: {
                    params: this.ctx.params
                },
                success: function (ctl) {
                    // you can add as many children as you like
                    // and use any library you want to make managing
                    // async code easier; in this case we are just
                    // adding a single child so we can execue the callback
                    // for the controller action once the child has been added
                    options.success('index');
                }
            });
        }

    });

});
<!-- parent controller template markup -->
<div lazo-cmp-container="container_name"></div>

Events

A controller instance has Backbone.Events mixed in. The methods are noop’d on the server. It is best practice that child components do not have knowledge of their parent. However, a component instance does have knowledge of its children via the children property in its controller.

// inside of a parent controller method

// get a reference to a child controller
var child = this.children.container_name[0];

// listen for a child event inside of the parent
child.on('some_event', function (message) {
    // take some action in the parent controller
});

// inside of a child controller method
var message = { foo: 1 };
this.trigger('some_event', message);

Clone this wiki locally