Skip to content

Instantly share code, notes, and snippets.

@ibolmo
Forked from arian/moo.js
Created June 4, 2011 14:40
Show Gist options
  • Save ibolmo/1007945 to your computer and use it in GitHub Desktop.
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.
// 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