| 1 | /* |
|---|
| 2 | DOM Component Library - Copyright 2005 Six Apart |
|---|
| 3 | $Id$ |
|---|
| 4 | |
|---|
| 5 | Copyright (c) 2005, Six Apart, Ltd. |
|---|
| 6 | All rights reserved. |
|---|
| 7 | |
|---|
| 8 | Redistribution and use in source and binary forms, with or without |
|---|
| 9 | modification, are permitted provided that the following conditions are |
|---|
| 10 | met: |
|---|
| 11 | |
|---|
| 12 | * Redistributions of source code must retain the above copyright |
|---|
| 13 | notice, this list of conditions and the following disclaimer. |
|---|
| 14 | |
|---|
| 15 | * Redistributions in binary form must reproduce the above |
|---|
| 16 | copyright notice, this list of conditions and the following disclaimer |
|---|
| 17 | in the documentation and/or other materials provided with the |
|---|
| 18 | distribution. |
|---|
| 19 | |
|---|
| 20 | * Neither the name of "Six Apart" nor the names of its |
|---|
| 21 | contributors may be used to endorse or promote products derived from |
|---|
| 22 | this software without specific prior written permission. |
|---|
| 23 | |
|---|
| 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|---|
| 25 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|---|
| 26 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|---|
| 27 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|---|
| 28 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|---|
| 29 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|---|
| 30 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|---|
| 31 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|---|
| 32 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|---|
| 33 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|---|
| 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 35 | |
|---|
| 36 | * <br/><br/> |
|---|
| 37 | * <hr><br/><br/> |
|---|
| 38 | * Class <code>Component</code> class.<br/><br/> |
|---|
| 39 | * @object-prop <code>sentinels</code> <code>Array</code> A collection of |
|---|
| 40 | * hidden html input fields that bestow tabbability and modality upon components.<br/> |
|---|
| 41 | * @object-prop <code>ativatable</code> <code>boolean</code> Whether or not this component could |
|---|
| 42 | * be made active -- see below.<br/> |
|---|
| 43 | * @object-prop <code>ative</code> <code>boolean</code> Whether or not this component is active -- active |
|---|
| 44 | * components are managed by <code>App</code>.<br/> |
|---|
| 45 | * @object-prop <code>modal</code> <code>boolean</code> Whether or not this component is modal |
|---|
| 46 | * (is the only part of the application that may be currently interacted with by the user) |
|---|
| 47 | * -- modal components are managed by <code>App</code>.<br/> |
|---|
| 48 | * @object-prop <code>focusableTagNames</code> <code>Object</code>A dictionary of names of html tags that are |
|---|
| 49 | * allowed focus under the 'active' state and under modality. Used for controlling focus.<br/> |
|---|
| 50 | * <br/><hr/> |
|---|
| 51 | * Class <code>Modal</code> The modal pop-up superclass.<br/> |
|---|
| 52 | * @inherits-from <code>Component</code>.<br/> |
|---|
| 53 | * <hr/> |
|---|
| 54 | * Class <code>Transient</code> A transient modal pop-up, such as a list or color picker.<br/> |
|---|
| 55 | * @inherits-from <code>Modal</code>.<br/> |
|---|
| 56 | * @listens-to <code>onkeypress</code> <br/> |
|---|
| 57 | * <hr/> |
|---|
| 58 | * Class <code>Modal-Message</code> A modal pop-up message, such as a confirm dialog.<br/> |
|---|
| 59 | * @inherits-from <code>Modal</code>.<br/> |
|---|
| 60 | * @listens-to <code>onkeypress</code> <br/> |
|---|
| 61 | * <hr/> |
|---|
| 62 | * Class <code>ModalMask</code> The event-blocking mask that preserves the modality of modals |
|---|
| 63 | * from clicking outside their boundaries.<br/> |
|---|
| 64 | * <br/><br/> |
|---|
| 65 | */ |
|---|
| 66 | |
|---|
| 67 | Component = new Class( Observable, Autolayout, { |
|---|
| 68 | useClosures: false, |
|---|
| 69 | |
|---|
| 70 | |
|---|
| 71 | init: function() { |
|---|
| 72 | arguments.callee.applySuper( this, arguments ); |
|---|
| 73 | this.initObject.apply( this, arguments ); |
|---|
| 74 | this.initSentinels(); |
|---|
| 75 | this.initEventListeners(); |
|---|
| 76 | this.initComponents(); |
|---|
| 77 | }, |
|---|
| 78 | |
|---|
| 79 | |
|---|
| 80 | destroy: function() { |
|---|
| 81 | arguments.callee.applySuper( this, arguments ); |
|---|
| 82 | this.destroyComponents(); |
|---|
| 83 | this.destroyEventListeners(); |
|---|
| 84 | this.destroyObject(); |
|---|
| 85 | }, |
|---|
| 86 | |
|---|
| 87 | |
|---|
| 88 | initObject: function( element ) { |
|---|
| 89 | this.element = DOM.getElement( element ); |
|---|
| 90 | if( !this.element ) |
|---|
| 91 | throw typeof element == "string" |
|---|
| 92 | ? "no element: " + element |
|---|
| 93 | : "no element"; |
|---|
| 94 | this.name = element; |
|---|
| 95 | this.parent = null; |
|---|
| 96 | this.components = []; |
|---|
| 97 | this.sentinels = null; |
|---|
| 98 | this.active = false; |
|---|
| 99 | }, |
|---|
| 100 | |
|---|
| 101 | |
|---|
| 102 | destroyObject: function( element ) { |
|---|
| 103 | this.components = null; |
|---|
| 104 | this.sentinels = null; |
|---|
| 105 | this.parent = null; |
|---|
| 106 | this.element = null; |
|---|
| 107 | this.name = null; |
|---|
| 108 | }, |
|---|
| 109 | |
|---|
| 110 | |
|---|
| 111 | /* events */ |
|---|
| 112 | |
|---|
| 113 | initEventListeners: function() { |
|---|
| 114 | this.addEventListener( this.element, "mouseover", "eventMouseOver" ); |
|---|
| 115 | this.addEventListener( this.element, "mouseout", "eventMouseOut" ); |
|---|
| 116 | this.addEventListener( this.element, "mousemove", "eventMouseMove" ); |
|---|
| 117 | this.addEventListener( this.element, "mousedown", "eventMouseDown" ); |
|---|
| 118 | this.addEventListener( this.element, "mouseup", "eventMouseUp" ); |
|---|
| 119 | this.addEventListener( this.element, "click", "eventClick" ); |
|---|
| 120 | this.addEventListener( this.element, "dblclick", "eventDoubleClick" ); |
|---|
| 121 | this.addEventListener( this.element, "contextmenu", "eventContextMenu" ); |
|---|
| 122 | this.addEventListener( this.element, "keydown", "eventKeyDown" ); |
|---|
| 123 | this.addEventListener( this.element, "keyup", "eventKeyUp" ); |
|---|
| 124 | this.addEventListener( this.element, "keypress", "eventKeyPress" ); |
|---|
| 125 | this.addEventListener( this.element, "focus", "eventFocus", true ); // FIXME: why are these capturing? |
|---|
| 126 | this.addEventListener( this.element, "blur", "eventBlur", true ); // FIXME: why are these capturing? |
|---|
| 127 | this.addEventListener( this.element, "focusin", "eventFocusIn" ); |
|---|
| 128 | this.addEventListener( this.element, "focusout", "eventFocusOut" ); |
|---|
| 129 | }, |
|---|
| 130 | |
|---|
| 131 | |
|---|
| 132 | destroyEventListeners: function() {}, |
|---|
| 133 | |
|---|
| 134 | |
|---|
| 135 | addEventListener: function( object, eventName, methodName, useCapture ) { |
|---|
| 136 | if ( !this[ methodName ] || this[ methodName ] === Function.stub ) /* XXX - purge instances of Function.stub */ |
|---|
| 137 | return; |
|---|
| 138 | DOM.addEventListener( object, eventName, |
|---|
| 139 | (this.useClosures |
|---|
| 140 | ? this.getEventListener( methodName ) |
|---|
| 141 | : this.getIndirectEventListener( methodName ) ), |
|---|
| 142 | useCapture ); |
|---|
| 143 | }, |
|---|
| 144 | |
|---|
| 145 | |
|---|
| 146 | removeEventListener: function( object, eventName, methodName, useCapture ) { |
|---|
| 147 | var listener = this.useClosures |
|---|
| 148 | ? this.getEventListener( methodName ) |
|---|
| 149 | : this.getIndirectEventListener( methodName ); |
|---|
| 150 | DOM.removeEventListener( object, eventName, listener, useCapture ); |
|---|
| 151 | }, |
|---|
| 152 | |
|---|
| 153 | |
|---|
| 154 | matchCommand: /(?:^|\s)command-(\S+)(?:\s|$)/, |
|---|
| 155 | |
|---|
| 156 | |
|---|
| 157 | getMouseEventCommand: function( event, rootElement ) { |
|---|
| 158 | var ancestors = DOM.getAncestors( event.target, true ); |
|---|
| 159 | var cmdattr = app.NAMESPACE + ":command"; |
|---|
| 160 | for( var i = 0; i < ancestors.length; i++ ) { |
|---|
| 161 | try { |
|---|
| 162 | /* check the new NAMESPACE:command attribute first */ |
|---|
| 163 | var result = ancestors[ i ].getAttribute( cmdattr ); |
|---|
| 164 | if ( !result ) { |
|---|
| 165 | result = this.matchCommand.exec( ancestors[ i ].className ); |
|---|
| 166 | if ( result[ 1 ] ) |
|---|
| 167 | result = result[ 1 ]; |
|---|
| 168 | } |
|---|
| 169 | if ( result ) { |
|---|
| 170 | event.commandElement = ancestors[ i ]; |
|---|
| 171 | /* foo-bar -> fooBar */ |
|---|
| 172 | event.command = result.cssToJS(); |
|---|
| 173 | return event.command; |
|---|
| 174 | } |
|---|
| 175 | if ( ancestors[ i ] == rootElement ) |
|---|
| 176 | break; |
|---|
| 177 | } catch( e ) {} |
|---|
| 178 | } |
|---|
| 179 | }, |
|---|
| 180 | |
|---|
| 181 | |
|---|
| 182 | /* event listeners */ |
|---|
| 183 | |
|---|
| 184 | eventMouseDown : function( event ) { |
|---|
| 185 | if( this.activatable ) |
|---|
| 186 | this.activate( event ); |
|---|
| 187 | }, |
|---|
| 188 | |
|---|
| 189 | |
|---|
| 190 | |
|---|
| 191 | eventFocus: function( event ) { |
|---|
| 192 | if( this.activatable ) |
|---|
| 193 | this.activate( event ); |
|---|
| 194 | }, |
|---|
| 195 | |
|---|
| 196 | |
|---|
| 197 | eventFocusIn: function( event ) { |
|---|
| 198 | if( this.activatable ) |
|---|
| 199 | this.activate( event ); |
|---|
| 200 | }, |
|---|
| 201 | |
|---|
| 202 | |
|---|
| 203 | /* layout */ |
|---|
| 204 | |
|---|
| 205 | reflow: function( event ) { |
|---|
| 206 | this.applyAutolayouts( this.element ); |
|---|
| 207 | this.reflowComponents( event ); |
|---|
| 208 | }, |
|---|
| 209 | |
|---|
| 210 | |
|---|
| 211 | reflowComponents: function( event ) { |
|---|
| 212 | this.components.forEach( function( component ) { component.reflow( event ); } ); |
|---|
| 213 | }, |
|---|
| 214 | |
|---|
| 215 | |
|---|
| 216 | /* components */ |
|---|
| 217 | |
|---|
| 218 | initComponents: function() {}, |
|---|
| 219 | |
|---|
| 220 | |
|---|
| 221 | destroyComponents: function() { |
|---|
| 222 | this.components.forEach( function( component ) { component.destroy(); } ); |
|---|
| 223 | this.components.length = 0; |
|---|
| 224 | }, |
|---|
| 225 | |
|---|
| 226 | |
|---|
| 227 | addComponent: function( component ) { |
|---|
| 228 | this.components.add( component ); |
|---|
| 229 | component.parent = this; |
|---|
| 230 | component.reflow(); |
|---|
| 231 | return component; |
|---|
| 232 | }, |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 235 | removeComponent: function( component ) { |
|---|
| 236 | this.components.remove( component ); |
|---|
| 237 | component.parent = null; |
|---|
| 238 | return component; |
|---|
| 239 | }, |
|---|
| 240 | |
|---|
| 241 | |
|---|
| 242 | /* active component functionality (including modality) */ |
|---|
| 243 | |
|---|
| 244 | activatable: false, |
|---|
| 245 | modal: false, |
|---|
| 246 | |
|---|
| 247 | |
|---|
| 248 | /** |
|---|
| 249 | * class <code>Component</code> |
|---|
| 250 | * Creates the invisible text input elements used as focus sinks for tabbability and modality |
|---|
| 251 | * (modal <code>div</code> "windows", etc). These text input elements are arranged in a |
|---|
| 252 | * specific way so that, for modal components, they may trap tabbing events to keep tab focus |
|---|
| 253 | * within the modal component. When it receives focus, the last sentinal 'punts' focus |
|---|
| 254 | * back to the first sentinel. |
|---|
| 255 | */ |
|---|
| 256 | initSentinels: function() { |
|---|
| 257 | if( !this.activatable || this.sentinels ) |
|---|
| 258 | return; |
|---|
| 259 | |
|---|
| 260 | this.sentinels = { |
|---|
| 261 | captureStart: DOM.createInvisibleInput() |
|---|
| 262 | }; |
|---|
| 263 | |
|---|
| 264 | this.element.insertBefore( this.sentinels.captureStart, this.element.firstChild ); |
|---|
| 265 | |
|---|
| 266 | if( !this.modal ) |
|---|
| 267 | return; |
|---|
| 268 | |
|---|
| 269 | extend( this.sentinels, { |
|---|
| 270 | puntStart: DOM.createInvisibleInput(), |
|---|
| 271 | captureEnd: DOM.createInvisibleInput(), |
|---|
| 272 | puntEnd: DOM.createInvisibleInput() |
|---|
| 273 | } ); |
|---|
| 274 | |
|---|
| 275 | this.element.insertBefore( this.sentinels.puntStart, this.element.firstChild ); |
|---|
| 276 | this.element.appendChild( this.sentinels.captureEnd ); |
|---|
| 277 | this.element.appendChild( this.sentinels.puntEnd ); |
|---|
| 278 | }, |
|---|
| 279 | |
|---|
| 280 | |
|---|
| 281 | /** |
|---|
| 282 | * class <code>Component</code> |
|---|
| 283 | * Create new sentinels. This is necessary in case their html must be destroyed. |
|---|
| 284 | */ |
|---|
| 285 | refreshSentinels: function() { |
|---|
| 286 | this.sentinels = null; |
|---|
| 287 | this.initSentinels(); |
|---|
| 288 | }, |
|---|
| 289 | |
|---|
| 290 | |
|---|
| 291 | /** |
|---|
| 292 | * class <code>Component</code> |
|---|
| 293 | * Activate this component (can be either modal or 'plain' active). |
|---|
| 294 | */ |
|---|
| 295 | activate: function( event ) { |
|---|
| 296 | if( !this.activatable ) |
|---|
| 297 | return; |
|---|
| 298 | if( !window.app.setActiveComponent( this ) ) |
|---|
| 299 | return; |
|---|
| 300 | |
|---|
| 301 | this.active = true; |
|---|
| 302 | this.captureFocus( event ); |
|---|
| 303 | DOM.addClassName( this.element, "active-component" ); |
|---|
| 304 | this.broadcastToObserversNB( "componentActivated", this ); |
|---|
| 305 | }, |
|---|
| 306 | |
|---|
| 307 | |
|---|
| 308 | /** |
|---|
| 309 | * class <code>Component</code> |
|---|
| 310 | * De-activate this component (can be either modal or 'plain' active). |
|---|
| 311 | */ |
|---|
| 312 | deactivate: function() { |
|---|
| 313 | this.active = false; |
|---|
| 314 | DOM.removeClassName( this.element, "active-component" ); |
|---|
| 315 | this.broadcastToObserversNB( "componentDeactivated", this ); |
|---|
| 316 | }, |
|---|
| 317 | |
|---|
| 318 | |
|---|
| 319 | focusableTagNames: { // Note: All elements with 'mouse event commands' are focusable (see 'captureFocus'). |
|---|
| 320 | taginput: defined, |
|---|
| 321 | tagtextarea: defined, |
|---|
| 322 | tagbutton: defined, |
|---|
| 323 | tagselect: defined, |
|---|
| 324 | tagdiv: defined, // FF 1.5+ puts divs in the tabbing order. Added 2006-05-03: See case 27751 and case 34362. |
|---|
| 325 | taga: defined, // 'anchor' tag. Added 2006-05-03: See Case 27751. |
|---|
| 326 | tagoption: defined, |
|---|
| 327 | tagp: defined // case 36542 |
|---|
| 328 | }, |
|---|
| 329 | |
|---|
| 330 | |
|---|
| 331 | /** |
|---|
| 332 | * class <code>Component</code> |
|---|
| 333 | * Capture and manage the focus on an activatable (optionally including modal) component. |
|---|
| 334 | * The behavior differs for the various types of activatable component. |
|---|
| 335 | * @param event <code>Event</code> The event that leads to focus (i.e., a tab or a click). |
|---|
| 336 | */ |
|---|
| 337 | captureFocus: function( event ) { |
|---|
| 338 | if ( this.active && !this.modal ) |
|---|
| 339 | return; |
|---|
| 340 | var tagName = ( defined( event ) && event.target.tagName ) |
|---|
| 341 | ? "tag" + event.target.tagName.toLowerCase() |
|---|
| 342 | : null; |
|---|
| 343 | var command; |
|---|
| 344 | if ( tagName && event ) |
|---|
| 345 | command = this.getMouseEventCommand( event ); // Avoid scrolling out from over command elements. |
|---|
| 346 | // Set the tabbing focus on the sentinel, unless the user wants it on another form or command element: |
|---|
| 347 | if( !tagName || ( this.focusableTagNames[ tagName ] !== defined ) && !command ) { |
|---|
| 348 | try { |
|---|
| 349 | this.sentinels.captureStart.focus(); |
|---|
| 350 | event.stop(); // Force the action to stop at the focus set above. |
|---|
| 351 | if( defined( this.sentinels.captureStart.focusIn ) ) |
|---|
| 352 | this.sentinels.captureStart.focusIn(); |
|---|
| 353 | } catch ( e ) {} |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | if( !defined( event ) || !this.modal ) |
|---|
| 357 | return; |
|---|
| 358 | /*- |
|---|
| 359 | * As of Firefox 1.5.0.6 at least, bug where key events could not be heard from divs has been fixed. |
|---|
| 360 | * Workaround code for this put in on 2006-05-03 and removed 2006-08-24. See case 27751 and case 34362. |
|---|
| 361 | */ |
|---|
| 362 | try { |
|---|
| 363 | if( event.target === this.sentinels.puntEnd ) |
|---|
| 364 | this.sentinels.captureStart.focus(); |
|---|
| 365 | else if( event.target === this.sentinels.puntStart ) |
|---|
| 366 | this.sentinels.captureEnd.focus(); |
|---|
| 367 | } catch( e ) {} |
|---|
| 368 | }, |
|---|
| 369 | |
|---|
| 370 | |
|---|
| 371 | /* misc */ |
|---|
| 372 | |
|---|
| 373 | hide: function() { |
|---|
| 374 | DOM.removeClassName( this.element, "visible" ); |
|---|
| 375 | DOM.addClassName( this.element, "hidden" ); |
|---|
| 376 | }, |
|---|
| 377 | |
|---|
| 378 | |
|---|
| 379 | show: function() { |
|---|
| 380 | DOM.removeClassName( this.element, "hidden" ); |
|---|
| 381 | DOM.addClassName( this.element, "visible" ); |
|---|
| 382 | this.reflow(); |
|---|
| 383 | } |
|---|
| 384 | } ); |
|---|
| 385 | |
|---|
| 386 | |
|---|
| 387 | /* modal subclass */ |
|---|
| 388 | |
|---|
| 389 | Modal = new Class( Component, { |
|---|
| 390 | activatable: true, |
|---|
| 391 | modal: true, |
|---|
| 392 | isOpen: false, |
|---|
| 393 | |
|---|
| 394 | /* execution */ |
|---|
| 395 | |
|---|
| 396 | open: function( data, callback ) { |
|---|
| 397 | if ( !this.transitory ) |
|---|
| 398 | window.scrollTo( 0, 0 ); |
|---|
| 399 | this.data = defined( data ) ? data : {}; |
|---|
| 400 | this.callback = callback; |
|---|
| 401 | this.active = false; |
|---|
| 402 | this.isOpen = true; |
|---|
| 403 | window.app.addModal( this ); |
|---|
| 404 | }, |
|---|
| 405 | |
|---|
| 406 | |
|---|
| 407 | close: function( data ) { |
|---|
| 408 | window.app.removeModal( this ); |
|---|
| 409 | this.isOpen = false; |
|---|
| 410 | if( this.callback ) |
|---|
| 411 | this.callback( data, this ); |
|---|
| 412 | this.callback = null; |
|---|
| 413 | this.data = null; |
|---|
| 414 | }, |
|---|
| 415 | |
|---|
| 416 | |
|---|
| 417 | eventClick: function( event ) { |
|---|
| 418 | if ( event.shiftKey ) |
|---|
| 419 | event.stop(); |
|---|
| 420 | }, |
|---|
| 421 | |
|---|
| 422 | |
|---|
| 423 | eventKeyPress: function( event ) { |
|---|
| 424 | switch( event.keyCode ) { |
|---|
| 425 | case 27: |
|---|
| 426 | this.close( false ); |
|---|
| 427 | } |
|---|
| 428 | } |
|---|
| 429 | } ); |
|---|
| 430 | |
|---|
| 431 | |
|---|
| 432 | /* transient subclass */ |
|---|
| 433 | |
|---|
| 434 | Transient = new Class( Modal, { |
|---|
| 435 | transitory: true, |
|---|
| 436 | |
|---|
| 437 | /* events */ |
|---|
| 438 | |
|---|
| 439 | /** |
|---|
| 440 | * Class: <code>Transient</code><br> |
|---|
| 441 | * This method allows a <ocde>Transient</code> to disappear on keypress of 'esc'.. |
|---|
| 442 | * @param event <code>Event</code> A prepared (processed by the custom js framework) <code>Event</code> object. |
|---|
| 443 | */ |
|---|
| 444 | eventKeyPress: function( event ) { |
|---|
| 445 | switch( event.keyCode ) { |
|---|
| 446 | case 27: |
|---|
| 447 | this.close( false ); |
|---|
| 448 | } |
|---|
| 449 | }, |
|---|
| 450 | |
|---|
| 451 | |
|---|
| 452 | /** |
|---|
| 453 | * Returns the command from the event, via <code>getMouseEventCommand</code>. |
|---|
| 454 | * @param event <code>Event</code> A prepared event object. |
|---|
| 455 | */ |
|---|
| 456 | eventClick: function( event ) { |
|---|
| 457 | this.close( this.getMouseEventCommand( event ) ); |
|---|
| 458 | return event.stop(); |
|---|
| 459 | }, |
|---|
| 460 | |
|---|
| 461 | |
|---|
| 462 | eventContextMenu: function( event ) { |
|---|
| 463 | return event.stop(); |
|---|
| 464 | }, |
|---|
| 465 | |
|---|
| 466 | |
|---|
| 467 | open: function( data, callback, targetElement ) { |
|---|
| 468 | this.targetElement = targetElement; |
|---|
| 469 | return arguments.callee.applySuper( this, arguments ); |
|---|
| 470 | } |
|---|
| 471 | } ); |
|---|
| 472 | |
|---|
| 473 | |
|---|
| 474 | Component.Delegator = { |
|---|
| 475 | |
|---|
| 476 | DEFAULT_NAMESPACE: "core", |
|---|
| 477 | |
|---|
| 478 | setupDelegates: function( object ) { |
|---|
| 479 | /* this needs more testing before enabling |
|---|
| 480 | if ( object && !object.delegateParent ) |
|---|
| 481 | object.delegateParent = this; |
|---|
| 482 | */ |
|---|
| 483 | |
|---|
| 484 | if ( !this.delegateListeners ) |
|---|
| 485 | this.delegateListeners = {}; |
|---|
| 486 | |
|---|
| 487 | if ( !this.delegates ) |
|---|
| 488 | this.delegates = {}; |
|---|
| 489 | |
|---|
| 490 | if ( !defined( this.NAMESPACE ) ) |
|---|
| 491 | this.NAMESPACE = ( window.app && app.NAMESPACE ) |
|---|
| 492 | ? app.NAMESPACE : this.DEFAULT_NAMESPACE; |
|---|
| 493 | }, |
|---|
| 494 | |
|---|
| 495 | |
|---|
| 496 | addEventListener: function( object, eventName, methodName, useCapture ) { |
|---|
| 497 | DOM.addEventListener( object, eventName, |
|---|
| 498 | (this.useClosures |
|---|
| 499 | ? this.getEventListener( methodName ) |
|---|
| 500 | : this.getIndirectEventListener( methodName ) ), |
|---|
| 501 | useCapture ); |
|---|
| 502 | }, |
|---|
| 503 | |
|---|
| 504 | |
|---|
| 505 | /* delegate functions */ |
|---|
| 506 | setDelegate: function( name, object ) { |
|---|
| 507 | this.setupDelegates( object ); |
|---|
| 508 | this.delegates[ name ] = object; |
|---|
| 509 | return object; |
|---|
| 510 | }, |
|---|
| 511 | |
|---|
| 512 | |
|---|
| 513 | setDelegateListener: function( eventName, delegateName ) { |
|---|
| 514 | this.setupDelegates(); |
|---|
| 515 | this.delegateListeners[ eventName ] = delegateName; |
|---|
| 516 | }, |
|---|
| 517 | |
|---|
| 518 | |
|---|
| 519 | delegateEvent: function( event, eventName ) { |
|---|
| 520 | var delegate = DOM.getMouseEventAttribute( event, this.NAMESPACE + ":delegate" ); |
|---|
| 521 | |
|---|
| 522 | if ( !delegate ) { |
|---|
| 523 | if ( this.delegateListeners && this.delegateListeners.hasOwnProperty( eventName ) ) |
|---|
| 524 | delegate = this.delegateListeners[ eventName ]; |
|---|
| 525 | else |
|---|
| 526 | return undefined; |
|---|
| 527 | } else |
|---|
| 528 | delegate = delegate.cssToJS(); |
|---|
| 529 | |
|---|
| 530 | if ( this.delegates && this.delegates.hasOwnProperty( delegate ) && this.delegates[ delegate ][ eventName ] ) |
|---|
| 531 | return this.delegates[ delegate ][ eventName ]( event, this ); |
|---|
| 532 | }, |
|---|
| 533 | |
|---|
| 534 | |
|---|
| 535 | getIndirectEventListener: function( methodName ) { |
|---|
| 536 | if( !this.indirectEventListeners ) |
|---|
| 537 | this.indirectEventListeners = {}; |
|---|
| 538 | var method = this[ methodName ]; |
|---|
| 539 | var indirectIndex = this.getIndirectIndex(); |
|---|
| 540 | if( !this.indirectEventListeners[ methodName ] ) { |
|---|
| 541 | return this.indirectEventListeners[ methodName ] = new Function( "event", |
|---|
| 542 | "if ( window.indirectObjects === undefined ) return;" + |
|---|
| 543 | "try { event = Event.prep( event ); } catch( e ) {}" + |
|---|
| 544 | "var o = window.indirectObjects[" + indirectIndex + "];" + |
|---|
| 545 | "if ( !o ) return;" + |
|---|
| 546 | "var r = o.delegateEvent( event, '" + methodName + |
|---|
| 547 | "' ); if ( r ) return r; if ( o[ '" + methodName + |
|---|
| 548 | "' ] ) return o['" + methodName + "'].call( o, event );" ); |
|---|
| 549 | } |
|---|
| 550 | |
|---|
| 551 | return this.indirectEventListeners[ methodName ]; |
|---|
| 552 | } |
|---|
| 553 | }; |
|---|