MSV FM

dot.antimicrobial@66.96.161.157: ~ $
Path : /hermes/sb_web/b2680/summitdaymedia.com/demos/aframe-master/src/core/
File Upload :
Current < : /hermes/sb_web/b2680/summitdaymedia.com/demos/aframe-master/src/core/component.js

/* global Node */
var schema = require('./schema');
var scenes = require('./scene/scenes');
var systems = require('./system');
var utils = require('../utils/');

var components = module.exports.components = {};  // Keep track of registered components.
var parseProperties = schema.parseProperties;
var parseProperty = schema.parseProperty;
var processSchema = schema.process;
var isSingleProp = schema.isSingleProperty;
var stringifyProperties = schema.stringifyProperties;
var stringifyProperty = schema.stringifyProperty;
var styleParser = utils.styleParser;
var warn = utils.debug('core:component:warn');

var aframeScript = document.currentScript;
var upperCaseRegExp = new RegExp('[A-Z]+');

/**
 * Component class definition.
 *
 * Components configure appearance, modify behavior, or add functionality to
 * entities. The behavior and appearance of an entity can be changed at runtime
 * by adding, removing, or updating components. Entities do not share instances
 * of components.
 *
 * @member {object} el - Reference to the entity element.
 * @member {string} attrValue - Value of the corresponding HTML attribute.
 * @member {object} data - Component data populated by parsing the
 *         mapped attribute of the component plus applying defaults and mixins.
 */
var Component = module.exports.Component = function (el, attrValue, id) {
  var self = this;
  this.el = el;
  this.id = id;
  this.attrName = this.name + (id ? '__' + id : '');
  this.evtDetail = {id: this.id, name: this.name};
  this.initialized = false;
  this.el.components[this.attrName] = this;

  // Store component data from previous update call.
  this.oldData = undefined;

  // Last value passed to updateProperties.
  this.previousAttrValue = undefined;
  this.throttledEmitComponentChanged = utils.throttle(function emitChange () {
    el.emit('componentchanged', self.evtDetail, false);
  }, 200);
  this.updateProperties(attrValue);
};

Component.prototype = {
  /**
   * Contains the type schema and defaults for the data values.
   * Data is coerced into the types of the values of the defaults.
   */
  schema: {},

  /**
   * Init handler. Similar to attachedCallback.
   * Called during component initialization and is only run once.
   * Components can use this to set initial state.
   */
  init: function () { /* no-op */ },

  /**
   * Update handler. Similar to attributeChangedCallback.
   * Called whenever component's data changes.
   * Also called on component initialization when the component receives initial data.
   *
   * @param {object} prevData - Previous attributes of the component.
   */
  update: function (prevData) { /* no-op */ },

  updateSchema: undefined,

  /**
   * Tick handler.
   * Called on each tick of the scene render loop.
   * Affected by play and pause.
   *
   * @param {number} time - Scene tick time.
   * @param {number} timeDelta - Difference in current render time and previous render time.
   */
  tick: undefined,

  /**
   * Tock handler.
   * Called on each tock of the scene render loop.
   * Affected by play and pause.
   *
   * @param {number} time - Scene tick time.
   * @param {number} timeDelta - Difference in current render time and previous render time.
   * @param {object} camera - Camera used to render the last frame.
   */
  tock: undefined,

  /**
   * Called to start any dynamic behavior (e.g., animation, AI, events, physics).
   */
  play: function () { /* no-op */ },

  /**
   * Called to stop any dynamic behavior (e.g., animation, AI, events, physics).
   */
  pause: function () { /* no-op */ },

  /**
   * Remove handler. Similar to detachedCallback.
   * Called whenever component is removed from the entity (i.e., removeAttribute).
   * Components can use this to reset behavior on the entity.
   */
  remove: function () { /* no-op */ },

  /**
   * Parses each property based on property type.
   * If component is single-property, then parses the single property value.
   *
   * @param {string} value - HTML attribute value.
   * @param {boolean} silent - Suppress warning messages.
   * @returns {object} Component data.
   */
  parse: function (value, silent) {
    var schema = this.schema;
    if (isSingleProp(schema)) { return parseProperty(value, schema); }
    return parseProperties(styleParser.parse(value), schema, true, this.name, silent);
  },

  /**
   * Stringify properties if necessary.
   *
   * Only called from `Entity.setAttribute` for properties whose parsers accept a non-string
   * value (e.g., selector, vec3 property types).
   *
   * @param {object} data - Complete component data.
   * @returns {string}
   */
  stringify: function (data) {
    var schema = this.schema;
    if (typeof data === 'string') { return data; }
    if (isSingleProp(schema)) { return stringifyProperty(data, schema); }
    data = stringifyProperties(data, schema);
    return styleParser.stringify(data);
  },

  /**
   * Update the cache of the pre-parsed attribute value.
   *
   * @param {string} value - New data.
   * @param {boolean } clobber - Whether to wipe out and replace previous data.
   */
  updateCachedAttrValue: function (value, clobber) {
    var attrValue = this.parseAttrValueForCache(value);
    var isSinglePropSchema = isSingleProp(this.schema);
    var property;
    if (value === undefined) { return; }

    // Merge new data with previous `attrValue` if updating and not clobbering.
    if (!isSinglePropSchema && !clobber) {
      this.attrValue = this.attrValue ? cloneData(this.attrValue) : {};
      for (property in attrValue) {
        this.attrValue[property] = attrValue[property];
      }
      return;
    }

    // If single-prop schema or clobber.
    this.attrValue = attrValue;
  },

  /**
   * Given an HTML attribute value parses the string
   * based on the component schema. To avoid double parsings of
   * strings into strings we store the original instead
   * of the parsed one
   *
   * @param {string} value - HTML attribute value
   */
  parseAttrValueForCache: function (value) {
    var parsedValue;
    if (typeof value !== 'string') { return value; }
    if (isSingleProp(this.schema)) {
      parsedValue = this.schema.parse(value);
      /**
       * To avoid bogus double parsings. Cached values will be parsed when building
       * component data. For instance when parsing a src id to its url, we want to cache
       * original string and not the parsed one (#monster -> models/monster.dae)
       * so when building data we parse the expected value.
       */
      if (typeof parsedValue === 'string') { parsedValue = value; }
    } else {
      // Parse using the style parser to avoid double parsing of individual properties.
      parsedValue = styleParser.parse(value);
    }
    return parsedValue;
  },

  /**
   * Write cached attribute data to the entity DOM element.
   *
   * @param {boolean} isDefault - Whether component is a default component. Always flush for
   *   default components.
   */
  flushToDOM: function (isDefault) {
    var attrValue = isDefault ? this.data : this.attrValue;
    if (!attrValue) { return; }
    window.HTMLElement.prototype.setAttribute.call(this.el, this.attrName,
                                            this.stringify(attrValue));
  },

  /**
   * Apply new component data if data has changed.
   *
   * @param {string} attrValue - HTML attribute value.
   *        If undefined, use the cached attribute value and continue updating properties.
   * @param {boolean} clobber - The previous component data is overwritten by the atrrValue
   */
  updateProperties: function (attrValue, clobber) {
    var el = this.el;
    var isSinglePropSchema;
    var key;
    var skipTypeChecking;
    var oldData = this.oldData;

    // Just cache the attribute if the entity has not loaded
    // Components are not initialized until the entity has loaded
    if (!el.hasLoaded) {
      this.updateCachedAttrValue(attrValue);
      return;
    }

    isSinglePropSchema = isSingleProp(this.schema);

    // Disable type checking if the passed attribute is an object and has not changed.
    skipTypeChecking = attrValue !== null && typeof this.previousAttrValue === 'object' &&
                       attrValue === this.previousAttrValue;
    if (skipTypeChecking) {
      for (key in this.attrValue) {
        if (!(key in attrValue)) {
          skipTypeChecking = false;
          break;
        }
      }
      for (key in attrValue) {
        if (!(key in this.attrValue)) {
          skipTypeChecking = false;
          break;
        }
      }
    }

    // Cache previously passed attribute to decide if we skip type checking.
    this.previousAttrValue = attrValue;

    // Cache current attrValue for future updates. Updates `this.attrValue`.
    attrValue = this.parseAttrValueForCache(attrValue);
    this.updateCachedAttrValue(attrValue, clobber);

    if (this.updateSchema) {
      this.updateSchema(this.buildData(this.attrValue, false, true));
    }
    this.data = this.buildData(this.attrValue, clobber, false, skipTypeChecking);

    if (!this.initialized) {
      // Component is being already initialized.
      if (el.initializingComponents[this.name]) { return; }
      // Prevent infinite loop in case of init method setting same component on the entity.
      el.initializingComponents[this.name] = true;
      // Initialize component.
      this.init();
      this.initialized = true;
      delete el.initializingComponents[this.name];
      // For oldData, pass empty object to multiple-prop schemas or object single-prop schema.
      // Pass undefined to rest of types.
      oldData = (!isSinglePropSchema ||
                 typeof parseProperty(undefined, this.schema) === 'object') ? {} : undefined;
      // Store current data as previous data for future updates.
      this.oldData = extendProperties({}, this.data, isSinglePropSchema);
      this.update(oldData);
      // Play the component if the entity is playing.
      if (el.isPlaying) { this.play(); }
      el.emit('componentinitialized', this.evtDetail, false);
    } else {
      // Don't update if properties haven't changed
      if (utils.deepEqual(this.oldData, this.data)) { return; }
     // Store current data as previous data for future updates.
      this.oldData = extendProperties({}, this.data, isSinglePropSchema);
      // Update component.
      this.update(oldData);
      this.throttledEmitComponentChanged();
    }
  },

  /**
   * Reset value of a property to the property's default value.
   * If single-prop component, reset value to component's default value.
   *
   * @param {string} propertyName - Name of property to reset.
   */
  resetProperty: function (propertyName) {
    if (isSingleProp(this.schema)) {
      this.attrValue = undefined;
    } else {
      if (!(propertyName in this.attrValue)) { return; }
      delete this.attrValue[propertyName];
    }
    this.updateProperties(this.attrValue);
  },

  /**
   * Extend schema of component given a partial schema.
   *
   * Some components might want to mutate their schema based on certain properties.
   * e.g., Material component changes its schema based on `shader` to account for different
   * uniforms
   *
   * @param {object} schemaAddon - Schema chunk that extend base schema.
   */
  extendSchema: function (schemaAddon) {
    // Clone base schema.
    var extendedSchema = utils.extend({}, components[this.name].schema);
    // Extend base schema with new schema chunk.
    utils.extend(extendedSchema, schemaAddon);
    this.schema = processSchema(extendedSchema);
    this.el.emit('schemachanged', {component: this.name});
  },

  /**
   * Builds component data from the current state of the entity, ultimately
   * updating this.data.
   *
   * If the component was detached completely, set data to null.
   *
   * Precedence:
   * 1. Defaults data
   * 2. Mixin data.
   * 3. Attribute data.
   *
   * Finally coerce the data to the types of the defaults.
   *
   * @param {object} newData - Element new data.
   * @param {boolean} clobber - The previous data is completely replaced by the new one.
   * @param {boolean} silent - Suppress warning messages.
   * @param {boolean} skipTypeChecking - Skip type checking and cohercion.
   * @return {object} The component data
   */
  buildData: function (newData, clobber, silent, skipTypeChecking) {
    var componentDefined;
    var data;
    var defaultValue;
    var keys;
    var keysLength;
    var mixinData;
    var schema = this.schema;
    var i;
    var isSinglePropSchema = isSingleProp(schema);
    var mixinEls = this.el.mixinEls;
    var previousData;

    // Whether component has a defined value. For arrays, treat empty as not defined.
    componentDefined = newData && newData.constructor === Array
      ? newData.length
      : newData !== undefined && newData !== null;

    // 1. Default values (lowest precendence).
    if (isSinglePropSchema) {
      // Clone default value if plain object so components don't share the same object
      // that might be modified by the user.
      data = isObjectOrArray(schema.default) ? utils.clone(schema.default) : schema.default;
    } else {
      // Preserve previously set properties if clobber not enabled.
      previousData = !clobber && this.attrValue;
      // Clone previous data to prevent sharing references with attrValue that might be
      // modified by the user.
      data = typeof previousData === 'object' ? cloneData(previousData) : {};

      // Apply defaults.
      for (i = 0, keys = Object.keys(schema), keysLength = keys.length; i < keysLength; i++) {
        defaultValue = schema[keys[i]].default;
        if (data[keys[i]] !== undefined) { continue; }
        // Clone default value if object so components don't share object
        data[keys[i]] = isObjectOrArray(defaultValue) ? utils.clone(defaultValue) : defaultValue;
      }
    }

    // 2. Mixin values.
    for (i = 0; i < mixinEls.length; i++) {
      mixinData = mixinEls[i].getAttribute(this.attrName);
      if (mixinData) {
        data = extendProperties(data, mixinData, isSinglePropSchema);
      }
    }

    // 3. Attribute values (highest precendence).
    if (componentDefined) {
      if (isSinglePropSchema) {
        if (skipTypeChecking === true) { return newData; }
        return parseProperty(newData, schema);
      }
      data = extendProperties(data, newData, isSinglePropSchema);
    } else {
      if (skipTypeChecking === true) { return data; }
      // Parse and coerce using the schema.
      if (isSinglePropSchema) { return parseProperty(data, schema); }
    }

    if (skipTypeChecking === true) { return data; }
    return parseProperties(data, schema, undefined, this.name, silent);
  }
};

// For testing.
if (window.debug) {
  var registrationOrderWarnings = module.exports.registrationOrderWarnings = {};
}

/**
 * Registers a component to A-Frame.
 *
 * @param {string} name - Component name.
 * @param {object} definition - Component schema and lifecycle method handlers.
 * @returns {object} Component.
 */
module.exports.registerComponent = function (name, definition) {
  var NewComponent;
  var proto = {};

  // Warning if component is statically registered after the scene.
  if (document.currentScript && document.currentScript !== aframeScript) {
    scenes.forEach(function checkPosition (sceneEl) {
      // Okay to register component after the scene at runtime.
      if (sceneEl.hasLoaded) { return; }

      // Check that component is declared before the scene.
      if (document.currentScript.compareDocumentPosition(sceneEl) ===
          Node.DOCUMENT_POSITION_FOLLOWING) { return; }

      warn('The component `' + name + '` was registered in a <script> tag after the scene. ' +
           'Component <script> tags in an HTML file should be declared *before* the scene ' +
           'such that the component is available to entities during scene initialization.');

      // For testing.
      if (window.debug) { registrationOrderWarnings[name] = true; }
    });
  }

  if (upperCaseRegExp.test(name) === true) {
    warn('The component name `' + name + '` contains uppercase characters, but ' +
         'HTML will ignore the capitalization of attribute names. ' +
         'Change the name to be lowercase: `' + name.toLowerCase() + '`');
  }

  if (name.indexOf('__') !== -1) {
    throw new Error('The component name `' + name + '` is not allowed. ' +
                    'The sequence __ (double underscore) is reserved to specify an id' +
                    ' for multiple components of the same type');
  }

  // Format definition object to prototype object.
  Object.keys(definition).forEach(function (key) {
    proto[key] = {
      value: definition[key],
      writable: true
    };
  });

  if (components[name]) {
    throw new Error('The component `' + name + '` has been already registered. ' +
                    'Check that you are not loading two versions of the same component ' +
                    'or two different components of the same name.');
  }
  NewComponent = function (el, attr, id) {
    Component.call(this, el, attr, id);
  };

  NewComponent.prototype = Object.create(Component.prototype, proto);
  NewComponent.prototype.name = name;
  NewComponent.prototype.constructor = NewComponent;
  NewComponent.prototype.system = systems && systems.systems[name];
  NewComponent.prototype.play = wrapPlay(NewComponent.prototype.play);
  NewComponent.prototype.pause = wrapPause(NewComponent.prototype.pause);

  components[name] = {
    Component: NewComponent,
    dependencies: NewComponent.prototype.dependencies,
    isSingleProp: isSingleProp(NewComponent.prototype.schema),
    multiple: NewComponent.prototype.multiple,
    parse: NewComponent.prototype.parse,
    parseAttrValueForCache: NewComponent.prototype.parseAttrValueForCache,
    schema: utils.extend(processSchema(NewComponent.prototype.schema, NewComponent.prototype.name)),
    stringify: NewComponent.prototype.stringify,
    type: NewComponent.prototype.type
  };
  return NewComponent;
};

/**
* Clone component data.
* Clone only the properties that are plain objects while keeping a reference for the rest.
*
* @param data - Component data to clone.
* @returns Cloned data.
*/
function cloneData (data) {
  var clone = {};
  var parsedProperty;
  var key;
  for (key in data) {
    parsedProperty = data[key];
    clone[key] = isObjectOrArray(parsedProperty) ? utils.clone(parsedProperty) : parsedProperty;
  }
  return clone;
}

/**
* Object extending with checking for single-property schema.
*
* @param dest - Destination object or value.
* @param source - Source object or value
* @param {boolean} isSinglePropSchema - Whether or not schema is only a single property.
* @returns Overridden object or value.
*/
function extendProperties (dest, source, isSinglePropSchema) {
  if (isSinglePropSchema && (source === null || typeof source !== 'object')) { return source; }
  var copy = utils.extend(dest, source);
  var key;
  for (key in copy) {
    if (!copy[key] || copy[key].constructor !== Object) { continue; }
    copy[key] = utils.clone(copy[key]);
  }
  return copy;
}

/**
 * Checks if a component has defined a method that needs to run every frame.
 */
function hasBehavior (component) {
  return component.tick || component.tock;
}

/**
 * Wrapper for user defined pause method
 * Pause component by removing tick behavior and calling user's pause method.
 *
 * @param pauseMethod {function} - user defined pause method
 */
function wrapPause (pauseMethod) {
  return function pause () {
    var sceneEl = this.el.sceneEl;
    if (!this.isPlaying) { return; }
    pauseMethod.call(this);
    this.isPlaying = false;
    // Remove tick behavior.
    if (!hasBehavior(this)) { return; }
    sceneEl.removeBehavior(this);
  };
}

/**
 * Wrapper for user defined play method
 * Play component by adding tick behavior and calling user's play method.
 *
 * @param playMethod {function} - user defined play method
 *
 */
function wrapPlay (playMethod) {
  return function play () {
    var sceneEl = this.el.sceneEl;
    var shouldPlay = this.el.isPlaying && !this.isPlaying;
    if (!this.initialized || !shouldPlay) { return; }
    playMethod.call(this);
    this.isPlaying = true;
    // Add tick behavior.
    if (!hasBehavior(this)) { return; }
    sceneEl.addBehavior(this);
  };
}

function isObjectOrArray (value) {
  return value && (value.constructor === Object || value.constructor === Array);
}