1 /** 2 * @author <a href="http://www.creynders.be">Camille Reynders</a> 3 */ 4 ( function ( scope ) { 5 6 "use strict"; 7 8 /** 9 * @namespace 10 */ 11 var dijon = { 12 /** 13 * framework version number 14 * @constant 15 * @type String 16 */ 17 VERSION:'0.5.3' 18 };//dijon 19 20 21 //======================================// 22 // dijon.System 23 //======================================// 24 25 /** 26 * @class dijon.System 27 * @constructor 28 */ 29 dijon.System = function () { 30 /** @private */ 31 this._mappings = {}; 32 33 /** @private */ 34 this._outlets = {}; 35 36 /** @private */ 37 this._handlers = {}; 38 39 /** 40 * When <code>true</code> injections are made only if an object has a property with the mapped outlet name.<br/> 41 * <strong>Set to <code>false</code> at own risk</strong>, may have quite undesired side effects. 42 * @example 43 * system.strictInjections = true 44 * var o = {}; 45 * system.mapSingleton( 'userModel', UserModel ); 46 * system.mapOutlet( 'userModel' ); 47 * system.injectInto( o ); 48 * 49 * //o is unchanged 50 * 51 * system.strictInjections = false; 52 * system.injectInto( o ); 53 * 54 * //o now has a member 'userModel' which holds a reference to the singleton instance 55 * //of UserModel 56 * @type Boolean 57 * @default true 58 */ 59 this.strictInjections = true; 60 61 /** 62 * Enables the automatic mapping of outlets for mapped values, singletons and classes 63 * When this is true any value, singleton or class that is mapped will automatically be mapped as a global outlet 64 * using the value of <code>key</code> as outlet name 65 * 66 * @example 67 * var o = { 68 * userModel : undefined; //inject 69 * } 70 * system.mapSingleton( 'userModel', UserModel ); 71 * system.injectInto( o ): 72 * //o.userModel now holds a reference to the singleton instance of UserModel 73 * @type Boolean 74 * @default false 75 */ 76 this.autoMapOutlets = false; 77 78 /** 79 * The name of the method that will be called for all instances, right after injection has occured. 80 * @type String 81 * @default 'setup' 82 */ 83 this.postInjectionHook = 'setup'; 84 85 };//dijon.System 86 87 dijon.System.prototype = { 88 89 /** 90 * @private 91 * @param {Class} clazz 92 */ 93 _createAndSetupInstance:function ( key, Clazz ) { 94 var instance = new Clazz(); 95 this.injectInto( instance, key ); 96 return instance; 97 }, 98 99 /** 100 * @private 101 * @param {String} key 102 * @param {Boolean} overrideRules 103 * @return {Object} 104 */ 105 _retrieveFromCacheOrCreate:function ( key, overrideRules ) { 106 if ( typeof overrideRules === 'undefined' ) { 107 overrideRules = false; 108 } 109 var output; 110 if ( this._mappings.hasOwnProperty( key ) ) { 111 var config = this._mappings[ key ]; 112 if ( !overrideRules && config.isSingleton ) { 113 if ( config.object == null ) { 114 config.object = this._createAndSetupInstance( key, config.clazz ); 115 } 116 output = config.object; 117 } else { 118 if ( config.clazz ) { 119 output = this._createAndSetupInstance( key, config.clazz ); 120 } else { 121 //TODO shouldn't this be null 122 output = config.object; 123 } 124 } 125 } else { 126 throw new Error( 1000 ); 127 } 128 return output; 129 }, 130 131 132 /** 133 * defines <code>outletName</code> as an injection point in <code>targetKey</code>for the object mapped to <code>sourceKey</code> 134 * @example 135 system.mapSingleton( 'userModel', TestClassA ); 136 var o = { 137 user : undefined //inject 138 } 139 system.mapOutlet( 'userModel', 'o', 'user' ); 140 system.mapValue( 'o', o ); 141 142 var obj = system.getObject( 'o' ); 143 * //obj.user holds a reference to the singleton instance of UserModel 144 * 145 * @example 146 system.mapSingleton( 'userModel', TestClassA ); 147 var o = { 148 userModel : undefined //inject 149 } 150 system.mapOutlet( 'userModel', 'o' ); 151 system.mapValue( 'o', o ); 152 153 var obj = system.getObject( 'o' ); 154 * //obj.userModel holds a reference to the singleton instance of UserModel 155 * 156 * @example 157 system.mapSingleton( 'userModel', TestClassA ); 158 system.mapOutlet( 'userModel' ); 159 var o = { 160 userModel : undefined //inject 161 } 162 system.mapValue( 'o', o ); 163 164 var obj = system.getObject( 'o' ); 165 * //o.userModel holds a reference to the singleton instance of userModel 166 * 167 * @param {String} sourceKey the key mapped to the object that will be injected 168 * @param {String} [targetKey='global'] the key the outlet is assigned to. 169 * @param {String} [outletName=sourceKey] the name of the property used as an outlet.<br/> 170 * @return {dijon.System} 171 * @see dijon.System#unmapOutlet 172 */ 173 mapOutlet:function ( sourceKey, targetKey, outletName ) { 174 if ( typeof sourceKey === 'undefined' ) { 175 throw new Error( 1010 ); 176 } 177 targetKey = targetKey || "global"; 178 outletName = outletName || sourceKey; 179 180 if ( !this._outlets.hasOwnProperty( targetKey ) ) { 181 this._outlets[ targetKey ] = {}; 182 } 183 this._outlets[ targetKey ][ outletName ] = sourceKey; 184 185 return this; 186 }, 187 188 /** 189 * Retrieve (or create) the object mapped to <code>key</code> 190 * @example 191 * system.mapValue( 'foo', 'bar' ); 192 * var b = system.getObject( 'foo' ); //now contains 'bar' 193 * @param {Object} key 194 * @return {Object} 195 */ 196 getObject:function ( key ) { 197 if ( typeof key === 'undefined' ) { 198 throw new Error( 1020 ); 199 } 200 return this._retrieveFromCacheOrCreate( key ); 201 }, 202 203 /** 204 * Maps <code>useValue</code> to <code>key</code> 205 * @example 206 * system.mapValue( 'foo', 'bar' ); 207 * var b = system.getObject( 'foo' ); //now contains 'bar' 208 * @param {String} key 209 * @param {Object} useValue 210 * @return {dijon.System} 211 */ 212 mapValue:function ( key, useValue ) { 213 if ( typeof key === 'undefined' ) { 214 throw new Error( 1030 ); 215 } 216 this._mappings[ key ] = { 217 clazz:null, 218 object:useValue, 219 isSingleton:true 220 }; 221 if ( this.autoMapOutlets ) { 222 this.mapOutlet( key ); 223 } 224 if ( this.hasMapping( key )) { 225 this.injectInto( useValue, key ); 226 } 227 return this; 228 }, 229 230 /** 231 * Returns whether the key is mapped to an object 232 * @example 233 * system.mapValue( 'foo', 'bar' ); 234 * var isMapped = system.hasMapping( 'foo' ); 235 * @param {String} key 236 * @return {Boolean} 237 */ 238 hasMapping:function ( key ) { 239 if ( typeof key === 'undefined' ) { 240 throw new Error( 1040 ); 241 } 242 return this._mappings.hasOwnProperty( key ); 243 }, 244 245 /** 246 * Maps <code>clazz</code> as a factory to <code>key</code> 247 * @example 248 * var SomeClass = function(){ 249 * } 250 * system.mapClass( 'o', SomeClass ); 251 * 252 * var s1 = system.getObject( 'o' ); 253 * var s2 = system.getObject( 'o' ); 254 * 255 * //s1 and s2 reference two different instances of SomeClass 256 * 257 * @param {String} key 258 * @param {Function} clazz 259 * @return {dijon.System} 260 */ 261 mapClass:function ( key, clazz ) { 262 if ( typeof key === 'undefined' ) { 263 throw new Error( 1050 ); 264 } 265 if ( typeof clazz === 'undefined' ) { 266 throw new Error( 1051 ); 267 } 268 this._mappings[ key ] = { 269 clazz:clazz, 270 object:null, 271 isSingleton:false 272 }; 273 if ( this.autoMapOutlets ) { 274 this.mapOutlet( key ); 275 } 276 return this; 277 }, 278 279 /** 280 * Maps <code>clazz</code> as a singleton factory to <code>key</code> 281 * @example 282 * var SomeClass = function(){ 283 * } 284 * system.mapSingleton( 'o', SomeClass ); 285 * 286 * var s1 = system.getObject( 'o' ); 287 * var s2 = system.getObject( 'o' ); 288 * 289 * //s1 and s2 reference the same instance of SomeClass 290 * 291 * @param {String} key 292 * @param {Function} clazz 293 * @return {dijon.System} 294 */ 295 mapSingleton:function ( key, clazz ) { 296 if ( typeof key === 'undefined' ) { 297 throw new Error( 1060 ); 298 } 299 if ( typeof clazz === 'undefined' ) { 300 throw new Error( 1061 ); 301 } 302 this._mappings[ key ] = { 303 clazz:clazz, 304 object:null, 305 isSingleton:true 306 }; 307 if ( this.autoMapOutlets ) { 308 this.mapOutlet( key ); 309 } 310 return this; 311 }, 312 313 /** 314 * Force instantiation of the class mapped to <code>key</code>, whether it was mapped as a singleton or not. 315 * When a value was mapped, the value will be returned. 316 * TODO: should this last rule be changed? 317 * @example 318 var SomeClass = function(){ 319 } 320 system.mapClass( 'o', SomeClass ); 321 322 var s1 = system.getObject( 'o' ); 323 var s2 = system.getObject( 'o' ); 324 * //s1 and s2 reference different instances of SomeClass 325 * 326 * @param {String} key 327 * @return {Object} 328 */ 329 instantiate:function ( key ) { 330 if ( typeof key === 'undefined' ) { 331 throw new Error( 1070 ); 332 } 333 return this._retrieveFromCacheOrCreate( key, true ); 334 }, 335 336 /** 337 * Perform an injection into an object's mapped outlets, satisfying all it's dependencies 338 * @example 339 * var UserModel = function(){ 340 * } 341 * system.mapSingleton( 'userModel', UserModel ); 342 * var SomeClass = function(){ 343 * user = undefined; //inject 344 * } 345 * system.mapSingleton( 'o', SomeClass ); 346 * system.mapOutlet( 'userModel', 'o', 'user' ); 347 * 348 * var foo = { 349 * user : undefined //inject 350 * } 351 * 352 * system.injectInto( foo, 'o' ); 353 * 354 * //foo.user now holds a reference to the singleton instance of UserModel 355 * @param {Object} instance 356 * @param {String} [key] use the outlet mappings as defined for <code>key</code>, otherwise only the globally 357 * mapped outlets will be used. 358 * @return {dijon.System} 359 */ 360 injectInto:function ( instance, key ) { 361 if ( typeof instance === 'undefined' ) { 362 throw new Error( 1080 ); 363 } 364 if( ( typeof instance === 'object' ) ){ 365 var o = []; 366 if ( this._outlets.hasOwnProperty( 'global' ) ) { 367 o.push( this._outlets[ 'global' ] ); 368 } 369 if ( typeof key !== 'undefined' && this._outlets.hasOwnProperty( key ) ) { 370 o.push( this._outlets[ key ] ); 371 } 372 for ( var i in o ) { 373 var l = o [ i ]; 374 for ( var outlet in l ) { 375 var source = l[ outlet ]; 376 //must be "in" [!] 377 if ( !this.strictInjections || outlet in instance ) { 378 instance[ outlet ] = this.getObject( source ); 379 } 380 } 381 } 382 if ( "setup" in instance ) { 383 instance.setup.call( instance ); 384 } 385 } 386 return this; 387 }, 388 389 /** 390 * Remove the mapping of <code>key</code> from the system 391 * @param {String} key 392 * @return {dijon.System} 393 */ 394 unmap:function ( key ) { 395 if ( typeof key === 'undefined' ) { 396 throw new Error( 1090 ); 397 } 398 delete this._mappings[ key ]; 399 400 return this; 401 }, 402 403 /** 404 * removes an injection point mapping for a given object mapped to <code>key</code> 405 * @param {String} target 406 * @param {String} outlet 407 * @return {dijon.System} 408 * @see dijon.System#addOutlet 409 */ 410 unmapOutlet:function ( target, outlet ) { 411 if ( typeof target === 'undefined' ) { 412 throw new Error( 1100 ); 413 } 414 if ( typeof outlet === 'undefined' ) { 415 throw new Error( 1101 ); 416 } 417 delete this._outlets[ target ][ outlet ]; 418 419 return this; 420 }, 421 422 /** 423 * maps a handler for an event/route.<br/> 424 * @example 425 var hasExecuted = false; 426 var userView = { 427 showUserProfile : function(){ 428 hasExecuted = true; 429 } 430 } 431 system.mapValue( 'userView', userView ); 432 system.mapHandler( 'user/profile', 'userView', 'showUserProfile' ); 433 system.notify( 'user/profile' ); 434 //hasExecuted is true 435 * @example 436 * var userView = { 437 * showUserProfile : function(){ 438 * //do stuff 439 * } 440 * } 441 * system.mapValue( 'userView', userView ); 442 * <strong>system.mapHandler( 'showUserProfile', 'userView' );</strong> 443 * system.notify( 'showUserProfile' ); 444 * 445 * //userView.showUserProfile is called 446 * @example 447 * var showUserProfile = function(){ 448 * //do stuff 449 * } 450 * <strong>system.mapHandler( 'user/profile', undefined, showUserProfile );</strong> 451 * system.notify( 'user/profile' ); 452 * 453 * //showUserProfile is called 454 * @example 455 * var userView = {}; 456 * var showUserProfile = function(){ 457 * //do stuff 458 * } 459 * system.mapValue( 'userView', userView ); 460 * <strong>system.mapHandler( 'user/profile', 'userView', showUserProfile );</strong> 461 * system.notify( 'user/profile' ); 462 * 463 * //showUserProfile is called within the scope of the userView object 464 * @example 465 * var userView = { 466 * showUserProfile : function(){ 467 * //do stuff 468 * } 469 * } 470 * system.mapValue( 'userView', userView ); 471 * <strong>system.mapHandler( 'user/profile', 'userView', 'showUserProfile', true );</strong> 472 * system.notify( 'user/profile' ); 473 * system.notify( 'user/profile' ); 474 * system.notify( 'user/profile' ); 475 * 476 * //userView.showUserProfile is called exactly once [!] 477 * @example 478 * var userView = { 479 * showUserProfile : function( route ){ 480 * //do stuff 481 * } 482 * } 483 * system.mapValue( 'userView', userView ); 484 * <strong>system.mapHandler( 'user/profile', 'userView', 'showUserProfile', false, true );</strong> 485 * system.notify( 'user/profile' ); 486 * 487 * //userView.showUserProfile is called and the route/eventName is passed to the handler 488 * @param {String} eventName/route 489 * @param {String} [key=undefined] If <code>key</code> is <code>undefined</code> the handler will be called without 490 * scope. 491 * @param {String|Function} [handler=eventName] If <code>handler</code> is <code>undefined</code> the value of 492 * <code>eventName</code> will be used as the name of the member holding the reference to the to-be-called function. 493 * <code>handler</code> accepts either a string, which will be used as the name of the member holding the reference 494 * to the to-be-called function, or a direct function reference. 495 * @param {Boolean} [oneShot=false] Defines whether the handler should be called exactly once and then automatically 496 * unmapped 497 * @param {Boolean} [passEvent=false] Defines whether the event object should be passed to the handler or not. 498 * @return {dijon.System} 499 * @see dijon.System#notify 500 * @see dijon.System#unmapHandler 501 */ 502 mapHandler:function ( eventName, key, handler, oneShot, passEvent ) { 503 if ( typeof eventName === 'undefined' ) { 504 throw new Error( 1110 ); 505 } 506 key = key || 'global'; 507 handler = handler || eventName; 508 509 if ( typeof oneShot === 'undefined' ) { 510 oneShot = false; 511 } 512 if ( typeof passEvent === 'undefined' ) { 513 passEvent = false; 514 } 515 if ( !this._handlers.hasOwnProperty( eventName ) ) { 516 this._handlers[ eventName ] = {}; 517 } 518 if ( !this._handlers[eventName].hasOwnProperty( key ) ) { 519 this._handlers[eventName][key] = []; 520 } 521 this._handlers[ eventName ][ key ].push( { 522 handler:handler, 523 oneShot:oneShot, 524 passEvent:passEvent 525 } ); 526 527 return this; 528 }, 529 530 /** 531 * Unmaps the handler for a specific event/route. 532 * @param {String} eventName Name of the event/route 533 * @param {String} [key=undefined] If <code>key</code> is <code>undefined</code> the handler is removed from the 534 * global mapping space. (If the same event is mapped globally and specifically for an object, then 535 * only the globally mapped one will be removed) 536 * @param {String | Function} [handler=eventName] 537 * @return {dijon.System} 538 * @see dijon.System#mapHandler 539 */ 540 unmapHandler:function ( eventName, key, handler ) { 541 if ( typeof eventName === 'undefined' ) { 542 throw new Error( 1120 ); 543 } 544 key = key || 'global'; 545 handler = handler || eventName; 546 547 if ( this._handlers.hasOwnProperty( eventName ) && this._handlers[ eventName ].hasOwnProperty( key ) ) { 548 var handlers = this._handlers[ eventName ][ key ]; 549 for ( var i in handlers ) { 550 var config = handlers[ i ]; 551 if ( config.handler === handler ) { 552 handlers.splice( i, 1 ); 553 break; 554 } 555 } 556 } 557 return this; 558 }, 559 560 /** 561 * calls all handlers mapped to <code>eventName/route</code> 562 * @param {String} eventName/route 563 * @return {dijon.System} 564 * @see dijon.System#mapHandler 565 */ 566 notify:function ( eventName ) { 567 if ( typeof eventName === 'undefined' ) { 568 throw new Error( 1130 ); 569 } 570 var argsWithEvent = Array.prototype.slice.call( arguments ); 571 var argsClean = argsWithEvent.slice( 1 ); 572 if ( this._handlers.hasOwnProperty( eventName ) ) { 573 var handlers = this._handlers[ eventName ]; 574 for ( var key in handlers ) { 575 var configs = handlers[ key ]; 576 var instance; 577 if ( key !== 'global' ) { 578 instance = this.getObject( key ); 579 } 580 var toBeDeleted = []; 581 var i, n; 582 for ( i = 0, n = configs.length ; i < n ; i++ ) { 583 var handler; 584 var config = configs[ i ]; 585 if ( instance && typeof config.handler === "string" ) { 586 handler = instance[ config.handler ]; 587 } else { 588 handler = config.handler; 589 } 590 591 //see deletion below 592 if ( config.oneShot ) { 593 toBeDeleted.unshift( i ); 594 } 595 596 if ( config.passEvent ) { 597 handler.apply( instance, argsWithEvent ); 598 } else { 599 handler.apply( instance, argsClean ); 600 } 601 } 602 603 //items should be deleted in reverse order 604 //either use push above and decrement here 605 //or use unshift above and increment here 606 for ( i = 0, n = toBeDeleted.length ; i < n ; i++ ) { 607 configs.splice( toBeDeleted[ i ], 1 ); 608 } 609 } 610 } 611 612 return this; 613 } 614 615 };//dijon.System.prototype 616 617 scope.dijon = dijon; 618 }( this )); 619 620 621