Created
October 8, 2012 20:26
-
-
Save Skateside/3854762 to your computer and use it in GitHub Desktop.
Started using mixins quite a bit so I built a few functions to make it easier.
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
// These shims patch methods that the SK80 micro-library uses. References to the | |
// ES5 spec have been included to ensure that the shims are as close to | |
// standards as possible. | |
(function () { | |
'use strict'; | |
var isStringArray = 'a'[0] === 'a', | |
objProto = Object.prototype, | |
toString = objProto.toString; | |
// http://es5.github.com/#x9.9 | |
function toObject(o) { | |
if (o === undefined || o === null) { | |
throw { | |
name: 'TypeError', | |
message: o + ' is null or not an object' | |
}; | |
} | |
if (!isStringArray && toString.call(o) === '[object String]') { | |
o = o.split(''); | |
} | |
return Object(o); | |
} | |
// http://es5.github.com/#sign | |
function sign(number) { | |
return number < 0 ? -1 : 1; | |
} | |
// Used a few times in the specs, always described like this: | |
// sign(number) * floor(abs(number)) | |
function makeInt(n) { | |
return sign(n) * Math.floor(Math.abs(n)); | |
} | |
// http://es5.github.com/#x9.5 | |
function toUint32(n) { | |
var number = +n, | |
ret = 0, | |
twoPow32 = Math.pow(2, 32); | |
if (!isNaN(number) && isFinite(number)) { | |
ret = makeInt(number) % twoPow32; | |
if (ret >= twoPow32 / 2) { | |
ret -= twoPow32; | |
} | |
} | |
return ret; | |
} | |
// http://es5.github.com/#x9.4 | |
function toInteger(str) { | |
var number = Number(str), | |
returnValue = number; | |
if (isNaN(number)) { | |
returnValue = 0; | |
} else if (number !== 0 && isFinite(number)) { | |
returnValue = makeInt(number); | |
} | |
return returnValue; | |
} | |
// http://es5.github.com/#x15.4.4.18 | |
if (!Array.prototype.hasOwnProperty('forEach')) { | |
Array.prototype.forEach = function (func, thisArg) { | |
var index = 0, | |
array = toObject(this), | |
length = toUint32(array.length); | |
if (toString.call(func) !== '[object Function]') { | |
throw { | |
name: 'TypeError', | |
message: func + ' is not a function' | |
}; | |
} | |
while (index < length) { | |
if (objProto.hasOwnProperty.call(array, index)) { | |
func.call(thisArg, array[index], index, array); | |
} | |
index += 1; | |
} | |
}; | |
} | |
// http://es5.github.com/#x15.4.4.14 | |
if (!Array.prototype.hasOwnProperty('indexOf')) { | |
Array.prototype.indexOf = function (search, offset) { | |
var array = toObject(this), | |
len = toUint32(array.length), | |
n = offset === undefined ? 0 : toInteger(offset), | |
index = -1; | |
if (len > 0 && len > n) { | |
if (n < 0) { | |
n = Math.max(0, len - Math.abs(n)); | |
} | |
while (n < len) { | |
if (objProto.hasOwnProperty.call(array, index) && | |
array[n] === search) { | |
index = n; | |
break; | |
} | |
n += 1; | |
} | |
} | |
return index; | |
}; | |
} | |
// http://es5.github.com/#x15.4.3.2 | |
if (!Array.hasOwnProperty('isArray')) { | |
Array.isArray = function (o) { | |
return toString.call(o) === '[object Array]'; | |
}; | |
} | |
// http://es5.github.com/#x15.2.3.5 | |
if (!Object.hasOwnProperty('create')) { | |
Object.create = function (proto, properties) { | |
var object, | |
F = function () {}; | |
if (proto === null) { | |
object = {'__proto__': null}; | |
} else { | |
if (typeof proto !== 'object') { | |
throw { | |
name: 'TypeError', | |
message: 'typeof prototype[' + (typeof proto) + '] ' + | |
'must be an object' | |
}; | |
} | |
F.prototype = proto; | |
object = new F(); | |
object.__proto__ = proto; | |
} | |
if (properties !== undefined) { | |
throw { | |
name: 'BrowserError', | |
message: 'The second arguments of Object.create() is not' + | |
'supported in this browser' | |
}; | |
} | |
return object; | |
}; | |
} | |
// http://es5.github.com/#x15.2.3.2 | |
if (!Object.hasOwnProperty('getPrototypeOf')) { | |
Object.getPrototypeOf = function (object) { | |
return object.__proto__ || ( | |
object.constructor ? | |
object.constructor.prototpe : | |
Object.prototype | |
); | |
}; | |
} | |
}()); |
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
// The SK80 mixin. Adds 2 properties and 2 methods to an object. | |
// | |
// Properties: | |
// this.mixins (Object) A public store for the mixins. Initially | |
// empty. | |
// this.inter (Object) An object to create and check interfaces. | |
// this.inter.init() Defines an interface. | |
// this.inter.matches() Checks that an object matches the | |
// interface. | |
// Methods: | |
// this.addMixin() Adds a mixin to the public store. | |
// this.create() Creates an object based on another. Can also | |
// fire the objects "init" method and add | |
// mixins. | |
// | |
// It can be used in any of three ways. | |
// | |
// To add these properties to your own namespace: | |
// SK80.call(MYNAMESPACE); | |
// To contain these properties within the SK80 namespace: | |
// SK80.call(SK80); | |
// To create a new object with these properties: | |
// var myObject = new SK80(); | |
// | |
// The SK80.namespace has another property "version" which contains information | |
// about the current version of this micro-library. This property is not added | |
// to another namespace. | |
var SK80 = (function () { | |
'use strict'; | |
var sk80, | |
version = '0.3b', | |
toString = Object.prototype.toString, | |
reserved = ['arguments', 'break', 'case', 'catch', 'class', 'const', | |
'continue', 'debugger', 'default', 'do', 'else', 'enum', 'extends', | |
'false', 'finally', 'for', 'function', 'if', 'implements', 'import', | |
'in', 'instanceof', 'interface', 'let', 'new', 'null', 'package', | |
'private', 'protected', 'public', 'return', 'super', 'static', | |
'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', | |
'while', 'with', 'yield']; | |
// Checks to ensure that a given object is a String. | |
// | |
// Takes: | |
// object (Mixed) The object to check. | |
// Returns: | |
// (Boolean) true if a String, false otherwise. | |
function isString(object) { | |
return toString.call(object) === '[object String]'; | |
} | |
// Checks to ensure that a given object is a Function. | |
// | |
// Takes: | |
// object (Mixed) The object to check. | |
// Returns: | |
// (Boolean) true if a Function, false otherwise. | |
function isFunction(object) { | |
return toString.call(object) === '[object Function]'; | |
} | |
// Works out the type of a given object, based on the object's [[Class]]. The | |
// function fixes a few browser quirks with undefined and null, it also reduces | |
// the [[Class]] of all DOM nodes to "htmlelement". Returned strings are always | |
// lowercase. | |
// | |
// Takes: | |
// object (Object) The object to analyse. | |
// Returns: | |
// (String) The [[Class]] of the object, always lower case | |
// and DOM nodes are always "htmlelement". | |
function getClass(object) { | |
var string; | |
if (object === undefined) { | |
string = 'undefined'; | |
} else if (object === null) { | |
string = 'null'; | |
} else if (getClass(object.nodeName) === 'string' | |
&& getClass(object.nodeType) === 'number') { | |
string = 'htmlelement'; | |
} else { | |
string = toString.call(object); | |
string = string.substr(8); | |
string = string.substr(0, string.length - 1).toLowerCase(); | |
} | |
return string; | |
} | |
// Checks that a property exists somewhere in the prototype chain of a given | |
// object and that the value is undefined. This function exists because | |
// hasOwnProperty only works on the own properties, not the inherited ones and | |
// to simply check that o[prop] === undefined will return true if prop doesn't | |
// exist. We need to know that it does and has the value undefined. | |
// | |
// Takes: | |
// object (Object) The object to check for the property. | |
// prop (String) The property name to check. | |
// Returns: | |
// (Boolean) true if the property exists, false otherwise. | |
function checkUndefined(object, prop) { | |
var proto = object, | |
prev = object, | |
hasProp = false; | |
do { | |
// Walk up the prototype chain. To prevent an infinite loop from occuring if | |
// object is an HTMLElement, break the loop if proto and prev are the same. | |
proto = Object.getPrototypeOf(prev); | |
if (proto === prev || proto === null) { | |
break; | |
} | |
if (proto.hasOwnProperty(prop) && proto[prop] === undefined) { | |
hasProp = true; | |
break; | |
} | |
prev = proto; | |
} while (proto); | |
return hasProp; | |
} | |
sk80 = function () { | |
// The mixins Object contains all the mixins created for this application. It's | |
// public so that the mixins may be accessed directly. | |
this.mixins = {}; | |
// Adds mixins to the mixins Object and performs type checking and ensures that | |
// an identically named mixin does not already exist. Also checks that the mixin | |
// name is not a reserved work in JavaScript. | |
// | |
// Takes: | |
// name (String) The name of the mixin. It should be a valid | |
// Object key. | |
// mixin (Function) The mixin function. The "this" keyword will | |
// point to the Object that this mixin is added to. | |
this.addMixin = function (name, mixin) { | |
if (!isString(name)) { | |
throw { | |
name: 'TypeError', | |
message: 'SK80.addMixin name argument must be a String' | |
}; | |
} | |
if (!isFunction(mixin)) { | |
throw { | |
name: 'TypeError', | |
message: 'SK80.addMixin mixin argument must be a Function' | |
}; | |
} | |
if (this.mixins.hasOwnProperty(name)) { | |
throw { | |
name: 'SyntaxError', | |
message: 'SK80.addMixin "' + name + '" mixin has already ' + | |
'been defined' | |
}; | |
} | |
if (reserved.indexOf(name) > -1) { | |
throw { | |
name: 'SyntaxError', | |
message: 'SK80.addMixin "' + name + '" is a reserved ' + | |
'word in JavaScript' | |
}; | |
} | |
this.mixins[name] = mixin; | |
}; | |
// Creates objects based on the arguments that are given. The object argument is | |
// the Object from which the new object should be created, the optional settings | |
// arguments helps define the newly created object. | |
// | |
// Takes: | |
// object (Object) The object from which to create the new | |
// object. | |
// [settings] (Object) Settings for the newly created object. Has | |
// two possible keys: | |
// mixins (Array) An Array of Strings, the names of the mixins | |
// to add to the newly created object. | |
// args (Array) Any arguments to pass to the newly created | |
// objects "init" method. If the object has no | |
// "init" method, no action is taken. | |
// Returns: | |
// (Object) The newly created object. | |
this.create = function (object, settings) { | |
var store = this.mixins, | |
created = Object.create(object); | |
if (settings !== undefined) { | |
if (settings.hasOwnProperty('mixins')) { | |
if (!Array.isArray(settings.mixins)) { | |
throw { | |
name: 'TypeError', | |
message: 'SK80.create mixins must be an Array' | |
}; | |
} | |
settings.mixins.forEach(function (mixin) { | |
if (!isString(mixin)) { | |
throw { | |
name: 'TypeError', | |
message: 'SK80.create settings.mixins must ' + | |
'be an Array of Strings' | |
}; | |
} | |
if (!store.hasOwnProperty(mixin)) { | |
throw { | |
name: 'ReferenceError', | |
message: 'SK80.create "' + mixin + '" mixin ' + | |
'cannot be found' | |
}; | |
} | |
if (!isFunction(store[mixin])) { | |
throw { | |
name: 'TypeError', | |
message: 'SK80.create "' + mixin + '" mixin ' + | |
'is not a Function' | |
}; | |
} | |
store[mixin].call(created); | |
}); | |
} | |
if (settings.hasOwnProperty('args') | |
&& isFunction(created.init)) { | |
if (!Array.isArray(settings.args)) { | |
throw { | |
name: 'TypeError', | |
message: 'SK80.create settings.args must be an ' + | |
'Array' | |
}; | |
} | |
created.init.apply(created, settings.args); | |
} | |
} | |
return created; | |
}; | |
// Allows us to define interfaces. The given object must contain the stored | |
// methods; Errors are thrown if it does not match. In JavaScript, "interface" | |
// is a reserved word, so we use "inter" here instead. | |
this.inter = { | |
// Creates the interface by defining a name and the methods it requires. | |
// | |
// Takes: | |
// name (String) The name of the interface. | |
// properties (Object) The properties of the interface and their | |
// types in key/value pairs. | |
init: function (name, properties) { | |
var prop; | |
if (!isString(name)) { | |
throw { | |
name: 'TypeError', | |
message: 'SK80.inter name arguments must be a String' | |
}; | |
} | |
this.name = name; | |
for (prop in properties) { | |
if (properties.hasOwnProperty(prop)) { | |
if (!isString(prop)) { | |
throw { | |
name: 'TypeError', | |
message: 'SK80.inter properties argument can ' + | |
'only contain Strings, ' + (typeof prop) + | |
' given' | |
}; | |
} | |
properties[prop] = properties[prop].toLowerCase(); | |
} | |
} | |
this.properties = properties; | |
}, | |
// Checks to ensure that a given object implements this interface. This function | |
// throws Errors if a mis-match is detected. Since "implements" is a reserved | |
// word in JavaScript, we use "matches" instead. | |
// | |
// Takes: | |
// object (Object) The object that should implement this interface. | |
// Returns: | |
// (Boolean) true if the object implements this interface. | |
matches: function (object) { | |
var prop, | |
gotten, | |
current; | |
if (!this.name || !this.properties) { | |
throw { | |
name: 'InitError', | |
message: 'SK80.inter.matches called on a non-' + | |
'initialised interface' | |
}; | |
} | |
for (prop in this.properties) { | |
if (this.properties.hasOwnProperty(prop)) { | |
gotten = getClass(object[prop]); | |
current = this.properties[prop]; | |
if (current === 'undefined' && | |
!checkUndefined(object, prop)) { | |
gotten = 'missing'; | |
} | |
if (gotten !== current) { | |
throw { | |
name: 'ValidationError', | |
message: 'Object does not match the "' + | |
this.name + '" interface: "' + prop + '" ' + | |
'property is ' + gotten + ', should be ' + | |
current | |
}; | |
} | |
} | |
} | |
return true; | |
} | |
}; | |
}; | |
sk80.version = version; | |
return sk80; | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment