├── Test ├── test.js ├── lib │ ├── sg-testrunner.js │ ├── sg-JSSpec.CommonJS.test.js │ └── sg-JSSpec.CommonJS.js └── Class.test.js ├── README.md └── mootools-class.js /Test/test.js: -------------------------------------------------------------------------------- 1 | 2 | require.paths.unshift('.') 3 | require.paths.unshift('..') 4 | require.paths.unshift('./lib') 5 | require.paths.unshift('./Test/lib') 6 | 7 | require('mootools-class') 8 | require('sg-JSSpec.CommonJS') 9 | require('./Class.test') 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MooTools' Class 2 | =============== 3 | 4 | Test 5 | ---- 6 | `node Test/test.js ; narwhal Test/test.js` 7 | 8 | Test the Tester 9 | --------------- 10 | `node Test/lib/sg-JSSpec.CommonJS.test.js ; narwhal Test/lib/sg-JSSpec.CommonJS.test.js` 11 | -------------------------------------------------------------------------------- /Test/lib/sg-testrunner.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: SG-TestRunner 4 | author: Thomas Aylott 5 | license: MIT Style 6 | ... 7 | */ 8 | 9 | if (typeof print == 'undefined') var print = require('sys').print; 10 | 11 | var assert = require('assert'); 12 | 13 | exports.run = run; 14 | 15 | function run(tests){ 16 | var fails = 0; 17 | var passed = 0; 18 | var errors = 0; 19 | var ran = 0; 20 | 21 | for (var name in tests){ 22 | if (!tests.hasOwnProperty(name)) continue; 23 | if (!/^test/.test(name)) continue; 24 | if (typeof tests[name] == 'function'){ 25 | print('\t\t+ '+name +'\n'); 26 | try { 27 | ++ ran; 28 | tests[name](assert); 29 | ++ passed; 30 | } catch(e){ 31 | print('\t\t\tError: '+ e +'\n'); 32 | ++ errors; 33 | } 34 | } else { 35 | print('\t+ '+name +'\n'); 36 | fails += run(tests[name]); 37 | } 38 | } 39 | 40 | if (ran){ 41 | print("Passed " + passed) 42 | print("; ") 43 | print("Failed " + fails) 44 | print("; ") 45 | print("Error " + errors) 46 | print("\n\n") 47 | } 48 | return fails; 49 | } 50 | -------------------------------------------------------------------------------- /Test/lib/sg-JSSpec.CommonJS.test.js: -------------------------------------------------------------------------------- 1 | var JSSpec = require('./sg-JSSpec.CommonJS') 2 | , value_of = JSSpec.value_of 3 | , describe = JSSpec.describe 4 | 5 | describe('JSSpec Basics', { 6 | before: function(){} 7 | ,after: function(){} 8 | 9 | ,"value_of() Should respond to should_be": function(){ 10 | value_of( 11 | typeof value_of(true).should_be 12 | ) 13 | .should_be('function') 14 | } 15 | 16 | ,"value_of() Should respond to should_be_true": function(){ 17 | value_of( 18 | typeof value_of(true).should_be_true 19 | ) 20 | .should_be('function') 21 | } 22 | 23 | ,"Should error": function(){ 24 | ++ DOES_NOT_EXIST 25 | } 26 | 27 | 28 | ,"should_fail":function(){ 29 | value_of(function(){}).should_fail() 30 | } 31 | ,"should_be":function(){ 32 | value_of(1).should_be(2) 33 | } 34 | ,"should_be_true":function(){ 35 | value_of(false).should_be_true() 36 | } 37 | ,"should_not_be":function(){ 38 | value_of(false).should_not_be(false) 39 | } 40 | ,"should_be_empty":function(){ 41 | value_of([1,2,3]).should_be_empty() 42 | } 43 | ,"should_not_be_empty":function(){ 44 | value_of().should_not_be_empty() 45 | } 46 | ,"should_be_true":function(){ 47 | value_of().should_be_true() 48 | } 49 | ,"should_be_false":function(){ 50 | value_of().should_be_false() 51 | } 52 | ,"should_be_null":function(){ 53 | value_of().should_be_null() 54 | } 55 | ,"should_be_undefined":function(){ 56 | value_of().should_be_undefined() 57 | } 58 | ,"should_not_be_null":function(){ 59 | value_of().should_not_be_null() 60 | } 61 | ,"should_not_be_undefined":function(){ 62 | value_of().should_not_be_undefined() 63 | } 64 | ,"should_have":function(){ 65 | value_of().should_have() 66 | } 67 | ,"should_have_exactly":function(){ 68 | value_of().should_have_exactly() 69 | } 70 | ,"should_have_at_least":function(){ 71 | value_of().should_have_at_least() 72 | } 73 | ,"should_have_at_most":function(){ 74 | value_of().should_have_at_most() 75 | } 76 | ,"should_include":function(){ 77 | value_of().should_include() 78 | } 79 | ,"should_not_include":function(){ 80 | value_of().should_not_include() 81 | } 82 | ,"should_match":function(){ 83 | value_of().should_match() 84 | } 85 | ,"should_not_match":function(){ 86 | value_of().should_not_match() 87 | } 88 | ,"getType":function(){ 89 | value_of().getType() 90 | } 91 | 92 | }) 93 | 94 | -------------------------------------------------------------------------------- /Test/lib/sg-JSSpec.CommonJS.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name : JSSpec 4 | description : Implementation of the public JSSpec API using the CommonJS Unit Test 1.0 API or QUnit 5 | version : '0.1alpha' 6 | 7 | authors : Thomas Aylott 8 | copyright : © 2010 Thomas Aylott 9 | license : MIT 10 | 11 | provides : 12 | - JSSpec 13 | - describe 14 | - value_of 15 | - assert 16 | 17 | # requires : 18 | # - assert || QUnit || require('assert') 19 | # - test/runner || sg-testrunner 20 | ... 21 | */ 22 | 23 | // resolve requires 24 | 25 | if (typeof assert == 'undefined' && typeof QUnit != 'undefined') var assert = QUnit 26 | if (typeof assert == 'undefined' && typeof require != 'undefined') var assert = require('assert') 27 | 28 | 29 | // CommonJS Module 30 | 31 | if (typeof exports == 'undefined') var exports = this 32 | 33 | // provides JSSpec 34 | 35 | exports.assert = assert 36 | exports.JSSpec = exports 37 | exports.Browser = {} 38 | var hasOwnProperty = {}.hasOwnProperty 39 | function NOP(){} 40 | 41 | require.paths.unshift('.') 42 | 43 | var run 44 | try { 45 | run = exports.run = require('test').run 46 | } catch(e){} 47 | try { 48 | if (!exports.run) run = exports.run = require('test/runner').run 49 | } catch(e){} 50 | try { 51 | if (!exports.run) run = exports.run = require('sg-testrunner').run 52 | } catch(e){} 53 | try { 54 | if (!exports.run) run = exports.run = require('./sg-testrunner').run 55 | } catch(e){} 56 | 57 | 58 | function describeTest(test, before, after){ 59 | return function(){ before() ; test() ; after() } 60 | } 61 | 62 | exports.describe = describe 63 | function describe(context, specs, base){ 64 | if (base) throw new Error("describe(,,base) is not implemented") 65 | 66 | var before = 67 | specs.before 68 | || specs.beforeEach 69 | || specs.before_each 70 | || specs['before each'] 71 | || NOP 72 | 73 | var after = 74 | specs.after 75 | || specs.afterEach 76 | || specs.after_each 77 | || specs['after each'] 78 | || NOP 79 | 80 | var tests = {} 81 | 82 | tests.testBefore = 83 | specs.beforeAll 84 | || specs.before_all 85 | || specs['before all'] 86 | || NOP 87 | 88 | for (var name in specs){ 89 | if (!hasOwnProperty.call(specs, name)) continue 90 | tests['test ' + name] = describeTest(specs[name], before, after) 91 | } 92 | 93 | tests.testAfter = 94 | specs.afterAll 95 | || specs.after_all 96 | || specs['after all'] 97 | || NOP 98 | 99 | describe.specs['test ' + context] = tests 100 | 101 | if (!describe.runManually){ 102 | run(describe.specs) 103 | describe.specs = {} 104 | } 105 | 106 | } 107 | describe.specs = {} 108 | 109 | 110 | 111 | exports.value_of = value_of 112 | function value_of(actual){ 113 | if (!(this instanceof value_of)) return new value_of(actual) 114 | this.actual = actual 115 | } 116 | 117 | function notImplemented(){ throw new Error('Not Implemented Yet') } 118 | 119 | 120 | value_of.should_fail = assert['throws'] 121 | value_of.should_be = assert.equal 122 | value_of.should_be_true = assert.ok 123 | value_of.should_not_be = notImplemented // function(actual, expected, message){} 124 | value_of.should_be_empty = notImplemented // function(actual, message){} 125 | value_of.should_not_be_empty = notImplemented // function(actual, message){} 126 | value_of.should_be_true = notImplemented // function(actual, message){} 127 | value_of.should_be_false = notImplemented // function(actual, message){} 128 | value_of.should_be_null = notImplemented // function(actual, message){} 129 | value_of.should_be_undefined = notImplemented // function(actual, message){} 130 | value_of.should_not_be_null = notImplemented // function(actual, message){} 131 | value_of.should_not_be_undefined = notImplemented // function(actual, message){} 132 | value_of.should_have = notImplemented // function(actual, expected, message){} 133 | value_of.should_have_exactly = notImplemented // function(actual, expected, message){} 134 | value_of.should_have_at_least = notImplemented // function(actual, expected, message){} 135 | value_of.should_have_at_most = notImplemented // function(actual, expected, message){} 136 | value_of.should_include = notImplemented // function(actual, expected, message){} 137 | value_of.should_not_include = notImplemented // function(actual, expected, message){} 138 | value_of.should_match = notImplemented // function(actual, expected, message){} 139 | value_of.should_not_match = notImplemented // function(actual, expected, message){} 140 | value_of.getType = notImplemented // function(actual){} 141 | 142 | 143 | for (var methodName in value_of){ 144 | if (!hasOwnProperty.call(value_of, methodName)) continue 145 | value_of.prototype[methodName] = passArgumentFromThis(value_of[methodName], 'actual') 146 | } 147 | 148 | function passArgumentFromThis(fn, propertyName){ 149 | return function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20){ 150 | return fn(this[propertyName], a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) 151 | } 152 | } 153 | 154 | 155 | // export to GLOBAL 156 | 157 | if (typeof GLOBAL != 'undefined') var Global = GLOBAL 158 | else var Global = this 159 | 160 | Global.describe = describe 161 | Global.value_of = value_of 162 | 163 | -------------------------------------------------------------------------------- /Test/Class.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Script: Class.js 3 | Specs for Class.js 4 | 5 | License: 6 | MIT-style license. 7 | */ 8 | 9 | (function(){ 10 | 11 | var Animal = new Class({ 12 | 13 | initialized: false, 14 | 15 | initialize: function(name, sound){ 16 | this.name = name; 17 | this.sound = sound || ''; 18 | this.initialized = true; 19 | }, 20 | 21 | eat: function(){ 22 | return 'animal:eat:' + this.name; 23 | }, 24 | 25 | say: function(){ 26 | return 'animal:say:' + this.name; 27 | }, 28 | 29 | toString: function(){ 30 | return 'Animal'; 31 | } 32 | 33 | }); 34 | 35 | var Cat = new Class({ 36 | 37 | Extends: Animal, 38 | 39 | ferocious: false, 40 | 41 | initialize: function(name, sound){ 42 | this.parent(name, sound || 'miao'); 43 | }, 44 | 45 | eat: function(){ 46 | return 'cat:eat:' + this.name; 47 | }, 48 | 49 | play: function(){ 50 | return 'cat:play:' + this.name; 51 | }, 52 | 53 | toString: function(){ 54 | return 'Cat'; 55 | } 56 | 57 | }); 58 | 59 | var Lion = new Class({ 60 | 61 | Extends: Cat, 62 | 63 | ferocious: true, 64 | 65 | initialize: function(name){ 66 | this.parent(name, 'rarr'); 67 | }, 68 | 69 | eat: function(){ 70 | return 'lion:eat:' + this.name; 71 | } 72 | 73 | }); 74 | 75 | var Actions = new Class({ 76 | 77 | jump: function(){ 78 | return 'actions:jump:' + this.name; 79 | }, 80 | 81 | sleep: function(){ 82 | return 'actions:sleep:' + this.name; 83 | } 84 | 85 | }); 86 | 87 | var Attributes = new Class({ 88 | 89 | color: function(){ 90 | return 'attributes:color:' + this.name; 91 | }, 92 | 93 | size: function(){ 94 | return 'attributes:size:' + this.name; 95 | } 96 | 97 | }); 98 | 99 | 100 | describe('Class creation', { 101 | 102 | "Classes should be of type 'class'": function(){ 103 | value_of(typeOf(Animal)).should_be('class'); 104 | value_of(Type.isClass(Animal)).should_be_true(); 105 | }, 106 | 107 | "should call initialize upon instantiation": function(){ 108 | var animal = new Animal('lamina'); 109 | value_of(animal.name).should_be('lamina'); 110 | value_of(animal.initialized).should_be_true(); 111 | value_of(animal.say()).should_be('animal:say:lamina'); 112 | }, 113 | 114 | "should use 'Extend' property to extend another class": function(){ 115 | var cat = new Cat('fluffy'); 116 | value_of(cat.name).should_be('fluffy'); 117 | value_of(cat.sound).should_be('miao'); 118 | value_of(cat.ferocious).should_be_false(); 119 | value_of(cat.say()).should_be('animal:say:fluffy'); 120 | value_of(cat.eat()).should_be('cat:eat:fluffy'); 121 | value_of(cat.play()).should_be('cat:play:fluffy'); 122 | }, 123 | 124 | "should use 'Extend' property to extend an extended class": function(){ 125 | var leo = new Lion('leo'); 126 | value_of(leo.name).should_be('leo'); 127 | value_of(leo.sound).should_be('rarr'); 128 | value_of(leo.ferocious).should_be_true(); 129 | value_of(leo.say()).should_be('animal:say:leo'); 130 | value_of(leo.eat()).should_be('lion:eat:leo'); 131 | value_of(leo.play()).should_be('cat:play:leo'); 132 | }, 133 | 134 | "should use 'Implements' property to implement another class": function(){ 135 | var Dog = new Class({ 136 | Implements: Animal 137 | }); 138 | 139 | var rover = new Dog('rover'); 140 | value_of(rover.name).should_be('rover'); 141 | value_of(rover.initialized).should_be_true(); 142 | value_of(rover.eat()).should_be('animal:eat:rover'); 143 | }, 144 | 145 | "should use 'Implements' property to implement any number of classes": function(){ 146 | var Dog = new Class({ 147 | Extends: Animal, 148 | Implements: [Actions, Attributes] 149 | }); 150 | 151 | var rover = new Dog('rover'); 152 | value_of(rover.initialized).should_be_true(); 153 | value_of(rover.eat()).should_be('animal:eat:rover'); 154 | value_of(rover.say()).should_be('animal:say:rover'); 155 | value_of(rover.jump()).should_be('actions:jump:rover'); 156 | value_of(rover.sleep()).should_be('actions:sleep:rover'); 157 | value_of(rover.size()).should_be('attributes:size:rover'); 158 | value_of(rover.color()).should_be('attributes:color:rover'); 159 | }, 160 | 161 | "should alter the Class's prototype when implementing new methods": function(){ 162 | var Dog = new Class({ 163 | Extends: Animal 164 | }); 165 | 166 | var rover = new Dog('rover'); 167 | 168 | Dog.implement({ 169 | jump: function(){ 170 | return 'dog:jump:' + this.name; 171 | } 172 | }); 173 | 174 | var spot = new Dog('spot'); 175 | 176 | value_of(spot.jump()).should_be('dog:jump:spot'); 177 | value_of(rover.jump()).should_be('dog:jump:rover'); 178 | }, 179 | 180 | "should alter the Class's prototype when implementing new methods into the super class": function(){ 181 | var Dog = new Class({ 182 | Extends: Animal 183 | }); 184 | 185 | var rover = new Dog('rover'); 186 | 187 | Animal.implement({ 188 | jump: function(){ 189 | return 'animal:jump:' + this.name; 190 | } 191 | }); 192 | 193 | var spot = new Dog('spot'); 194 | 195 | value_of(spot.jump()).should_be('animal:jump:spot'); 196 | value_of(rover.jump()).should_be('animal:jump:rover'); 197 | }, 198 | 199 | "should alter the Class's prototype when overwriting methods in the super class": function(){ 200 | var Dog = new Class({ 201 | Extends: Animal 202 | }); 203 | 204 | var rover = new Dog('rover'); 205 | value_of(rover.say()).should_be('animal:say:rover'); 206 | 207 | Animal.implement({ 208 | say: function(){ 209 | return 'NEW:animal:say:' + this.name; 210 | } 211 | }); 212 | 213 | var spot = new Dog('spot'); 214 | 215 | value_of(spot.say()).should_be('NEW:animal:say:spot'); 216 | value_of(rover.say()).should_be('NEW:animal:say:rover'); 217 | }, 218 | 219 | "should implement the toString method": function(){ 220 | var animal, cat; 221 | 222 | value_of(animal = new Animal).should_be_true(); 223 | value_of(cat = new Cat).should_be_true(); 224 | 225 | value_of(animal + '').should_be('Animal'); 226 | 227 | value_of(cat + '').should_be('Cat'); 228 | } 229 | 230 | //We might attempt to add support for the following at a later date 231 | 232 | /* 233 | "should access the proper parent when it is overwritten after instantiation": function(){ 234 | var Dog = new Class({ 235 | Extends: Animal, 236 | say: function(){ 237 | return this.parent(); 238 | } 239 | }); 240 | 241 | var rover = new Dog('rover'); 242 | value_of(rover.say()).should_be('animal:say:rover'); 243 | 244 | Animal.implement({ 245 | say: function(){ 246 | return 'NEW:animal:say:' + this.name; 247 | } 248 | }); 249 | 250 | var spot = new Dog('spot'); 251 | 252 | value_of(spot.say()).should_be('NEW:animal:say:spot'); 253 | value_of(rover.say()).should_be('NEW:animal:say:rover'); 254 | } 255 | */ 256 | 257 | }); 258 | 259 | describe('Class::implement', { 260 | 261 | 'should implement an object': function(){ 262 | var Dog = new Class({ 263 | Extends: Animal 264 | }); 265 | 266 | Dog.implement(new Actions); 267 | 268 | var rover = new Dog('rover'); 269 | 270 | value_of(rover.name).should_be('rover'); 271 | value_of(rover.jump()).should_be('actions:jump:rover'); 272 | value_of(rover.sleep()).should_be('actions:sleep:rover'); 273 | }, 274 | 275 | 'should implement any number of objects': function(){ 276 | var Dog = new Class({ 277 | Extends: Animal 278 | }); 279 | 280 | Dog.implement(new Actions).implement(new Attributes); 281 | 282 | var rover = new Dog('rover'); 283 | 284 | value_of(rover.name).should_be('rover'); 285 | value_of(rover.jump()).should_be('actions:jump:rover'); 286 | value_of(rover.sleep()).should_be('actions:sleep:rover'); 287 | value_of(rover.size()).should_be('attributes:size:rover'); 288 | value_of(rover.color()).should_be('attributes:color:rover'); 289 | } 290 | 291 | }); 292 | 293 | })(); 294 | -------------------------------------------------------------------------------- /mootools-class.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | --- 4 | name: Core 5 | description: The heart of MooTools. 6 | provides: [Core, MooTools, Type, typeOf, instanceOf] 7 | ... 8 | */ 9 | 10 | (function(){ 11 | 12 | this.MooTools = { 13 | version: '1.99dev', 14 | build: '' 15 | }; 16 | 17 | // nil 18 | 19 | this.nil = function(item){ 20 | return (item != null) ? item : null; 21 | }; 22 | 23 | Function.prototype.overloadSetter = function(usePlural){ 24 | var self = this; 25 | return function(a, b){ 26 | if (usePlural || typeof a != 'string'){ 27 | for (var k in a) self.call(this, k, a[k]); 28 | } else { 29 | self.call(this, a, b); 30 | } 31 | return this; 32 | }; 33 | }; 34 | 35 | Function.prototype.overloadGetter = function(usePlural){ 36 | var self = this; 37 | return function(a){ 38 | var args, result; 39 | if (usePlural || typeof a != 'string') args = a; 40 | else if (arguments.length > 1) args = arguments; 41 | if (args){ 42 | result = {}; 43 | for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]); 44 | } else { 45 | result = self.call(this, a); 46 | } 47 | return result; 48 | }; 49 | }; 50 | 51 | Function.prototype.extend = function(key, value){ 52 | this[key] = value; 53 | }.overloadSetter(); 54 | 55 | Function.prototype.implement = function(key, value){ 56 | this.prototype[key] = value; 57 | }.overloadSetter(); 58 | 59 | // typeOf, instanceOf 60 | 61 | var typeOf = this.typeOf = function(item){ 62 | if (item == null) return 'null'; 63 | if (item.$family) return item.$family(); 64 | 65 | if (item.nodeName){ 66 | if (item.nodeType == 1) return 'element'; 67 | if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace'; 68 | } else if (typeof item.length == 'number'){ 69 | if (item.callee) return 'arguments'; 70 | if ('item' in item) return 'collection'; 71 | } 72 | 73 | return typeof item; 74 | }; 75 | 76 | var instanceOf = this.instanceOf = function(item, object){ 77 | if (item == null) return false; 78 | var constructor = item.$constructor || item.constructor; 79 | if (object == null) return constructor; 80 | while (constructor){ 81 | if (constructor === object) return true; 82 | constructor = constructor.parent; 83 | } 84 | return item instanceof object; 85 | }; 86 | 87 | // From 88 | 89 | Function.from = function(item){ 90 | return (typeOf(item) == 'function') ? item : function(){ 91 | return item; 92 | }; 93 | }; 94 | 95 | Array.from = function(item){ 96 | if (item == null) return []; 97 | return (Type.isEnumerable(item)) ? (typeOf(item) == 'array') ? item : Array.prototype.slice.call(item) : [item]; 98 | }; 99 | 100 | Number.from = function(item){ 101 | var number = parseFloat(item); 102 | return isFinite(number) ? number : null; 103 | }; 104 | 105 | String.from = function(item){ 106 | return item + ''; 107 | }; 108 | 109 | // hide, protect 110 | 111 | Function.implement({ 112 | 113 | hide: function(){ 114 | this.$hidden = true; 115 | return this; 116 | }, 117 | 118 | protect: function(){ 119 | this.$protected = true; 120 | return this; 121 | } 122 | 123 | }); 124 | 125 | // Type 126 | 127 | var Type = this.Type = function(name, object){ 128 | 129 | var lower = (name || '').toLowerCase(); 130 | 131 | if (name) Type['is' + name] = function(item){ 132 | return (typeOf(item) == lower); 133 | }; 134 | 135 | if (object == null) return null; 136 | 137 | if (name){ 138 | object.prototype.$family = function(){ 139 | return lower; 140 | }.hide(); 141 | object.$name = lower; 142 | } 143 | 144 | object.extend(this); 145 | object.$constructor = Type; 146 | object.prototype.$constructor = object; 147 | 148 | return object; 149 | }; 150 | 151 | Type.isEnumerable = function(item){ 152 | return (typeof item == 'object' && typeof item.length == 'number'); 153 | }; 154 | 155 | var hooks = {}; 156 | 157 | var hooksOf = function(object){ 158 | var type = typeOf(object.prototype); 159 | return hooks[type] || (hooks[type] = []); 160 | }; 161 | 162 | var implement = function(name, method){ 163 | if (method && method.$hidden) return this; 164 | 165 | var hooks = hooksOf(this); 166 | 167 | for (var i = 0; i < hooks.length; i++){ 168 | var hook = hooks[i]; 169 | if (typeOf(hook) == 'type') implement.call(hook, name, method); 170 | else hook.call(this, name, method); 171 | } 172 | 173 | var previous = this.prototype[name]; 174 | if (previous == null || !previous.$protected) this.prototype[name] = method; 175 | 176 | if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){ 177 | return method.apply(item, Array.prototype.slice.call(arguments, 1)); 178 | }); 179 | 180 | return this; 181 | }; 182 | 183 | var extend = function(name, method){ 184 | if (method && method.$hidden) return this; 185 | var previous = this[name]; 186 | if (previous == null || !previous.$protected) this[name] = method; 187 | return this; 188 | }; 189 | 190 | Type.implement({ 191 | 192 | implement: implement.overloadSetter(), 193 | 194 | extend: extend.overloadSetter(), 195 | 196 | alias: function(key, value){ 197 | implement.call(this, key, this.prototype[value]); 198 | }.overloadSetter(), 199 | 200 | mirror: function(hook){ 201 | hooksOf(this).push(hook); 202 | return this; 203 | } 204 | 205 | }); 206 | 207 | new Type('Type', Type); 208 | 209 | // Default Types 210 | 211 | var force = function(type, methods){ 212 | var object = new Type(type, this[type]); 213 | 214 | var prototype = object.prototype; 215 | 216 | for (var i = 0, l = methods.length; i < l; i++){ 217 | var name = methods[i]; 218 | 219 | var generic = object[name]; 220 | if (generic) generic.protect(); 221 | 222 | var proto = prototype[name]; 223 | if (proto){ 224 | delete prototype[name]; 225 | prototype[name] = proto.protect(); 226 | } 227 | } 228 | 229 | return object.implement(object.prototype); 230 | }; 231 | 232 | force('String', [ 233 | 'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search', 234 | 'slice', 'split', 'substr', 'substring', 'toLowerCase', 'toUpperCase' 235 | ]); 236 | 237 | force('Array', [ 238 | 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', 239 | 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight' 240 | ]); 241 | 242 | force('Number', ['toExponential', 'toFixed', 'toLocaleString', 'toPrecision']); 243 | 244 | force('Function', ['apply', 'call']); 245 | 246 | force('RegExp', ['exec', 'test']); 247 | 248 | force('Date', ['now']); 249 | 250 | Date.extend('now', function(){ 251 | return +(new Date); 252 | }); 253 | 254 | new Type('Boolean', Boolean); 255 | 256 | // fixes NaN returning as Number 257 | 258 | Number.prototype.$family = function(){ 259 | return (isFinite(this)) ? 'number' : 'null'; 260 | }.hide(); 261 | 262 | // forEach, each 263 | 264 | Object.extend('forEach', function(object, fn, bind){ 265 | for (var key in object) fn.call(bind, object[key], key, object); 266 | }); 267 | 268 | Object.each = Object.forEach; 269 | 270 | Array.implement('forEach', function(fn, bind){ 271 | for (var i = 0, l = this.length; i < l; i++){ 272 | if (i in this) fn.call(bind, this[i], i, this); 273 | } 274 | }).alias('each', 'forEach'); 275 | 276 | // Array & Object cloning 277 | 278 | var cloneOf = function(item){ 279 | switch (typeOf(item)){ 280 | case 'array': return item.clone(); 281 | case 'object': return Object.clone(item); 282 | default: return item; 283 | } 284 | }; 285 | 286 | Array.implement('clone', function(){ 287 | var i = this.length, clone = new Array(i); 288 | while (i--) clone[i] = cloneOf(this[i]); 289 | return clone; 290 | }); 291 | 292 | Object.extend('clone', function(object){ 293 | var clone = {}; 294 | for (var key in object) clone[key] = cloneOf(object[key]); 295 | return clone; 296 | }); 297 | 298 | // Object merging 299 | 300 | var merge = function(source, key, current){ 301 | switch (typeOf(current)){ 302 | case 'object': 303 | if (typeOf(source[key]) == 'object') Object.merge(source[key], current); 304 | else source[key] = Object.clone(current); 305 | break; 306 | case 'array': source[key] = current.clone(); break; 307 | default: source[key] = current; 308 | } 309 | return source; 310 | }; 311 | 312 | Object.extend('merge', function(source, k, v){ 313 | if (typeof k == 'string') return merge(source, k, v); 314 | for (var i = 1, l = arguments.length; i < l; i++){ 315 | var object = arguments[i]; 316 | for (var key in object) merge(source, key, object[key]); 317 | } 318 | return source; 319 | }); 320 | 321 | // Object-less types 322 | 323 | ['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){ 324 | Type(name); 325 | }); 326 | 327 | // UID generator 328 | 329 | var UID = 0; 330 | 331 | this.uniqueID = function(){ 332 | return (Date.now() + (UID++)).toString(36); 333 | }; 334 | 335 | })(); 336 | 337 | 338 | /* 339 | --- 340 | name: Array 341 | description: Array prototypes and generics. 342 | requires: Type 343 | provides: Array 344 | ... 345 | */ 346 | 347 | Array.implement({ 348 | 349 | filter: function(fn, bind){ 350 | var results = []; 351 | for (var i = 0, l = this.length; i < l; i++){ 352 | if ((i in this) && fn.call(bind, this[i], i, this)) results.push(this[i]); 353 | } 354 | return results; 355 | }, 356 | 357 | pair: function(fn, bind){ 358 | var object = {}; 359 | for (var i = 0, l = this.length; i < l; i++){ 360 | if (i in this) object[this[i]] = fn.call(bind, this[i], i, this); 361 | } 362 | return object; 363 | }, 364 | 365 | indexOf: function(item, from){ 366 | for (var l = this.length, i = (from < 0) ? Math.max(0, l + from) : from || 0; i < l; i++){ 367 | if (this[i] === item) return i; 368 | } 369 | return -1; 370 | }, 371 | 372 | map: function(fn, bind){ 373 | var results = []; 374 | for (var i = 0, l = this.length; i < l; i++){ 375 | if (i in this) results[i] = fn.call(bind, this[i], i, this); 376 | } 377 | return results; 378 | }, 379 | 380 | every: function(fn, bind){ 381 | for (var i = 0, l = this.length; i < l; i++){ 382 | if ((i in this) && !fn.call(bind, this[i], i, this)) return false; 383 | } 384 | return true; 385 | }, 386 | 387 | some: function(fn, bind){ 388 | for (var i = 0, l = this.length; i < l; i++){ 389 | if ((i in this) && fn.call(bind, this[i], i, this)) return true; 390 | } 391 | return false; 392 | }, 393 | 394 | clean: function(){ 395 | return this.filter(function(item){ 396 | return item != null; 397 | }); 398 | }, 399 | 400 | pick: function(){ 401 | for (var i = 0, l = this.length; i < l; i++){ 402 | if (this[i] != null) return this[i]; 403 | } 404 | return null; 405 | }, 406 | 407 | invoke: function(methodName){ 408 | var args = Array.slice(arguments, 1), results = []; 409 | for (var i = 0, j = this.length; i < j; i++){ 410 | var item = this[i]; 411 | results.push(item[methodName].apply(item, args)); 412 | } 413 | return results; 414 | }, 415 | 416 | append: function(array){ 417 | this.push.apply(this, array); 418 | return this; 419 | }, 420 | 421 | contains: function(item, from){ 422 | return this.indexOf(item, from) != -1; 423 | }, 424 | 425 | last: function(){ 426 | return (this.length) ? this[this.length - 1] : null; 427 | }, 428 | 429 | random: function(){ 430 | return (this.length) ? this[Number.random(0, this.length - 1)] : null; 431 | }, 432 | 433 | include: function(item){ 434 | if (!this.contains(item)) this.push(item); 435 | return this; 436 | }, 437 | 438 | combine: function(array){ 439 | for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); 440 | return this; 441 | }, 442 | 443 | erase: function(item){ 444 | for (var i = this.length; i--; i){ 445 | if (this[i] === item) this.splice(i, 1); 446 | } 447 | return this; 448 | }, 449 | 450 | empty: function(){ 451 | this.length = 0; 452 | return this; 453 | }, 454 | 455 | flatten: function(){ 456 | var array = []; 457 | for (var i = 0, l = this.length; i < l; i++){ 458 | var ti = this[i], t = typeOf(this[i]); 459 | if (t == 'null') continue; 460 | array = array.concat((t == 'array' || t == 'collection' || t == 'arguments' || instanceOf(ti, Array)) ? Array.flatten(ti) : ti); 461 | } 462 | return array; 463 | }, 464 | 465 | item: function(at){ 466 | if (at < 0) at = (at % this.length) + this.length; 467 | return (at < 0 || at >= this.length || this[at] == null) ? null : this[at]; 468 | } 469 | 470 | }); 471 | 472 | 473 | /* 474 | --- 475 | name: String 476 | description: String prototypes and generics. 477 | requires: Type 478 | provides: String 479 | ... 480 | */ 481 | 482 | String.implement({ 483 | 484 | test: function(regex, params){ 485 | return ((typeOf(regex) == 'string') ? new RegExp(regex, params) : regex).test(this); 486 | }, 487 | 488 | contains: function(string, separator){ 489 | return ((separator) ? (separator + this + separator).indexOf(separator + string + separator) : this.indexOf(string)) > -1; 490 | }, 491 | 492 | trim: function(){ 493 | return this.replace(/^\s+|\s+$/g, ''); 494 | }, 495 | 496 | clean: function(){ 497 | return this.replace(/\s+/g, ' ').trim(); 498 | }, 499 | 500 | camelCase: function(){ 501 | return this.replace(/-\D/g, function(match){ 502 | return match.charAt(1).toUpperCase(); 503 | }); 504 | }, 505 | 506 | hyphenate: function(){ 507 | return this.replace(/[A-Z]/g, function(match){ 508 | return '-' + match.toLowerCase(); 509 | }); 510 | }, 511 | 512 | capitalize: function(){ 513 | return this.replace(/\b[a-z]/g, function(match){ 514 | return match.toUpperCase(); 515 | }); 516 | }, 517 | 518 | escapeRegExp: function(){ 519 | return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); 520 | }, 521 | 522 | substitute: function(object, regexp){ 523 | return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ 524 | if (match.charAt(0) == '\\') return match.slice(1); 525 | return (object[name] != null) ? object[name] : ''; 526 | }); 527 | }, 528 | 529 | toInt: function(base){ 530 | return parseInt(this, base || 10); 531 | }, 532 | 533 | toFloat: function(){ 534 | return parseFloat(this); 535 | } 536 | 537 | }); 538 | 539 | 540 | /* 541 | --- 542 | name: Function 543 | description: Function prototypes and generics. 544 | requires: Type 545 | provides: Function 546 | ... 547 | */ 548 | 549 | Function.extend({ 550 | 551 | clear: function(timer){ 552 | clearInterval(timer); 553 | clearTimeout(timer); 554 | return null; 555 | }, 556 | 557 | stab: function(){ 558 | for (var i = 0, l = arguments.length; i < l; i++){ 559 | try { 560 | return arguments[i](); 561 | } catch (e){} 562 | } 563 | return null; 564 | } 565 | 566 | }); 567 | 568 | Function.implement({ 569 | 570 | attempt: function(args, bind){ 571 | try { 572 | return this.apply(bind, Array.from(args)); 573 | } catch (e){ 574 | return null; 575 | } 576 | }, 577 | 578 | bind: function(bind, args){ 579 | var self = this; 580 | if (args != null) args = Array.from(args); 581 | return function(){ 582 | return self.apply(bind, args || arguments); 583 | }; 584 | }, 585 | 586 | delay: function(delay, bind, args){ 587 | return setTimeout(this.bind(bind, args), delay); 588 | }, 589 | 590 | pass: function(args, bind){ 591 | return this.bind(bind, args); 592 | }, 593 | 594 | periodical: function(periodical, bind, args){ 595 | return setInterval(this.bind(bind, args), periodical); 596 | }, 597 | 598 | run: function(args, bind){ 599 | return this.apply(bind, Array.from(args)); 600 | } 601 | 602 | }); 603 | 604 | 605 | /* 606 | --- 607 | name: Number 608 | description: Number prototypes and generics. 609 | requires: Type 610 | provides: Number 611 | ... 612 | */ 613 | 614 | Number.extend({ 615 | 616 | random: function(min, max){ 617 | return Math.floor(Math.random() * (max - min + 1) + min); 618 | }, 619 | 620 | toInt: function(number, base){ 621 | return parseInt(number, base || 10); 622 | }, 623 | 624 | toFloat: function(number){ 625 | return parseFloat(number); 626 | } 627 | 628 | }); 629 | 630 | Number.implement({ 631 | 632 | limit: function(min, max){ 633 | return Math.min(max, Math.max(min, this)); 634 | }, 635 | 636 | round: function(precision){ 637 | precision = Math.pow(10, precision || 0); 638 | return Math.round(this * precision) / precision; 639 | }, 640 | 641 | times: function(fn, bind){ 642 | for (var i = 0; i < this; i++) fn.call(bind, i, null, this); 643 | } 644 | 645 | }); 646 | 647 | ['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan'].each(function(name){ 648 | Number.extend(name, Math[name]).implement(name, function(){ 649 | return Math[name].apply(null, [this].concat(Array.slice(arguments))); 650 | }); 651 | }); 652 | 653 | 654 | /* 655 | --- 656 | name: Object 657 | description: Object generics 658 | requires: Type 659 | provides: Object 660 | ... 661 | */ 662 | 663 | Object.extend({ 664 | 665 | length: function(object){ 666 | var length = 0; 667 | for (var key in object) length++; 668 | return length; 669 | }, 670 | 671 | from: function(keys, values){ 672 | var object = {}; 673 | for (var i = 0; i < keys.length; i++) object[keys[i]] = nil(values[i]); 674 | return object; 675 | }, 676 | 677 | append: function(original){ 678 | for (var i = 1; i < arguments.length; i++){ 679 | var extended = arguments[i] || {}; 680 | for (var key in extended) original[key] = extended[key]; 681 | } 682 | return original; 683 | }, 684 | 685 | subset: function(object, keys){ 686 | var results = {}; 687 | for (var i = 0, l = keys.length; i < l; i++){ 688 | var k = keys[i], value = object[k]; 689 | results[k] = nil(value); 690 | } 691 | return results; 692 | }, 693 | 694 | map: function(object, fn, bind){ 695 | var results = {}; 696 | for (var key in object) results[key] = fn.call(bind, object[key], key, object); 697 | return results; 698 | }, 699 | 700 | filter: function(object, fn, bind){ 701 | var results = {}; 702 | for (var key in object){ 703 | if (fn.call(bind, object[key], key, object)) results[key] = object[key]; 704 | } 705 | return results; 706 | }, 707 | 708 | every: function(object, fn, bind){ 709 | for (var key in object){ 710 | if (!fn.call(bind, object[key], key)) return false; 711 | } 712 | return true; 713 | }, 714 | 715 | some: function(object, fn, bind){ 716 | for (var key in object){ 717 | if (fn.call(bind, object[key], key)) return true; 718 | } 719 | return false; 720 | }, 721 | 722 | keys: function(object){ 723 | var keys = []; 724 | for (var key in object) keys.push(key); 725 | return keys; 726 | }, 727 | 728 | values: function(object){ 729 | var values = []; 730 | for (var key in object) values.push(object[key]); 731 | return values; 732 | } 733 | 734 | }); 735 | 736 | 737 | /* 738 | --- 739 | name: Accessor 740 | description: Accessor 741 | requires: [typeOf, Array, Function, String, Object] 742 | provides: Accessor 743 | ... 744 | */ 745 | 746 | (function(global){ 747 | 748 | /* Accessor */ 749 | 750 | this.Accessor = function(singular, plural){ 751 | 752 | singular = (singular || '').capitalize(); 753 | if (!plural) plural = singular + 's'; 754 | 755 | var define = 'define', lookup = 'lookup', match = 'match', each = 'each'; 756 | 757 | if (this === global) return [define + singular, define + plural, lookup + singular, lookup + plural, match + singular, each + singular].pair(function(name){ 758 | return function(){ 759 | Object.append(this, new Accessor(singular, plural)); 760 | return this[name].apply(this, arguments); 761 | }; 762 | }); 763 | 764 | var accessor = {}, matchers = []; 765 | 766 | this[define + singular] = function(key, value){ 767 | if (typeOf(key) == 'regexp') matchers.push({'regexp': key, 'action': value}); 768 | else accessor[key] = value; 769 | return this; 770 | }; 771 | 772 | this[define + plural] = function(object){ 773 | for (var key in object) accessor[key] = object[key]; 774 | return this; 775 | }; 776 | 777 | this[match + singular] = function(name){ 778 | for (var l = matchers.length; l--; l){ 779 | var matcher = matchers[l], match = name.match(matcher.regexp); 780 | if (match && (match = match.slice(1))) return function(){ 781 | return matcher.action.apply(this, Array.slice(arguments).append(match)); 782 | }; 783 | } 784 | return null; 785 | }; 786 | 787 | this[lookup + singular] = function(key){ 788 | return accessor[key] || null; 789 | }; 790 | 791 | this[lookup + plural] = function(keys){ 792 | return Object.subset(accessor, keys); 793 | }; 794 | 795 | this[each + singular] = function(fn, bind){ 796 | for (var p in accessor) fn.call(bind, accessor[p], p); 797 | }; 798 | 799 | return this; 800 | 801 | }; 802 | 803 | })(this); 804 | 805 | 806 | /* 807 | --- 808 | name: Class 809 | description: Contains the Class Function for easily creating, extending, and implementing reusable Classes. 810 | requires: [typeOf, instanceOf, Array, String, Function, Number, Accessor] 811 | provides: Class 812 | ... 813 | */ 814 | 815 | (function(){ 816 | 817 | var Class = this.Class = new Type('Class', function(params){ 818 | 819 | if (instanceOf(params, Function)) params = {'initialize': params}; 820 | 821 | var newClass = function(){ 822 | reset(this); 823 | if (newClass.$prototyping) return this; 824 | this.$caller = null; 825 | var value = (this.initialize) ? this.initialize.apply(this, arguments) : this; 826 | this.$caller = this.caller = null; 827 | return value; 828 | }.extend(this); 829 | 830 | newClass.implement(params); 831 | 832 | newClass.$constructor = Class; 833 | newClass.prototype.$constructor = newClass; 834 | newClass.prototype.parent = parent; 835 | 836 | return newClass; 837 | 838 | }); 839 | 840 | var parent = function(){ 841 | if (!this.$caller) throw new Error('The method "parent" cannot be called.'); 842 | var name = this.$caller.$name, parent = this.$caller.$owner.parent; 843 | var previous = (parent) ? parent.prototype[name] : null; 844 | if (!previous) throw new Error('The method "' + name + '" has no parent.'); 845 | return previous.apply(this, arguments); 846 | }; 847 | 848 | var reset = function(object){ 849 | for (var key in object){ 850 | var value = object[key]; 851 | switch (typeOf(value)){ 852 | case 'object': 853 | var F = function(){}; 854 | F.prototype = value; 855 | var instance = new F; 856 | object[key] = reset(instance); 857 | break; 858 | case 'array': object[key] = value.clone(); break; 859 | } 860 | } 861 | return object; 862 | }; 863 | 864 | var wrap = function(self, key, method){ 865 | if (method.$origin) method = method.$origin; 866 | 867 | return function(){ 868 | if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.'); 869 | var caller = this.caller, current = this.$caller; 870 | this.caller = current; this.$caller = arguments.callee; 871 | var result = method.apply(this, arguments); 872 | this.$caller = current; this.caller = caller; 873 | return result; 874 | }.extend({$owner: self, $origin: method, $name: key}); 875 | }; 876 | 877 | Class.extend(new Accessor('Mutator')); 878 | 879 | var implement = function(key, value, retainOwner){ 880 | 881 | var mutator = Class.matchMutator(key) || Class.lookupMutator(key); 882 | 883 | if (mutator){ 884 | value = mutator.call(this, value); 885 | if (value == null) return; 886 | } 887 | 888 | if (typeOf(value) == 'function'){ 889 | if (value.$hidden) return; 890 | this.prototype[key] = (retainOwner) ? value : wrap(this, key, value); 891 | } else { 892 | Object.merge(this.prototype, key, value); 893 | } 894 | 895 | }; 896 | 897 | var implementClass = function(item){ 898 | var instance = new item; 899 | for (var key in instance) implement.call(this, key, instance[key], true); 900 | }; 901 | 902 | Class.implement('implement', function(a, b){ 903 | 904 | switch (typeOf(a)){ 905 | case 'string': implement.call(this, a, b); break; 906 | case 'class': implementClass.call(this, a); break; 907 | default: for (var p in a) implement.call(this, p, a[p]); break; 908 | } 909 | 910 | return this; 911 | 912 | }); 913 | 914 | Class.defineMutators({ 915 | 916 | Extends: function(parent){ 917 | this.parent = parent; 918 | parent.$prototyping = true; 919 | var proto = new parent; 920 | delete parent.$prototyping; 921 | this.prototype = proto; 922 | }, 923 | 924 | Implements: function(items){ 925 | Array.from(items).each(function(item){ 926 | this.implement(item); 927 | }, this); 928 | } 929 | 930 | }); 931 | 932 | Class.defineMutator(/^protected\s(\w+)$/, function(fn, name){ 933 | implement.call(this, name, fn.protect()); 934 | }); 935 | 936 | Class.defineMutator(/^linked\s(\w+)$/, function(value, name){ 937 | this.prototype[name] = value; 938 | }); 939 | 940 | })(); 941 | 942 | --------------------------------------------------------------------------------