Source: suxess/core/renderable.js

/**
 * Created by z on 27.11.2015.
 */

'use strict';

/**
 * This class implements renderable interface.
 * Renderable is an object that can have child Renderable elements. Renderable.render method
 * creates DOM element with child elements according to domAttributes property. Any renderable
 * can have on<Eventname> method that will be called when DOM element event with corresponding
 * name is triggered e.g. renderable.onClick willbe called on $(element).trigger('click').
 *
 * @param domAttributes - json object e.g. :
 *       id: 'element-id',
 *       class: ["class1","class2"],
 *       element: '<div/>',
 *       attributes: {src:"url"}
 *
 * @constructor
 */
SUXESS.Renderable = function ( domAttributes ) {

    domAttributes = domAttributes || {};

    this._domAttributes = {
        id: undefined,
        class: [],
        element: '<div/>',
        attributes: {}
    };
    this._lastRender = null;
    this._children = [];

    this._domCallbacks = [];

    for( var prop in domAttributes){

        this._domAttributes[prop] = domAttributes[prop];

    }

};


/**
 * Adds renderable to children renderables
 * @param {SUXESS.Renderable} c
 */
SUXESS.Renderable.prototype.addChild = function (c) {

    this._children.push(c);

};

/**
 * Returns array of children elements.
 * @return {Array} - Array of SUXESS.Renderable
 */
SUXESS.Renderable.prototype.getChildren = function () {

    return this._children;

};


/**
 * Returns DOM element with child elements according to domAttributes with proper callbacks bound.
 * @return {DOM} - DOM element
 */
SUXESS.Renderable.prototype.render = function () {

    var children = this.getChildren();
    var childElems = [];

    for( var i = 0; i < children.length; i++ ) {

        childElems.push( children[i].render() );

    }

    var element = $( this._domAttributes.element )[0];

    this._addCssId(element);
    this._addCssClasses(element);
    this._addJsCallbacks(element);
    this._addHtmlAttributes(element);

    for( i = 0; i < this._domCallbacks.length; i++) {

        $(element).on(this._domCallbacks[i].event, this._domCallbacks[i].fn );

    }

    for( i = 0; i < childElems.length; i++ ) {

        $(element).append( childElems[i] );

    }

    $(element).data('renderable', this);
    this._lastRender = element;

    return element;

};

/**
 * Returns last rendered dom element.
 * @return {element}
 */
SUXESS.Renderable.prototype.getRender = function () {

    return this._lastRender;

};

/**
 * Adds callback to last render and any future renders.
 * Basically same as $(render).on(event,fn)
 * @param event
 * @param fn
 */
SUXESS.Renderable.prototype.addDomCallback = function (event, fn) {

    this._domCallbacks.push({event:event, fn:fn});
    if ( this._lastRender !== null ) {

        $(this._lastRender).on(event,fn);

    }

};


/**
 * Adds id to element being created.
 * @param e
 * @private
 */
SUXESS.Renderable.prototype._addCssId = function (e) {

    $(e).attr( 'id', this._domAttributes.id );

};

/**
 * Adds css classes to element being created.
 * @param e
 * @private
 */
SUXESS.Renderable.prototype._addCssClasses = function (e) {

    var classes = this._domAttributes.class;

    for( var i = 0; i < classes.length; i++ ) {

        $(e).addClass( classes[i] );

    }

};

/**
 * Adds event callbacks using on<Eventname> methods to element being created.
 * Any method that matches ^on[A-Z].* will be added to event callbacks.
 * @param e
 * @private
 */
SUXESS.Renderable.prototype._addJsCallbacks = function (e) {


    // PROPERTIES
    var callbacks = Object.getOwnPropertyNames(this).filter(

        function( property ) {

            if( typeof this[property] !== 'function' )
                return false;

            if( !property.match(/^on[A-Z].*/) )
                return false;

            return true;

        },

        this

    );

    var name;
    for( var i = 0; i < callbacks.length; i++ ){

        name = callbacks[i].substring( 2 );
        name = name.charAt(0).toLowerCase() + name.slice(1);

        $(e).on(name, this[ callbacks[i] ].bind(this) );

    }

    // PROTOTYPE METHODS
    callbacks = [];
    var proto = Object.getPrototypeOf(this);

    for( var f in proto ){

        if( f.match(/^on[A-Z].*/) ){

            name = f.substring( 2 );
            name = name.charAt(0).toLowerCase() + name.slice(1);

            $(e).on(name, proto[ f ].bind(this) );

        }

    }

};

/**
 * Adds html attributes to element being created.
 * @param e
 * @private
 */
SUXESS.Renderable.prototype._addHtmlAttributes = function (e) {

    $(e).attr( this._domAttributes.attributes );

};