-
-
Save ibolmo/1007945 to your computer and use it in GitHub Desktop.
MooTools.provide|require .. Vaporware. This is a DDD process. Imagine what interface you'd like to work with, and actually try to get it to work for the developer. Then we can make the underlying code.
This file contains hidden or 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 function gives the mootools context. this refers to a shared space that is not window. | |
// one could apply the context to window, so that native types are augmented, but by default it's best to work within the anon. function | |
MooTools.provide('typeOf', function(){ | |
this.typeOf = function(item){ | |
if (item == null) return 'null'; | |
if (item.$family) return item.$family(); | |
if (item.nodeName){ | |
if (item.nodeType == 1) return 'element'; | |
if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace'; | |
} else if (typeof item.length == 'number'){ | |
if (item.callee) return 'arguments'; | |
if ('item' in item) return 'collection'; | |
} | |
return typeof item; | |
}; | |
}); | |
MooTools.provide('instanceOf', function(){ | |
this.instanceOf = function(item, object){ | |
if (item == null) return false; | |
var constructor = item.$constructor || item.constructor; | |
while (constructor){ | |
if (constructor === object) return true; | |
constructor = constructor.parent; | |
} | |
return item instanceof object; | |
}; | |
}); | |
MooTools.provide('Function.overloadSetter', function(){ | |
// Function overloading | |
var enumerables = true; | |
for (var i in {toString: 1}) enumerables = null; | |
if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor']; | |
this.Function.prototype.overloadSetter = function(usePlural){ | |
var self = this; | |
return function(a, b){ | |
if (a == null) return this; | |
if (usePlural || typeof a != 'string'){ | |
for (var k in a) self.call(this, k, a[k]); | |
if (enumerables) for (var i = enumerables.length; i--;){ | |
k = enumerables[i]; | |
if (a.hasOwnProperty(k)) self.call(this, k, a[k]); | |
} | |
} else { | |
self.call(this, a, b); | |
} | |
return this; | |
}; | |
}; | |
}); | |
// second argument can be an array or a string of oomponents (methods, or modules (e.g. Type)) that are required. | |
// the first argument of the closure takes (in order) the Function because we required a method in Function.prototype. | |
// Keep in mind, that in a packaged scenario Function.prototype.overloadSetter would be defined prior and | |
// Function passed to the argument just helps reduce the 'this.Function' burden. | |
MooTools.provide('Function.overloadGetter', 'Function', function(Function){ | |
Function.prototype.overloadGetter = function(usePlural){ | |
var self = this; | |
return function(a){ | |
var args, result; | |
if (usePlural || typeof a != 'string') args = a; | |
else if (arguments.length > 1) args = arguments; | |
if (args){ | |
result = {}; | |
for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]); | |
} else { | |
result = self.call(this, a); | |
} | |
return result; | |
}; | |
}; | |
}); | |
MooTools.provide('Function.extend', 'Function.overloadSetter', function(Function){ | |
Function.prototype.extend = function(key, value){ | |
this[key] = value; | |
}.overloadSetter(); | |
}); | |
// as suggested by keeto. We can have a MooTools.uses which does the second arg's job. For now, I'll opt to use the former | |
// in this prototype. Since it is more concise. | |
MooTools.uses('Function.overloadSetter', function(Function){ | |
MooTools.provide('Function.implement', function(){ | |
Function.prototype.implement = function(key, value){ | |
this.prototype[key] = value; | |
}.overloadSetter(); | |
}); | |
}); | |
MooTools.provide('Function.from', ['Function', 'typeOf'], function(Function, typeOf){ | |
Function.from = function(item){ | |
return (typeOf(item) == 'function') ? item : function(){ | |
return item; | |
}; | |
}; | |
}); | |
// notice, again, how Type is passed to the closure and not Type::isEnumerable? | |
// I'd imagine, as well, that Type object would only contain necessary code and methods and not everything provided inside of Type.* | |
MooTools.provide('Array.from', ['Type::isEnumerable', 'typeOf'], function(Type, typeOf){ | |
this.Array.from = function(item){ | |
if (item == null) return []; | |
return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item]; | |
}; | |
}); | |
// we can require any of the natives (so that we don't use this.). | |
// the Number provided, is a shared Number object. At any point we might augment the | |
// real Number object, but by default we don't. | |
MooTools.provide('Number.from', 'Number', function(Number){ | |
Number.from = function(item){ | |
var number = parseFloat(item); | |
return isFinite(number) ? number : null; | |
}; | |
}); | |
// crazy (cool) idea would be to parse the passed function for the required objects `/function[^\(]+\(([^\)]+)\)/` | |
MooTools.provide('String.from', function(String){ | |
String.from = function(item){ | |
return item + ''; | |
}; | |
}); | |
// only problem with the previous idea would be the specificity of the requirement (it's illegal to have a . in the arg) | |
MooTools.provide(['Function.hide', 'Function.protect'], 'Function::implement', function(Function){ | |
// What if implement hooked into the provide/requires/uses API? | |
// then no need to MooTools.provide as often, just use MooTools.uses | |
Function.implement({ | |
hide: function(){ | |
this.$hidden = true; | |
return this; | |
}, | |
protect: function(){ | |
this.$protected = true; | |
return this; | |
} | |
}); | |
}); | |
MooTools.provide('Type', ['typeOf', 'Function.hide'], function(typeOf){ | |
var Type = this.Type = function(name, object){ | |
if (name){ | |
var lower = name.toLowerCase(); | |
var typeCheck = function(item){ | |
return (typeOf(item) == lower); | |
}; | |
// what should we do here? looks like at each call to Type constructor Type::is* created. | |
Type['is' + name] = typeCheck; | |
if (object != null){ | |
object.prototype.$family = (function(){ | |
return lower; | |
}).hide(); | |
//<1.2compat> | |
// what should we do here. The object passed gets a type method. Does that mean that we provide a *.type | |
object.type = typeCheck; | |
//</1.2compat> | |
} | |
} | |
if (object == null) return null; | |
// extend is part of the object, packaging shouldn't guess if it's using Type.extend or Function.extend. | |
object.extend(this); | |
object.$constructor = Type; | |
object.prototype.$constructor = object; | |
return object; | |
}; | |
}); | |
MooTools.provide('Type::isEnumerable', 'Type', function(Type){ | |
var toString = Object.prototype.toString; | |
Type.isEnumerable = function(item){ | |
return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' ); | |
}; | |
}); | |
MooTools.uses(['Type', 'typeOf', 'Function.overloadSetter'], function(Type, typeOf){ | |
MooTools.provide(['Type.implement', 'Type.extend', 'Type.alias', 'Type.mirror', 'Object.extend'], function(){ | |
var hooks = {}; | |
var hooksOf = function(object){ | |
var type = typeOf(object.prototype); | |
return hooks[type] || (hooks[type] = []); | |
}; | |
var implement = function(name, method){ | |
if (method && method.$hidden) return; | |
var hooks = hooksOf(this); | |
for (var i = 0; i < hooks.length; i++){ | |
var hook = hooks[i]; | |
if (typeOf(hook) == 'type') implement.call(hook, name, method); | |
else hook.call(this, name, method); | |
} | |
var previous = this.prototype[name]; | |
if (previous == null || !previous.$protected) this.prototype[name] = method; | |
if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){ | |
return method.apply(item, slice.call(arguments, 1)); | |
}); | |
}; | |
var extend = function(name, method){ | |
if (method && method.$hidden) return; | |
var previous = this[name]; | |
if (previous == null || !previous.$protected) this[name] = method; | |
}; | |
Type.implement({ | |
implement: implement.overloadSetter(), | |
extend: extend.overloadSetter(), | |
alias: function(name, existing){ | |
implement.call(this, name, this.prototype[existing]); | |
}.overloadSetter(), | |
mirror: function(hook){ | |
hooksOf(this).push(hook); | |
return this; | |
} | |
}); | |
}); | |
}); | |
MooTools.provide(['Type::isType', /*<1.2compat>*/'Type.type'/*</1.2compat>*/], 'Type', function(Type){ | |
new Type('Type', Type); | |
}); | |
// this is incredibly tough to do. The best I can think of is to have a provide generator that lists what all this code does | |
// but it's ridiculous. I'd be Type::isRegExp, Type::exec, Type::test (since we create generics if they were not there). | |
// This is why, again, implements and extends hooked to the API would be great. | |
MooTools.provide(['Type::isString', 'Type::isArray', 'Type::isNumber', 'Type::isFunction', 'Type::isRegExp', 'Type::isObject', 'Type::isDate'], ['Type', 'Function.protect', 'Function.hide', 'Function.implement'], function(Type){ | |
var force = function(name, object, methods){ | |
var isType = (object != Object), | |
prototype = object.prototype; | |
if (isType) object = new Type(name, object); | |
for (var i = 0, l = methods.length; i < l; i++){ | |
var key = methods[i], | |
generic = object[key], | |
proto = prototype[key]; | |
if (generic) generic.protect(); | |
if (isType && proto){ | |
delete prototype[key]; | |
prototype[key] = proto.protect(); | |
} | |
} | |
if (isType) object.implement(prototype); | |
return force; | |
}; | |
force('String', String, [ | |
'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search', | |
'slice', 'split', 'substr', 'substring', 'toLowerCase', 'toUpperCase' | |
])('Array', Array, [ | |
'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', | |
'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight' | |
])('Number', Number, [ | |
'toExponential', 'toFixed', 'toLocaleString', 'toPrecision' | |
])('Function', Function, [ | |
'apply', 'call', 'bind' | |
])('RegExp', RegExp, [ | |
'exec', 'test' | |
])('Object', Object, [ | |
'create', 'defineProperty', 'defineProperties', 'keys', | |
'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames', | |
'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen' | |
])('Date', Date, ['now']); | |
// moved from line 286 | |
Number.prototype.$family = function(){ | |
return isFinite(this) ? 'number' : 'null'; | |
}.hide(); | |
}); | |
MooTools.provide('Object::extend', ['Type::isObject'], function(Type){ | |
// rather than copy and paste the private extend method (above), just link to the Type extend (should be exact one). | |
Object.extend = Type.prototype.extend; | |
}); | |
//<JS1.5> | |
MooTools.provide('Date::now', ['Date', 'Type::isDate', function(Date){ | |
Date.extend('now', function(){ | |
return +(new Date); | |
}); | |
}); | |
//</JS1.5> | |
MooTools.provide('Type::isBoolean', ['Boolean', 'Type'], function(Type){ | |
new Type('Boolean', Boolean); | |
}); | |
// correct me, but this doesn't require Number to be protected right? So then only dep is Number | |
MooTools.provide('Number::random', 'Number', function(Number){ | |
Number.extend('random', function(min, max){ | |
return Math.floor(Math.random() * (max - min + 1) + min); | |
}); | |
}); | |
MooTools.provide(['Object::each', 'Object::forEach'], 'Object', function(Object){ | |
var hasOwnProperty = Object.prototype.hasOwnProperty; | |
Object.extend('forEach', function(object, fn, bind){ | |
for (var key in object){ | |
if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object); | |
} | |
}); | |
Object.each = Object.forEach; | |
}); | |
MooTools.provide(['Array.each', 'Array.forEach'], ['Array', 'Type::isArray'], function(Array){ | |
Array.implement({ | |
forEach: function(fn, bind){ | |
for (var i = 0, l = this.length; i < l; i++){ | |
if (i in this) fn.call(bind, this[i], i, this); | |
} | |
}, | |
each: function(fn, bind){ | |
Array.forEach(this, fn, bind); | |
return this; | |
} | |
}); | |
}); | |
MooTools.provide(['Array.clone', 'Object::merge', 'Object::clone', 'Object::append'], ['Object', 'Array', 'typeOf'], function(Object, Array, typeOf){ | |
var cloneOf = function(item){ | |
switch (typeOf(item)){ | |
case 'array': return item.clone(); | |
case 'object': return Object.clone(item); | |
default: return item; | |
} | |
}; | |
Array.implement('clone', function(){ | |
var i = this.length, clone = new Array(i); | |
while (i--) clone[i] = cloneOf(this[i]); | |
return clone; | |
}); | |
var mergeOne = function(source, key, current){ | |
switch (typeOf(current)){ | |
case 'object': | |
if (typeOf(source[key]) == 'object') Object.merge(source[key], current); | |
else source[key] = Object.clone(current); | |
break; | |
case 'array': source[key] = current.clone(); break; | |
default: source[key] = current; | |
} | |
return source; | |
}; | |
Object.extend({ | |
merge: function(source, k, v){ | |
if (typeOf(k) == 'string') return mergeOne(source, k, v); | |
for (var i = 1, l = arguments.length; i < l; i++){ | |
var object = arguments[i]; | |
for (var key in object) mergeOne(source, key, object[key]); | |
} | |
return source; | |
}, | |
clone: function(object){ | |
var clone = {}; | |
for (var key in object) clone[key] = cloneOf(object[key]); | |
return clone; | |
} | |
}); | |
}); | |
// Moved from 367 .. less deps. | |
MooTools.provide('Object::append', 'Object', function(Object){ | |
Object.append = function(original){ | |
for (var i = 1, l = arguments.length; i < l; i++){ | |
var extended = arguments[i] || {}; | |
for (var key in extended) original[key] = extended[key]; | |
} | |
return original; | |
}; | |
}); | |
MooTools.provide(['Type::isObject', 'Type::isWhiteSpace', 'Type::isTextNode', 'Type::isCollection', 'Type::isArguments'], ['Type', 'Array.each'], function(Type){ | |
// Object-less types | |
['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){ | |
new Type(name); | |
}); | |
}); | |
// For the case that we require a specific function that is polyfilled, like Date::now, the js packager has access to any method (now) available. Incase we strip our polyfill. | |
MooTools.provide('String::uniqueID', ['String', 'Date::now'], function(String, Date){ | |
// Unique ID | |
var UID = Date.now(); | |
String.extend('uniqueID', function(){ | |
return (UID++).toString(36); | |
}); | |
}); | |
//<1.2compat> | |
MooTools.provide(['Hash', 'Hash.each', 'Hash.forEach', 'Hash.getClean', 'Hash.getLength'], ['Type', 'Object::clone', 'typeOf', 'Object::forEach'], function(Type, Object, typeOf){ | |
var Hash = this.Hash = new Type('Hash', function(object){ | |
if (typeOf(object) == 'hash') object = Object.clone(object.getClean()); | |
for (var key in object) this[key] = object[key]; | |
return this; | |
}); | |
Hash.implement({ | |
forEach: function(fn, bind){ | |
Object.forEach(this, fn, bind); | |
}, | |
getClean: function(){ | |
var clean = {}; | |
for (var key in this){ | |
if (this.hasOwnProperty(key)) clean[key] = this[key]; | |
} | |
return clean; | |
}, | |
getLength: function(){ | |
var length = 0; | |
for (var key in this){ | |
if (this.hasOwnProperty(key)) length++; | |
} | |
return length; | |
} | |
}); | |
Hash.alias('each', 'forEach'); | |
}); | |
// err? Object.type = Type.isObject; | |
MooTools.provide(['Native', 'Native::type', 'Native::implement'], ['Type', 'Type::isType'], function(Type){ | |
var Native = this.Native = function(properties){ | |
return new Type(properties.name, properties.initialize); | |
}; | |
Native.type = Type.type; | |
Native.implement = function(objects, methods){ | |
for (var i = 0; i < objects.length; i++) objects[i].implement(methods); | |
return Native; | |
}; | |
}); | |
MooTools.provide('Array::type', ['instanceOf', 'Array', 'Type::isArray'], function(instanceOf, Array){ | |
var arrayType = Array.type; | |
Array.type = function(item){ | |
return instanceOf(item, Array) || arrayType(item); | |
}; | |
}); | |
MooTools.provide('$A', 'Array::from', function(Array){ | |
this.$A = function(item){ | |
return Array.from(item).slice(); | |
}; | |
}); | |
MooTools.provide('$arguments', function(){ | |
this.$arguments = function(i){ | |
return function(){ | |
return arguments[i]; | |
}; | |
}; | |
}); | |
MooTools.provide('$chk', function(){ | |
this.$chk = function(obj){ | |
return !!(obj || obj === 0); | |
}; | |
}); | |
MooTools.provide('$clear', function(){ | |
this.$clear = function(timer){ | |
clearTimeout(timer); | |
clearInterval(timer); | |
return null; | |
}; | |
}); | |
MooTools.provide('$defined', function(){ | |
this.$defined = function(obj){ | |
return (obj != null); | |
}; | |
}); | |
MooTools.provide('$each', ['typeOf', 'Array::each', 'Object::each'], function(typeOf, Array, Object){ | |
this.$each = function(iterable, fn, bind){ | |
var type = typeOf(iterable); | |
((type == 'arguments' || type == 'collection' || type == 'array' || type == 'elements') ? Array : Object).each(iterable, fn, bind); | |
}; | |
}); | |
MooTools.provide('$empty', function(){ | |
this.$empty = function(){}; | |
}); | |
MooTools.provide('$extend', 'Object::append', function(){ | |
this.$extend = function(original, extended){ | |
return Object.append(original, extended); | |
}; | |
}); | |
MooTools.provide('$H', 'Hash', function(Hash){ | |
this.$H = function(object){ | |
return new Hash(object); | |
}; | |
}); | |
MooTools.provide('$merge', ['Array', 'Object::merge'], function(Array, Object){ | |
this.$merge = function(){ | |
var args = Array.slice(arguments); | |
args.unshift({}); | |
return Object.merge.apply(null, args); | |
}; | |
}); | |
MooTools.provide('$lambda', 'Function::from', function(Function){ | |
this.$lambda = Function.from; | |
}); | |
MooTools.provide('$mixin', 'Object::merge', function(Object){ | |
this.$mixin = Object.merge; | |
}); | |
MooTools.provide('$random', 'Number::random', function(Number){ | |
this.$random = Number.random; | |
}); | |
MooTools.provide('$splat', 'Array::from', function(Array){ | |
this.$splat = Array.from; | |
}); | |
MooTools.provide('$time', 'Date::now', function(Date){ | |
this.$time = Date.now; | |
}); | |
MooTools.provide('$type', 'typeOf', function(typeOf){ | |
this.$type = function(object){ | |
var type = typeOf(object); | |
if (type == 'elements') return 'array'; | |
return (type == 'null') ? false : type; | |
}; | |
}); | |
MooTools.provide('$unlink', ['Object::clone', 'Array::clone', 'Hash'], function(Object, Array, Hash){ | |
this.$unlink = function(object){ | |
switch (typeOf(object)){ | |
case 'object': return Object.clone(object); | |
case 'array': return Array.clone(object); | |
case 'hash': return new Hash(object); | |
default: return object; | |
} | |
}; | |
}); | |
//</1.2compat> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment