c-frame / aframe-extras

Add-ons and helpers for A-Frame VR.
https://c-frame.github.io/aframe-extras/examples/
MIT License
971 stars 305 forks source link

event property type #144

Open donmccurdy opened 7 years ago

donmccurdy commented 7 years ago

Nice feature to have would be:

<a-entity event-set="event: statechanged | detail.state === 'grabbed';
                     prop: ...;
                     value: ...;"></a-entity>

<a-entity fire-the-missiles="event: keypress | key === 'Q';"></a-entity>
AFRAME.registerComponent('event-set', {
  schema: {
    event: {type: 'event'},
    prop: {default: ''},
    value: {default: ''}
  },
  init: function () {
    this.fire = this.fire.bind(this);
  },
  update: function (prevData) {
    var el = this.el;
    var data = this.data;
    if (data.event !== prevData.event) {
      if (prevData.event) prevData.event.unlisten(el, this.fire);
      if (data.event) data.event.listen(el, this.fire);
    }
  },
  remove: function () {
    if (prevData.event) prevData.event.unlisten(this.el, this.fire);
  },
  fire: function () {
    // do stuff.
  }
});
donmccurdy commented 7 years ago

Untested implementation:

var FilteredEventListener = require('./FilteredEventListener');

var DEFAULT_LISTENER = {
  listen: function () {},
  unlisten: function () {},
  expression: ''
};

var name = 'event';

var defaultValue = DEFAULT_LISTENER;

var parse = (function () {
  var memo = {};
  return function parse (string) {
    string = string.trim();

    if (!string) return DEFAULT_LISTENER;

    if (!memo[string]) {
      memo[string] = new FilteredEventListener(string);
    }

    return memo[string];
  };
}());

var stringify = function stringify (listener) {
  return listener.input;
};

module.exports = AFRAME.registerPropertyType(name, defaultValue, parse, stringify);
var expr = require('expression-eval');

function FilteredEventListener (input) {
  this.input = input;

  var parts = input.match(/^([^|]+)(?: \|(.*))?$/);
  this.type = parts[1];
  this.expression = parts[2];

  if (!this.type) {
    throw new Error('[filtered-event] Could not parse: "' + input + '"');
  }

  if (this.expression) {
    this.evaluator = expr.compile(this.expression);
  } else {
    this.evaluator = function () { return true; };
  }

  this.nodeMap = new WeakMap();
  this.nodeCounter = 0;

  this.callbackMap = new WeakMap();
  this.callbackCounter = 0;

  this.listenerMap = new Map();
}

FilteredEventListener.prototype.listen = function (el, callback) {
  var listeners = this.getListeners(el, callback);
  var listener = function (e) {
    if (this.evaluator(e)) callback(e);
  }.bind(this);
  el.addEventListener(this.type, listener);
  listeners.push(listener);
};

FilteredEventListener.prototype.unlisten = function (el, callback) {
  var listeners = this.getListeners(el, callback);
  listeners.forEach(function(listener) {
    el.removeEventListener(this.type, listener);
  }.bind(this));
  listeners.length = 0;
};

FilteredEventListener.prototype.getListeners = function (el, callback) {
  var nodeID = String(this.nodeMap.get(el) || ++this.nodeCounter);
  this.nodeMap.set(el, nodeID);

  var callbackID = String(this.callbackMap.get(callback) || ++this.callbackCounter);
  this.callbackMap.set(callback, callbackID);

  var listenerID = nodeID + ':' + callbackID;

  if (!this.listenerMap.has(listenerID)) {
    this.listenerMap.set(listenerID, []);
  }

  return this.listenerMap.get(listenerID);
};

module.exports = FilteredEventListener;
donmccurdy commented 7 years ago

Done on branch, usefulness TBD: https://github.com/donmccurdy/aframe-extras/tree/feat-state-machine