johnny-five
Version:
The JavaScript Robotics and Hardware Programming Framework. Use with: Arduino (all models), Electric Imp, Beagle Bone, Intel Galileo & Edison, Linino One, Pinoccio, pcDuino3, Raspberry Pi, Particle/Spark Core & Photon, Tessel 2, TI Launchpad and more!
263 lines (216 loc) • 6.03 kB
JavaScript
var IS_TEST_MODE = !!process.env.IS_TEST_MODE;
var Emitter = require("events").EventEmitter;
var priv = new Map();
/**
* Collection
*
* Make Collections for output classes
*
* @param {[type]} numsOrObjects
*/
function Collection(numsOrObjects) {
var Type = this.type;
var initObjects = [];
this.length = 0;
if (Array.isArray(numsOrObjects)) {
initObjects = numsOrObjects;
} else {
// Initialize with a Shared Properties object
/* istanbul ignore else */
if (Array.isArray(numsOrObjects.pins)) {
var keys = Object.keys(numsOrObjects).filter(function(key) {
return key !== "pins";
});
initObjects = numsOrObjects.pins.map(function(pin) {
var obj = {};
if (Array.isArray(pin)) {
obj.pins = pin;
} else {
obj.pin = pin;
}
return keys.reduce(function(accum, key) {
accum[key] = numsOrObjects[key];
return accum;
}, obj);
});
}
}
/* istanbul ignore else */
if (initObjects.length) {
while (initObjects.length) {
var numOrObject = initObjects.shift();
// When a Type exists, respect it!
if (typeof Type === "function") {
if (!(numOrObject instanceof Type || numOrObject instanceof this.constructor)) {
numOrObject = new Type(numOrObject);
}
}
this.add(numOrObject);
}
}
}
Collection.prototype.add = function() {
var length = this.length;
var aLen = arguments.length;
for (var i = 0; i < aLen; i++) {
// When a Type exists, respect it!
if (this.type) {
if (arguments[i] instanceof this.type ||
arguments[i] instanceof this.constructor) {
this[length++] = arguments[i];
}
} else {
// Otherwise allow user to directly instantiate
// Collection or Collection.Emitter to create
// a mixed collection
this[length++] = arguments[i];
}
}
return (this.length = length);
};
Collection.prototype.each = function(callbackFn) {
var length = this.length;
for (var i = 0; i < length; i++) {
callbackFn.call(this[i], this[i], i);
}
return this;
};
Collection.prototype.forEach = function() {
[].forEach.apply(this, arguments);
};
Collection.prototype.includes = function() {
return [].includes.apply(this, arguments);
};
Collection.prototype.indexOf = function() {
return [].indexOf.apply(this, arguments);
};
Collection.prototype.map = function() {
return [].map.apply(this, arguments);
};
Collection.prototype.slice = function() {
return new this.constructor([].slice.apply(this, arguments));
};
Collection.prototype.byId = function(id) {
return [].find.call(this, function(entry) {
return entry.id !== undefined && entry.id === id;
});
};
/**
* Collection.installMethodForwarding
*
* Copy single method to collection class
*
* @param {Object} target Target prototype
* @param {Object} source Source prototype
* @return {Object} target Modified Target prototype
*/
Collection.installMethodForwarding = function(target, source) {
return Object.keys(source).reduce(function(accum, method) {
// Create Inputs wrappers for each method listed.
// This will allow us control over all Input instances
// simultaneously.
accum[method] = function() {
var length = this.length;
for (var i = 0; i < length; i++) {
this[i][method].apply(this[i], arguments);
}
return this;
};
return accum;
}, target);
};
/**
* Collection.Emitter
*
* Make Collections for input classes
*
* @param {[type]} numsOrObjects
*
*/
Collection.Emitter = function(numsOrObjects) {
// Create private state ahead of super call
priv.set(this, {
timing: {
last: Date.now()
}
});
Collection.call(this, numsOrObjects);
// If the Collection.Emitter was created
// with a Shared Properties object, then
// we should abide by the freq or period
// properties...
var interval = null;
var period = 5;
if (!Array.isArray(numsOrObjects) &&
(typeof numsOrObjects === "object" && numsOrObjects !== null)) {
period = numsOrObjects.freq || numsOrObjects.period || period;
// _However_, looking to the future, we
// need to start thinking about replacing
// the garbage named _freq_ (the value is
// actually a period), with real _frequency_
// in Hz.
// If provided, convert frequency to period
/* istanbul ignore else */
if (numsOrObjects.frequency) {
period = (1 / numsOrObjects.frequency) * 1000;
}
}
Object.defineProperties(this, {
period: {
get: function() {
return period;
},
set: function(value) {
if (period !== value) {
period = value;
}
if (interval) {
clearInterval(interval);
}
interval = setInterval(function() {
this.emit("data", this);
}.bind(this), period);
}
},
});
this.period = period;
this.on("newListener", function(event) {
if (event === "change" || event === "data") {
return;
}
this.forEach(function(input) {
input.on(event, function(data) {
this.emit(event, input, data);
}.bind(this));
}, this);
});
};
Collection.Emitter.prototype = Object.create(Collection.prototype, {
constructor: {
value: Collection.Emitter
}
});
Object.assign(Collection.Emitter.prototype, Emitter.prototype);
Collection.Emitter.prototype.add = function() {
var inputs = Array.from(arguments);
/* istanbul ignore else */
if (inputs.length) {
Collection.prototype.add.apply(this, inputs);
inputs.forEach(function(input) {
if (input) {
input.on("change", function() {
this.emit("change", input);
}.bind(this));
}
}, this);
}
return this.length;
// return (this.length = length);
};
/* istanbul ignore else */
if (IS_TEST_MODE) {
Collection.purge = function() {
priv.clear();
};
}
module.exports = Collection;