lib/components/c_info.js

Summary

Maintainability
A
0 mins
Test Coverage
'use strict';

var componentsRegistry = require('./c_registry')
    , facetsRegistry = require('./c_facets/cf_registry')
    , componentName = require('../util/component_name')
    , Scope = require('./scope')
    , miloCore = require('milo-core')
    , logger = miloCore.util.logger
    , _ = miloCore.proto;


module.exports = ComponentInfo;


/**
 * Simple class to hold information allowing to create/copy component using [`Component.create`](./c_class.js.html#create) and [`Component.copy`](./c_class.js.html#copy).
 *
 * @constructor
 * @param {Scope} scope scope object the component belogs to, usually either top level scope that will be returned by [milo.binder](../binder.js.html) or `scope` property of [Container](./c_facets/Container.js.html) facet of containing component
 * @param {Element} el DOM element the component is attached to
 * @param {BindAttribute} attr BindAttribute instance that the component was created with
 * @param {Boolean} throwOnErrors If set to false, then errors will only be logged to console. True by default.
 * @return {ComponentInfo}
 */
function ComponentInfo(scope, el, attr, throwOnErrors) {
    attr.parse().validate();

    this.scope = scope;
    this.el = el;
    this.attr = attr;
    this.name = attr.compName;
    this.ComponentClass = getComponentClass(attr, throwOnErrors);
    this.extraFacetsClasses = getComponentExtraFacets(this.ComponentClass, attr, throwOnErrors);

    if (this.ComponentClass
            && hasContainerFacet(this.ComponentClass, this.extraFacetsClasses)) {
        this.container = {};
    }
}


/**
 * ####ComponentInfo instance methods####
 * 
 * - [destroy](#ComponentInfo$destroy)
 * - [rename](#ComponentInfo$rename)
 */
_.extendProto(ComponentInfo, {
    destroy: ComponentInfo$destroy,
    rename: ComponentInfo$rename
});


/**
 * ComponentInfo instance method
 * Destroys ComponentInfo by removing the references to DOM element
 */
function ComponentInfo$destroy() {
    delete this.el;
    this.attr.destroy();
}


/**
 * ComponentInfo instance method
 * Renames ComponentInfo object
 *
 * @param {String} [name] optional new component name, generated from timestamp by default
 * @param {Boolean} [renameInScope] optional false to not rename ComponentInfo object in its scope, true by default
 */
function ComponentInfo$rename(name, renameInScope) {
    name = name || componentName();
    Scope.rename(this, name, renameInScope);
    this.attr.compName = name;
    this.attr.decorate();
}


function getComponentClass(attr, throwOnErrors) {
    var ComponentClass = componentsRegistry.get(attr.compClass);
    if (! ComponentClass)
        reportBinderError(throwOnErrors, 'class ' + attr.compClass + ' is not registered');
    return ComponentClass;
}


function getComponentExtraFacets(ComponentClass, attr, throwOnErrors) {
    var facets = attr.compFacets
        , extraFacetsClasses = {};

    if (Array.isArray(facets))
        facets.forEach(function(fctName) {
            fctName = _.firstUpperCase(fctName);
            if (ComponentClass.hasFacet(fctName))
                reportBinderError(throwOnErrors, 'class ' + ComponentClass.name
                                      + ' already has facet ' + fctName);
            if (extraFacetsClasses[fctName])
                reportBinderError(throwOnErrors, 'component ' + attr.compName
                                      + ' already has facet ' + fctName);
            var FacetClass = facetsRegistry.get(fctName);
            extraFacetsClasses[fctName] = FacetClass;
        });

    return extraFacetsClasses;
}


function reportBinderError(throwOnErrors, message) {
    if (throwOnErrors === false)
        logger.error('ComponentInfo binder error:', message);
    else
        throw new Error(message);
}


function hasContainerFacet(ComponentClass, extraFacetsClasses) {
    return (ComponentClass.hasFacet('container')
        || 'Container' in extraFacetsClasses
        || _.someKey(extraFacetsClasses, facetRequiresContainer)
        || classHasFacetThatRequiresContainer());

    function classHasFacetThatRequiresContainer() {
        return (ComponentClass.prototype.facetsClasses
            && _.someKey(ComponentClass.prototype.facetsClasses, facetRequiresContainer));
    }

    function facetRequiresContainer(FacetClass) {
        return FacetClass.requiresFacet('container');
    }
}