API Docs for: 0.2.3
Show:

File: src\scene\index.js

/**
 * @module Lyria
 */
define('lyria/scene', ['jquery', 'mixer', 'nexttick', 'eventmap', 'lyria/gameobject', 'lyria/language', 'lyria/log', 'lyria/localization'], function($, mixer, nextTick, EventMap, GameObject, Language, Log, Localization) {'use strict';

  var createNamespace = function(obj, chain, value) {
    var chainArr = chain.split('.');

    for (var i = 0, j = chainArr.length; i < j; i++) {
      (function(item, lastElem) {
        if (lastElem) {
          obj[item] = value;
        } else {
          obj[item] = obj[item] || {};
        }

        obj = obj[item];
      })(chainArr[i], i === j - 1);
    }
  };

  var Scene = (function() {

    /**
     * Scene constructor
     *
     * @class Scene
     * @constructor
     */
    
    // TODO: Having options as the last parameter is kinda unintuitive
    var Scene = function(sceneName, sceneDeps, sceneFunction, options) {
      if (!sceneName) {
        return;
      }

      if ( typeof sceneDeps === 'function') {
        options = sceneFunction;
        sceneFunction = sceneDeps;
        sceneDeps = {};
      }

      // Mixin event map into Scene
      // Sender: "scene:#{sceneName}"
      mixer([this, Scene.prototype], new EventMap());

      // We need a reference to the scene not being this
      var self = this;

      // Set name
      this.name = sceneName;
      
      // Data
      this.data = options.data || {};

      // Default values
      this.localization = new Localization();

      // Default event value
      this.defaultEvent = 'click';
      
      // DOMEvents object
      this.DOMEvents = {};

      // Template values
      this.template = {};
      this.template.source = null;
      this.template.helpers = {};
      this.template.partials = {};
      
      // Collect all template values
      this.template.data = {};

      // Children object
      // TODO: This is not needed, if a scene is actually a component - maaagic!
      this.children = this.children || {};
      this.children.gameObjects = {};
      this.children.prefabs = {};
      
      // Set Id
      this.id = this.data.id || self.name;

      // Expose function for template values
      this.expose = function(obj) {
        if (!obj || $.isEmptyObject(obj)) {
          return;
        }

        self.template.data = $.extend(true, self.template.data, obj);
      };

      // Getter to have a safe way to the element of a scene
      Object.defineProperty(self, '$element', {
        get: function() {
          return $('#' + self.id);
        }
      });

      // Synchronizes events and scene view
      self.on('synchronize', function() {
        self.refresh();

        if (self.DOMEvents && !$.isEmptyObject(self.DOMEvents)) {
          self.DOMEvents.delegate = '#' + self.id;
        }

        // Your typical stock-of-the-mill update function
        self.on('update', function(dt) {
          $.each(self.children, function(childKey, childValue) {
            if (!$.isEmptyObject(childValue)) {
              $.each(childValue, function(key, value) {
                value.trigger('update', dt);
              });
            }
          });
        });

        // Bind events
        if (self.DOMEvents && !$.isEmptyObject(self.DOMEvents)) {
          $.each(self.DOMEvents, function(key, value) {
            if (( typeof value === 'object') && (key !== 'delegate')) {
              $(self.DOMEvents.delegate).on(value, key, {
                scene: self
              });
            }
          });
        }

        // Data binding
        // TODO: Find a better way than using Object.watch
        if (self.template.data && !$.isEmptyObject(self.template.data)) {
          self.$element.find('[data-bind]').each(function() {
            var $dataElem = $(this);

            var prop = $dataElem.data('bind');

            self.template.data.watch(prop, function(id, oldval, newval) {
              if (oldval !== newval) {
                $dataElem.html(newval);
              }

              return newval;
            });
          });
        }
      });

      var createScene = function(modules) {
        var sceneDone = function(err, success) {
          if (err) {
            return console.error('Error while executing scene ' + self.name + ': ' + err);
          }

          self.trigger('synchronize');
        };

        var async = false;

        var context = self;
        context.async = function() {
          async = true;

          return function() {
            nextTick(function() {
              sceneDone();
            });
          };
        };

        if (!self.hasOwnProperty('isAsync')) {
          Object.defineProperty(self, 'isAsync', {
            get: function() {
              return async;
            }
          });
        }

        context.modules = modules;

        try {
          var success = sceneFunction.apply(context, [context, modules]);

          if (!async) {
            sceneDone(null, success);
          }
        } catch (e) {
          sceneDone(e);
        }
      };

      // Call scene
      var reqModules = Object.keys(Scene.requireAlways) || [];

      self.on('added', function() {
        require(reqModules, function() {
          var importedModules = {};
  
          for (var i = 0, j = arguments.length; i < j; i++) {
            (function(dep) {
              createNamespace(importedModules, Scene.requireAlways[reqModules[i]], dep);
            })(arguments[i]);
          }
  
          sceneDeps = sceneDeps || {};
          var reqSceneModules = Object.keys(sceneDeps) || [];
  
          if (reqSceneModules.length) {
            require(reqSceneModules, function() {
  
              for (var k = 0, l = arguments.length; k < l; k++) {
                (function(sceneDep) {
                  createNamespace(importedModules, sceneDeps[reqSceneModules[j]], sceneDep);
                })(arguments[j]);
              }
  
              createScene(importedModules);
            });
          } else {
            createScene(importedModules);
          }
  
        });
      });
      
    };

    /**
     * Adds a gameobject to the scene
     *
     * @method add
     * @param {Object} child
     */
    Scene.prototype.add = function(child) {
      var self = this;

      if ( child instanceof GameObject) {
        this.children.gameObjects[child.name] = child;

        this.template.data.gameobject = (function() {
          var array = [];

          $.each(self.children.gameObjects, function(key, value) {
            array.push(value);
          });

          return array;
        })();

        this.trigger('add');

        return true;
      }

      if ( child instanceof GameObject) {
        this.children.prefabs[child.name] = child;

        this.template.data.gameobject = (function() {
          var array = [];

          $.each(self.children.gameObjects, function(key, value) {
            array.push(value);
          });

          return array;
        })();

        this.trigger('add');

        return true;
      }

      return false;
    };

    /**
     * Refreshes the scene (Re-renders the template)
     *
     * @method refresh
     * @param {Object} val
     */
    Scene.prototype.refresh = function(val) {
      if (val == null && this.template) {
        val = this.template.data;
      }

      // Add default helpers
      this.template.helpers['translate'] = Localization.elements(this.localization.data, this.localization.language);

      if (this.template && this.template.source) {
        this.content = this.template.source(val, {
          partials: this.template.partials,
          helpers: this.template.helpers
        });
      }

      if (this.$element.length > 0) {
        this.$element.html(this.content);
      }

      this.trigger('refresh');
    };

    /**
     * Sets an event to the event object (DOM events)
     *
     * @method bindEvent
     * @param {String} selector
     * @param {String} eventName
     * @param {Function} eventFunction
     */
    Scene.prototype.bindEvent = function(selector, eventName, eventFunction) {
      if (selector == null) {
        return;
      }

      if ( typeof eventName === 'function') {
        this.DOMEvents[selector][this.defaultEvent] = eventFunction;
      } else {
        if (typeof eventName === 'string') {
          this.DOMEvents[selector][eventName] = eventFunction;          
        } else {
          throw new Error('If you meant to bind more than one event, please use Scene#bindEvents');
        }
      }
    };

    /**
     * Binds a lot of events instead of a single one
     * 
     * @method bindEvents
     * @param {Object} eventObject
     * @see bindEvent
     */
    Scene.prototype.bindEvents = function(eventObject) {
      if (eventObject == null) {
        return;
      }
      
      for (var key in eventObject) {
        if (Object.hasOwnProperty.call(eventObject, key)) {
          this.DOMEvents[key] = eventObject[key];
        }
      }
    };

    /*
     * Unbinds a previously bound event
     *
     * @method unbindEvent
     * @param {String} selector
     * @param {String} eventName
     * @param {Function} eventFunction
     */
    Scene.prototype.unbindEvent = function(selector, eventName) {
      if (selector == null) {
        return;
      }

      if (eventName == null) {
        delete this.DOMEvents[selector];
      } else {
        delete this.DOMEvents[selector][eventName];
      }
    };
    
    /**
     * Unbinds a lot of events
     * 
     * @method unbindEvents
     * @param [Object] eventObject
     */
    Scene.prototype.unbindEvents = function(eventObject) {
      if (eventObject == null || eventObject === '*') {
        this.DOMEvents = {};
        return;
      }
      
      if (Array.isArray(eventObject)) {
        for (var i = 0, j = eventObject.length; i < j; i++) {
          if (this.DOMEvents[eventObject[i]]) {
            delete this.DOMEvents[eventObject[i]];
          }
        }
      } else {
        for (var key in eventObject) {
          if (Object.hasOwnProperty.call(eventObject, key)) {
            var value = eventObject[key];
            
            if (Array.isArray(value)) {
              for (var k = 0, l = value.length; k < l; k++) {
                delete this.DOMEvents[key][value[k]];
              }
            } else {
              delete this.DOMEvents[key][value];
            }
          }
        }
      }
    };

    /**
     * Gets localized value
     *
     * @param {Object} lang
     */
    Scene.prototype.t = function() {
      if (this.localization && this.localization.t) {
        return this.localization.t.apply(this.localization, arguments);
      } else {
        return;
      }
    };

    /**
     * Logging directly from the scene
     *
     * @param {Object} text
     */
    Scene.prototype.log = function(text) {
      Log.i('Scene ' + this.name + ': ' + text);
    };

    Scene.requireAlways = {
      // Third-party modules
      'jquery': '$',
      
      // Lyria modules
      'lyria/achievement': 'Lyria.Achievement',
      'lyria/achievement/manager': 'Lyria.AchievementManager',
      'lyria/animation': 'Lyria.Animation',
      'lyria/audio': 'Lyria.Audio',
      'lyria/checkpoints': 'Lyria.Checkpoints',
      'lyria/component': 'Lyria.Component',
      'lyria/events': 'Lyria.Events',
      'lyria/gameobject': 'Lyria.GameObject',
      'lyria/log': 'Lyria.Log',
      'lyria/loop': 'Lyria.Loop',
      'lyria/prefab/manager': 'Lyria.PrefabManager',
      'lyria/resource': 'Lyria.Resource',
      'lyria/tween': 'Lyria.Tween'
    };

    return Scene;

  })();

  return Scene;

});