Last active
December 14, 2015 22:34
-
-
Save Skateside/255a48439811b5b86f3d to your computer and use it in GitHub Desktop.
Getter/Setter object in JavaScript. Loosely based on the Varien_Object in Magento
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* access | |
* | |
* Created by calling the `Access` function. | |
* | |
* var a1 = Access(); | |
* var a2 = new Access(); | |
* | |
* Initial data can be passed, see [[access.addData]]. | |
* | |
* [[access]] objects have some pre-set methods but their main strength comes | |
* from the ability to access internal data using `get*`, `set*`, `has*` and | |
* `delete*` methods. | |
* | |
* For example: | |
* | |
* access.hasThing(); // -> false | |
* access.getThing(); // -> undefined | |
* access.setThing('thing'); // -> access | |
* access.hasThing(); // -> true | |
* access.getThing(); // -> "thing" | |
* access.deleteThing(); // -> true | |
* access.hasThing(); // -> false | |
* | |
* The internal data can be viewed using [[access.debug]], if needed. | |
**/ | |
function Access(initial) { | |
'use strict'; | |
var access = {}, | |
internalData = {}; | |
function decamelise(string) { | |
return util.String.hyphenate(util.String.toLowerFirst(string), '_'); | |
} | |
/** | |
* access.getData(name) -> ? | |
* - name (String): Data property to retrieve. | |
**/ | |
function getData(name) { | |
return internalData[name]; | |
} | |
/** | |
* access.setData(name, value) -> access | |
* - name (String): Data property to set. | |
* - value (?): Value for the property. | |
**/ | |
function setData(name, value) { | |
internalData[name] = value; | |
return access; | |
} | |
/** | |
* access.addData(data) | |
* - data (Object): Data to set. | |
* | |
* These two statements are equivalent. | |
* | |
* access.setData('one', 1).setData('two', 2); | |
* access.addData({one: 1, two: 2}) | |
* | |
**/ | |
function addData(data) { | |
util.Object.assign(internalData, data); | |
} | |
/** | |
* access.hasData(name) -> Boolean | |
* - name (String): Data property to check. | |
**/ | |
function hasData(name) { | |
return util.Object.owns(internalData, name); | |
} | |
/** | |
* access.deleteData(name) -> Boolean | |
* - name (String): Data property to delete. | |
* | |
* Returns `true` if data was deleted and `false` otherwise. | |
**/ | |
function deleteData(name) { | |
var had = hasData(name); | |
delete internalData[name]; | |
return had; | |
} | |
/** | |
* access.debug() -> Object | |
* | |
* Returns a copy of the internal data to aid debugging. | |
**/ | |
function debug() { | |
return util.Object.clone(internalData); | |
} | |
util.Object.assign(access, { | |
getData, | |
setData, | |
addData, | |
hasData, | |
deleteData, | |
debug | |
}); | |
access = new Proxy(access, { | |
get: function (target, name) { | |
var value, | |
rule = name.match(/(^([a-z]+)(\w+))/), | |
property; | |
if (util.Object.owns(target, name)) { | |
value = target[name]; | |
} else if (rule && rule.length) { | |
property = decamelise(rule[3]); | |
switch (rule[2]) { | |
case 'get': | |
target[name] = function () { | |
return target.getData(property); | |
}; | |
break; | |
case 'set': | |
target[name] = function (val) { | |
return target.setData(property, val); | |
}; | |
break; | |
case 'has': | |
target[name] = function () { | |
return target.hasData(property); | |
}; | |
break; | |
case 'delete': | |
target[name] = function () { | |
return target.deleteData(property); | |
}; | |
break; | |
} | |
value = target[name]; | |
} | |
return value; | |
} | |
}); | |
if (util.Object.isObject(initial)) { | |
addData(initial); | |
} | |
return access; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* class InheritableAccess | |
* | |
* Variant of [[access]] that allows for inheritance. Magic `get`, `set`, `has` | |
* and `delete` methods are still available. | |
**/ | |
var InheritableAccess = (function () { | |
'use strict'; | |
var internalData = new WeakMap(); | |
var getData = function (instance) { | |
if (!internalData.has(instance)) { | |
internalData.set(instance, {}); | |
} | |
return internalData.get(instance); | |
}; | |
var decamelise = function (string) { | |
return util.String.hyphenate(util.String.toLowerFirst(string), '_'); | |
}; | |
var InAccess = function (...args) { | |
return this.init(...args); | |
}; | |
InAccess.prototype = { | |
/** | |
* new InheritableAccess([initial]) | |
* - initial (Object): Optional initial data. | |
* | |
* Creates the access object. Initial data can optionally be set. | |
* | |
* var access1 = new InheritableAccess(); | |
* access1.getSomething(); // -> undefined | |
* var access2 = new InheritableAccess({something: true}); | |
* access2.getSomething(); // -> true | |
* | |
**/ | |
init: function (initial) { | |
if (util.Object.isObject(initial)) { | |
this.addData(initial); | |
} | |
}, | |
/** | |
* InheritableAccess#getData(key) -> ? | |
* - key (String): Data key. | |
* | |
* Returns data from the private data or `undefined` if no data can be | |
* found. | |
* | |
* var access = new InheritableAccess({something: true}); | |
* access.getData('something'); // -> true | |
* access.getData('something_else'); // -> undefined | |
* | |
**/ | |
getData: function (key) { | |
return getData(this)[key]; | |
}, | |
/** | |
* InheritableAccess#setData(key, value) -> InheritableAccess | |
* - key (String): Data key. | |
* - value (?): Data value. | |
* | |
* Sets internal data. The instance is returned so it can be chained. | |
* | |
* var access = new InheritableAccess(); | |
* access.hasData('something'); // -> false | |
* access.setData('something', true); // -> access | |
* access.hasData('something'); // -> true | |
* | |
**/ | |
setData: function (key, value) { | |
getData(this)[key] = value; | |
return this; | |
}, | |
/** | |
* InheritableAccess#hasData(key) -> Boolean | |
* - key (String): Data key. | |
* | |
* Checks to see if the given `key` has any associated data. | |
* | |
* var access = new InheritableAccess(); | |
* access.setData('something', true); | |
* access.setData('something_else', undefined); | |
* access.hasData('something'); // -> true | |
* access.hasData('something_else'); // -> true | |
* access.hasData('a_third_something'); // -> false | |
* | |
**/ | |
hasData: function (key) { | |
return util.Object.owns(getData(this), key); | |
}, | |
/** | |
* InheritableAccess#deleteData(key) -> Boolean | |
* - key (String): Data key. | |
* | |
* Deletes data from the private data. Returns `true` if data was | |
* removed and `false` otherwise. | |
* | |
* var access = new InheritableAccess(); | |
* access.setData('something', true); | |
* access.deleteData('something'); // -> true | |
* access.deleteData('something_else'); // -> false | |
* | |
**/ | |
deleteData: function (key) { | |
var had = this.hasData(key); | |
delete getData(this)[key]; | |
return had; | |
}, | |
/** | |
* InheritableAccess#addData(data) | |
* - data (Object): Data to add. | |
* | |
* Adds data to the instance. | |
* | |
* var access = new InheritableAccess(); | |
* access.addData({something: true, something_else: false}); | |
* access.getData('something'); // -> true | |
* access.getData('something_else'); // -> false | |
* | |
**/ | |
addData: function (data) { | |
Object.keys(data).forEach(function (key) { | |
this.setData(key, data[key]); | |
}, this); | |
}, | |
/** | |
* InheritableAccess#debug() -> Object | |
* | |
* Returns a copy of the private data to aid debugging. Although it | |
* should be possible to modify the copy without affecting the actual | |
* private data, this cannot be guarenteed. | |
* | |
* var access = new InheritableAccess(); | |
* access.setData('something', true); | |
* access.debug(); // -> {something: true} | |
* | |
**/ | |
debug: function () { | |
return util.Object.clone(getData(this)); | |
} | |
}; | |
InAccess.prototype = new Proxy(InAccess.prototype, { | |
get: function (target, name) { | |
var value, | |
rule = name.match(/(^([a-z]+)(\w+))/), | |
property; | |
if (util.Object.owns(target, name)) { | |
value = target[name]; | |
} else if (rule && rule.length) { | |
property = decamelise(rule[3]); | |
switch (rule[2]) { | |
case 'get': | |
target[name] = function () { | |
return this.getData(property); | |
}; | |
break; | |
case 'set': | |
target[name] = function (val) { | |
return this.setData(property, val); | |
}; | |
break; | |
case 'has': | |
target[name] = function () { | |
return this.hasData(property); | |
}; | |
break; | |
case 'delete': | |
target[name] = function () { | |
return this.deleteData(property); | |
}; | |
break; | |
} | |
value = target[name]; | |
} | |
return value; | |
} | |
}); | |
return InAccess; | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* util | |
* | |
* Utility functions. | |
**/ | |
var util = (function () { | |
'use strict'; | |
var utilities = { | |
/** | |
* util.Array | |
* | |
* Functions for manipulating Arrays | |
**/ | |
Array: {}, | |
/** | |
* util.Function | |
* | |
* Functions for manipulating Functions | |
**/ | |
Function: {}, | |
/** | |
* util.Number | |
* | |
* Functions.for manipulating Numbers. | |
**/ | |
Number: {}, | |
/** | |
* util.Object | |
* | |
* Functions for manipulating Objects. | |
**/ | |
Object: {}, | |
/** | |
* util.String | |
* | |
* Functions for manipulating Strings. | |
**/ | |
String: {} | |
}; | |
/** | |
* util.Object.assign(source, ...objects) -> Object | |
* - source (Object): Object to extend. | |
* - ...objects (Object): Objects to exetend with. | |
* | |
* Extends the `source` object with the other `objects`. Just a basic | |
* fallback for the native `Object.assign`. | |
**/ | |
var assign = Object.assign || function (source, ...objects) { | |
objects.forEach(function (object) { | |
Object.keys(object).forEach(function (key) { | |
source[key] = object[key]; | |
}); | |
}); | |
return source; | |
}; | |
/** | |
* util.Function.identity(x) -> ? | |
* - x (?): Object to return. | |
* | |
* Returns an object without modifying it. This exists mainly as a | |
* fallback. | |
**/ | |
var identity = function (x) { | |
return x; | |
}; | |
/** | |
* util.Object.getClass(object) -> String | |
* - object (?): Object whose class should be returned. | |
* | |
* Returns the class of the object as defined in the ECMAScript specs. | |
* | |
* util.Object.getClass({}); // -> "Object" | |
* util.Object.getClass([]); // -> "Array" | |
* util.Object.getClass(''); // -> "String" | |
* util.Object.getClass(); // -> "Undefined" | |
* | |
**/ | |
var getClass = function (object) { | |
var string = Object.prototype.toString.call(object); | |
return string.slice(8, -1); | |
}; | |
/** | |
* util.Function.isFunction(func) -> Boolean | |
* - func (Function): Function to test. | |
* | |
* Checks to see if the given `func` is a function. | |
**/ | |
var isFunction = function (func) { | |
return typeof func === 'function' && getClass(func) === 'Function'; | |
}; | |
/** | |
* util.Array.from(object) -> Array | |
* - object (?): Object to convert. | |
* | |
* Converts the given object into an array. | |
* | |
* var divs = document.querySelectorAll('div'); | |
* // -> NodeList[<div id="one">, <div id="two">, <div id="three">] | |
* util.Array.from(divs); | |
* // -> Array[<div id="one">, <div id="two">, <div id="three">] | |
* | |
* Optionally, a `map` may be provided to convert the original object. | |
* | |
* util.Array.from(divs, function (div) { | |
* return div.id; | |
* }); | |
* // -> Array['one', 'two', 'three'] | |
* | |
**/ | |
var arrayFrom = Array.from || function (array, map, context) { | |
if (!isFunction(map)) { | |
map = identity; | |
} | |
return Array.prototype.map.call(array, map, context); | |
}; | |
/** | |
* util.Array.isArrayLike(object) -> Boolean | |
* - object (?): Object to test. | |
* | |
* Tests to see if the given object is array-like. | |
* | |
* util.Array.isArrayLike([]); // -> true | |
* util.Array.isArrayLike(''); // -> true | |
* util.Array.isArrayLike(0); // -> false | |
* util.Array.isArrayLike({}); // -> false | |
* util.Array.isArrayLike({length: 0}); // -> true | |
* util.Array.isArrayLike(document.querySelector('*')); // -> false | |
* util.Array.isArrayLike(document.querySelectorAll('*')); // -> true | |
* | |
**/ | |
function isArrayLike(object) { | |
return object !== undefined && object !== null && | |
isNumeric(object.length); | |
} | |
/** | |
* util.Object.isPlainObject(object) -> Boolean | |
* - object (?): Object to test. | |
* | |
* Test to see whether or not the given `object` is a plain object. | |
* | |
* util.Object.isPlainObject({}); // -> true | |
* util.Object.isPlainObject([]); // -> false | |
* util.Object.isPlainObject(null); // -> false | |
* util.Object.isPlainObject(document.body); // -> false | |
* util.Object.isPlainObject(window); // -> false | |
* | |
**/ | |
function isPlainObject(object) { | |
var isPlain = object !== null && typeof object === 'object' && | |
object !== window && !object.nodeType; | |
if (isPlain) { | |
try { | |
if (!object.constructor || | |
!owns(object.constructor.prototype, 'isPrototypeOf')) { | |
isPlain = false; | |
} | |
} catch(ignore) { | |
} | |
} | |
return isPlain; | |
} | |
/** | |
* util.Object.owns(object, property) -> Boolean | |
* - object (Object): Object to test. | |
* - property (String): Property to check. | |
* | |
* Tests whether an object has the given property. | |
**/ | |
function owns(object, property) { | |
return Object.prototype.hasOwnProperty.call(object, property); | |
} | |
/** | |
* util.Object.clone(object) -> Object | |
* - object (Object): Object to clone. | |
* | |
* Creates a clone of an object such that modifying the close should not | |
* modify the original. Some attempts are made to clone deeply. | |
**/ | |
function clone(source) { | |
var copy = {}; | |
Object.getOwnPropertyNames(source).forEach(function (property) { | |
var orig = source[property], | |
desc = Object.getOwnPropertyDescriptor(source, property), | |
value = (isArrayLike(orig) || isPlainObject(orig)) | |
? clone(orig) | |
: orig; | |
Object.defineProperty(copy, property, assign(desc, {value})); | |
}); | |
return isArrayLike(source) | |
? arrayFrom(copy) | |
: copy; | |
} | |
/** | |
* util.String.hyphenate(str[, hyphen = "-"]) -> String | |
* - str (String): String to hyphenate. | |
* - hyphen (String): Hyphen character. | |
* | |
* Converts a camelCase string into a hyphenated one. | |
* | |
* util.String.hyphenate('fontFamily'); // -> "font-family" | |
* | |
* The hyphen character can be defined to allow for a different | |
* substitution. If ommitted, or a string is not provided, a hyphen (`-`) | |
* is assumed. | |
* | |
* util.String.hyphenate('fontFamily', '='); // -> "font=family" | |
* util.String.hyphenate('fontFamily', 4); // -> "font-family" | |
* util.String.hyphenate('fontFamily', '---'); // -> "font---family" | |
* | |
**/ | |
function hyphenate(str, hyphen) { | |
if (typeof hyphen !== 'string') { | |
hyphen = '-'; | |
} | |
return str.replace(/([a-z])([A-Z])/g, function (ignore, lower, upper) { | |
return lower + hyphen + upper.toLowerCase(); | |
}); | |
} | |
/** | |
* util.String.toLowerFirst(str) -> String | |
* - str (String): String to convert. | |
* | |
* Converts a string so that the first character is lower case. | |
* | |
* util.String.toLowerFirst('Abc'); // -> "abc" | |
* util.String.toLowerFirst('abc'); // -> "abc" | |
* util.String.toLowerFirst('ABC'); // -> "aBC" | |
* | |
**/ | |
function toLowerFirst(str) { | |
var string = String(str); | |
return string.charAt(0).toLowerCase() + string.slice(1); | |
} | |
assign(utilities.Array, { | |
from: arrayFrom, | |
isArrayLike | |
}); | |
assign(utilities.Function, { | |
identity, | |
isFunction | |
}); | |
assign(utilities.Number, { | |
isNumeric | |
}); | |
assign(utilities.Object, { | |
assign, | |
clone, | |
getClass, | |
isObject, | |
owns | |
}); | |
assign(utilities.String, { | |
hyphenate, | |
toLowerFirst | |
}); | |
return utilities; | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment