Source: carpet.js

(function () {
  'use strict';

  /**
   * Defining global Carpet's namespace (having readable name)
   *
   * @namespace Carpet
   * @type {Object}
   */
  window.Carpet = (function () {

    function Carpet () {

      var carpetModules;
      var carpetComponents;
      var arraySlice;

      carpetModules = {};
      carpetComponents = {};
      arraySlice = Array.prototype.slice;

      return {
        /**
         * Defines console output on or off
         *
         * @type {Boolean}
         * @memberOf Carpet
         */
        loggingEnabled : false,

        /**
         * Will log something in console only if Carpet.loggingEnabled is true
         *
         * @example
         *
         *  Carpet.log('I want to log text');                                 // => Carpet:log (23/11/2012 16:18:42) ["I want to log text"]
         *  Carpet.log('I want to log text', { and: 'a javascript object' }); // => Carpet:log (23/11/2012 16:19:01) ["I want to log text", > Object]
         *
         * @memberOf Carpet
         * @method log
         *
         * @param     {Any[]}  Any number of arguments of any type, all would be logged
         */
        log : function (/* arg1, arg2, ..., argn */) {
          this.debug.apply(this, ['log'].concat(arraySlice.call(arguments)));
        },

        /**
         * Will warn something in console only if Carpet.loggingEnabled is true
         *
         * @example
         *
         *  Carpet.warn('I want to log a warning');                                 // => Carpet:warn (23/11/2012 16:18:42) ["I want to log a warning"]
         *  Carpet.warn('I want to log a warning', { and: 'a javascript object' }); // => Carpet:warn (23/11/2012 16:19:01) ["I want to log a warning", > Object]
         *
         * @memberOf Carpet
         * @method warn
         *
         * @param     {Any[]}  Any number of arguments of any type, all would be warned
         */
        warn : function (/* arg1, arg2, ..., argn */) {
          this.debug.apply(this, ['warn'].concat(arraySlice.call(arguments)));
        },

        /**
         * Will log something in console only if Carpet.loggingEnabled is true
         *
         * @example
         *
         *  Carpet.error('I want to log an error');                                 // => Carpet:error (23/11/2012 16:18:42) ["I want to log an error"]
         *  Carpet.error('I want to log an error', { and: 'a javascript object' }); // => Carpet:error (23/11/2012 16:19:01) ["I want to log an error", > Object]
         *
         * @memberOf Carpet
         * @method error
         *
         * @param     {Any[]}  Any number of arguments of any type, all would be logged
         */
        error : function (/* arg1, arg2, ..., argn */) {
          this.debug.apply(this, ['error'].concat(arraySlice.call(arguments)));
        },

        /**
         * Will log something in console only if Carpet.loggingEnabled is true
         *
         * @example
         *
         *  Carpet.info('I want to log an info');                                 // => Carpet:info (23/11/2012 16:18:42) ["I want to log an info"]
         *  Carpet.info('I want to log an info', { and: 'a javascript object' }); // => Carpet:info (23/11/2012 16:19:01) ["I want to log an info", > Object]
         *
         * @memberOf Carpet
         * @method info
         *
         * @param     {Any[]}  Any number of arguments of any type, all would be logged
         */
        info : function (/* arg1, arg2, ..., argn */) {
          this.debug.apply(this, ['info'].concat(arraySlice.call(arguments)));
        },

        /**
         * Will clear all data that's already logged in console.
         *
         * @example
         *
         *  Carpet.clearConsole()
         *  // Console was cleared
         *
         * @memberOf Carpet
         * @method clearConsole
         */
        clearConsole : function () {
          this.debug.apply(this, ['clear']);
        },


        /**
         * Will debug something in console only if Carpet.loggingEnabled is true
         *
         * @example
         *
         *  Carpet.debug('log',   'I want to log text');          // => Carpet:log (23/11/2012 16:18:42) ["I want to log text"]
         *  Carpet.debug('warn',  'I want to log a warning');     // => Carpet:warn (23/11/2012 16:18:42) ["I want to log a warning"]
         *  Carpet.debug('error', 'I want to log an error');      // => Carpet:error (23/11/2012 16:18:42) ["I want to log an error"]
         *
         * @memberOf Carpet
         * @method debug
         *
         * @param     {String}  debugType   Type of debug info, can be 'log', 'warn' or 'error'
         * @param     {Any[]}               Any number of arguments of any type, all would be logged
         */
        debug : function (debugType /*, arg1, arg2, ..., argn */) {
          var args;
          if (this.loggingEnabled) {
            if (window.console && window.console[debugType] && window.console[debugType].apply) {
              args = arraySlice.call(arguments, 1);
              window.console[debugType].apply(window.console, ['Carpet:{0}'.replace('{0}', debugType), args]);
            }
          }
        },

        /**
         * Will create a new module and add returned methods to the modules namespace
         *
         * @example
         *
         *  // Single instance (the same for all modules, with shared settings etc)
         *  Carpet.module('sampleModule', function (exports, settings, DOMContext) {
         *
         *    var config = {
         *      privateVar: 123
         *    };
         *
         *    var privateFunction = function () {
         *      console.log('This is a private function. Do whatever you want.');
         *      console.log('We have access to public ', settings, ' and public methods ', exports, ' and context :)');
         *    };
         *
         *    exports.sampleMethod = function () {
         *      console.log('This is a sample method');
         *      console.log('We are working on ', DOMContext, ' element');
         *      privateFunction();
         *    };
         *
         *    exports.init = function () {
         *      console.log('this is automatically called after module is loaded');
         *      this.sampleMethod();
         *    };
         *
         *  });
         *
         *
         * @memberOf Carpet
         * @method module
         *
         * @param  {String}   moduleName Name of the module
         * @param  {Function} callback   Actual module body
         */
        module : function (moduleName, initCallback) {
          if (carpetModules[moduleName]) {
            this.warn('Module: {0} already exists. Name collision'.replace('{0}', moduleName));
            return;
          }

          carpetModules[moduleName] = {
            moduleBody : initCallback,
            name       : moduleName,
            settings   : {},
            methods    : {},
            component  : this.getComponent
          };

          this.info('Module: {0} has been loaded to memory'.replace('{0}', moduleName));
        },

        registerComponent : function (componentName, initCallback) {
          if (carpetComponents[componentName]) {
            this.warn('Component: {0} already exists. Name collision'.replace('{0}', componentName));
          }

          carpetComponents[componentName] = {
            name          : componentName,
            componentBody : initCallback,
            initialized   : false
          };

          this.info('Component: {0} has been loaded to memory'.replace('{0}', componentName));
        },

        /**
         * Returns module by given module name
         *
         * @example
         * ...
         * Carpet.module('oneAnotherModule', function () {});
         * ...
         *
         * Carpet.getModule('oneAnotherModule'); // => { name: 'oneAnotherModule', methods: [...]}
         *
         * @memberOf Carpet
         * @method getModule
         *
         * @param  {String} moduleName Name of the module you want to get
         * @return {Object}            Module object
         */
        getModule : function (moduleName) {
          if (carpetModules[moduleName]) {
            return carpetModules[moduleName];
          }
          else {
            this.warn('Module: {0} has not been found in memory'.replace('{0}', moduleName));
          }
        },

        /**
         * Returns registered component by its name
         *
         * @example
         * ...
         * Carpet.registerComponent('uber', function () { return 1; });
         * ...
         *
         * Carpet.getComponent('uber'); // => 1
         *
         * @memberOf Carpet
         * @method getComponent
         *
         * @param  {String} componentName Name of the component you want to get
         * @return {Any}                  Any type that component represents
         */
        getComponent : function (componentName) {
          var component;

          component = carpetComponents[componentName];

          if (component) {
            if (!component.initialized) {
              component.initialized = true;
              component.instance = component.componentBody.call(component);
            }
            return component.instance;
          }
          else {
            this.warn('Component: {0} has not been found in memory'.replace('{0}', componentName));
          }
        },

        /**
         * Loads the modules if found in DOM ([data-module="myModule"])
         * It will automatically fire the `init` method if found inside the module
         *
         * @example
         *
         * <div class="module" data-module="myModule"></div>
         *
         * <script type="text/javascript">
         *  Carpet.init();
         * </script>
         *
         * @memberOf Carpet
         * @method init
         *
         */
        init : function () {

          var DOMModules = document.querySelectorAll('[data-module]');

          for (var moduleIndex = 0; moduleIndex < DOMModules.length; moduleIndex++) {
            var domModule;
            var moduleName;
            var moduleSettings;
            var currentModule;

            domModule = DOMModules[moduleIndex];
            moduleName = domModule.getAttribute('data-module');

            /*jshint -W054 */
            moduleSettings = new Function ('return ' + domModule.getAttribute('data-settings'))() || {};
            /*jshint +W054 */

            currentModule = carpetModules[moduleName];

            if (currentModule) {

              currentModule.settings = moduleSettings;
              currentModule.moduleBody.call(currentModule, currentModule.methods, currentModule.settings, domModule);

              if (typeof currentModule.methods.init === 'function') {
                this.info('Module: {0} has been autoinited'.replace('{0}', currentModule.name));
                currentModule.methods.init();
              }
            }
            else {
              this.warn('Module: {0} has not been found'.replace('{0}', moduleName));
            }
          }
        }
      };
    }

    return new Carpet();
  }());
})();