1 /* 2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 /** 7 * @fileOverview The floating dialog plugin. 8 */ 9 10 /** 11 * No resize for this dialog. 12 * @constant 13 */ 14 CKEDITOR.DIALOG_RESIZE_NONE = 0; 15 16 /** 17 * Only allow horizontal resizing for this dialog, disable vertical resizing. 18 * @constant 19 */ 20 CKEDITOR.DIALOG_RESIZE_WIDTH = 1; 21 22 /** 23 * Only allow vertical resizing for this dialog, disable horizontal resizing. 24 * @constant 25 */ 26 CKEDITOR.DIALOG_RESIZE_HEIGHT = 2; 27 28 /* 29 * Allow the dialog to be resized in both directions. 30 * @constant 31 */ 32 CKEDITOR.DIALOG_RESIZE_BOTH = 3; 33 34 (function() 35 { 36 var cssLength = CKEDITOR.tools.cssLength; 37 function isTabVisible( tabId ) 38 { 39 return !!this._.tabs[ tabId ][ 0 ].$.offsetHeight; 40 } 41 42 function getPreviousVisibleTab() 43 { 44 var tabId = this._.currentTabId, 45 length = this._.tabIdList.length, 46 tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ) + length; 47 48 for ( var i = tabIndex - 1 ; i > tabIndex - length ; i-- ) 49 { 50 if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) 51 return this._.tabIdList[ i % length ]; 52 } 53 54 return null; 55 } 56 57 function getNextVisibleTab() 58 { 59 var tabId = this._.currentTabId, 60 length = this._.tabIdList.length, 61 tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ); 62 63 for ( var i = tabIndex + 1 ; i < tabIndex + length ; i++ ) 64 { 65 if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) 66 return this._.tabIdList[ i % length ]; 67 } 68 69 return null; 70 } 71 72 73 function clearOrRecoverTextInputValue( container, isRecover ) 74 { 75 var inputs = container.$.getElementsByTagName( 'input' ); 76 for ( var i = 0, length = inputs.length; i < length ; i++ ) 77 { 78 var item = new CKEDITOR.dom.element( inputs[ i ] ); 79 80 if ( item.getAttribute( 'type' ).toLowerCase() == 'text' ) 81 { 82 if ( isRecover ) 83 { 84 item.setAttribute( 'value', item.getCustomData( 'fake_value' ) || '' ); 85 item.removeCustomData( 'fake_value' ); 86 } 87 else 88 { 89 item.setCustomData( 'fake_value', item.getAttribute( 'value' ) ); 90 item.setAttribute( 'value', '' ); 91 } 92 } 93 } 94 } 95 96 // Handle dialog element validation state UI changes. 97 function handleFieldValidated( isValid, msg ) 98 { 99 var input = this.getInputElement(); 100 if ( input ) 101 { 102 isValid ? input.removeAttribute( 'aria-invalid' ) 103 : input.setAttribute( 'aria-invalid', true ); 104 } 105 106 if ( !isValid ) 107 { 108 if ( this.select ) 109 this.select(); 110 else 111 this.focus(); 112 } 113 114 msg && alert( msg ); 115 116 this.fire( 'validated', { valid : isValid, msg : msg } ); 117 } 118 119 function resetField() 120 { 121 var input = this.getInputElement(); 122 input && input.removeAttribute( 'aria-invalid' ); 123 } 124 125 126 /** 127 * This is the base class for runtime dialog objects. An instance of this 128 * class represents a single named dialog for a single editor instance. 129 * @param {Object} editor The editor which created the dialog. 130 * @param {String} dialogName The dialog's registered name. 131 * @constructor 132 * @example 133 * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' ); 134 */ 135 CKEDITOR.dialog = function( editor, dialogName ) 136 { 137 // Load the dialog definition. 138 var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], 139 defaultDefinition = CKEDITOR.tools.clone( defaultDialogDefinition ), 140 buttonsOrder = editor.config.dialog_buttonsOrder || 'OS', 141 dir = editor.lang.dir, 142 tabsToRemove = {}, 143 i, 144 processed; 145 146 if ( ( buttonsOrder == 'OS' && CKEDITOR.env.mac ) || // The buttons in MacOS Apps are in reverse order (#4750) 147 ( buttonsOrder == 'rtl' && dir == 'ltr' ) || 148 ( buttonsOrder == 'ltr' && dir == 'rtl' ) ) 149 defaultDefinition.buttons.reverse(); 150 151 152 // Completes the definition with the default values. 153 definition = CKEDITOR.tools.extend( definition( editor ), defaultDefinition ); 154 155 // Clone a functionally independent copy for this dialog. 156 definition = CKEDITOR.tools.clone( definition ); 157 158 // Create a complex definition object, extending it with the API 159 // functions. 160 definition = new definitionObject( this, definition ); 161 162 var doc = CKEDITOR.document; 163 164 var themeBuilt = editor.theme.buildDialog( editor ); 165 166 // Initialize some basic parameters. 167 this._ = 168 { 169 editor : editor, 170 element : themeBuilt.element, 171 name : dialogName, 172 contentSize : { width : 0, height : 0 }, 173 size : { width : 0, height : 0 }, 174 contents : {}, 175 buttons : {}, 176 accessKeyMap : {}, 177 178 // Initialize the tab and page map. 179 tabs : {}, 180 tabIdList : [], 181 currentTabId : null, 182 currentTabIndex : null, 183 pageCount : 0, 184 lastTab : null, 185 tabBarMode : false, 186 187 // Initialize the tab order array for input widgets. 188 focusList : [], 189 currentFocusIndex : 0, 190 hasFocus : false 191 }; 192 193 this.parts = themeBuilt.parts; 194 195 CKEDITOR.tools.setTimeout( function() 196 { 197 editor.fire( 'ariaWidget', this.parts.contents ); 198 }, 199 0, this ); 200 201 // Set the startup styles for the dialog, avoiding it enlarging the 202 // page size on the dialog creation. 203 var startStyles = { 204 position : CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed', 205 top : 0, 206 visibility : 'hidden' 207 }; 208 209 startStyles[ dir == 'rtl' ? 'right' : 'left' ] = 0; 210 this.parts.dialog.setStyles( startStyles ); 211 212 213 // Call the CKEDITOR.event constructor to initialize this instance. 214 CKEDITOR.event.call( this ); 215 216 // Fire the "dialogDefinition" event, making it possible to customize 217 // the dialog definition. 218 this.definition = definition = CKEDITOR.fire( 'dialogDefinition', 219 { 220 name : dialogName, 221 definition : definition 222 } 223 , editor ).definition; 224 225 // Cache tabs that should be removed. 226 if ( !( 'removeDialogTabs' in editor._ ) && editor.config.removeDialogTabs ) 227 { 228 var removeContents = editor.config.removeDialogTabs.split( ';' ); 229 230 for ( i = 0; i < removeContents.length; i++ ) 231 { 232 var parts = removeContents[ i ].split( ':' ); 233 if ( parts.length == 2 ) 234 { 235 var removeDialogName = parts[ 0 ]; 236 if ( !tabsToRemove[ removeDialogName ] ) 237 tabsToRemove[ removeDialogName ] = []; 238 tabsToRemove[ removeDialogName ].push( parts[ 1 ] ); 239 } 240 } 241 editor._.removeDialogTabs = tabsToRemove; 242 } 243 244 // Remove tabs of this dialog. 245 if ( editor._.removeDialogTabs && ( tabsToRemove = editor._.removeDialogTabs[ dialogName ] ) ) 246 { 247 for ( i = 0; i < tabsToRemove.length; i++ ) 248 definition.removeContents( tabsToRemove[ i ] ); 249 } 250 251 // Initialize load, show, hide, ok and cancel events. 252 if ( definition.onLoad ) 253 this.on( 'load', definition.onLoad ); 254 255 if ( definition.onShow ) 256 this.on( 'show', definition.onShow ); 257 258 if ( definition.onHide ) 259 this.on( 'hide', definition.onHide ); 260 261 if ( definition.onOk ) 262 { 263 this.on( 'ok', function( evt ) 264 { 265 // Dialog confirm might probably introduce content changes (#5415). 266 editor.fire( 'saveSnapshot' ); 267 setTimeout( function () { editor.fire( 'saveSnapshot' ); }, 0 ); 268 if ( definition.onOk.call( this, evt ) === false ) 269 evt.data.hide = false; 270 }); 271 } 272 273 if ( definition.onCancel ) 274 { 275 this.on( 'cancel', function( evt ) 276 { 277 if ( definition.onCancel.call( this, evt ) === false ) 278 evt.data.hide = false; 279 }); 280 } 281 282 var me = this; 283 284 // Iterates over all items inside all content in the dialog, calling a 285 // function for each of them. 286 var iterContents = function( func ) 287 { 288 var contents = me._.contents, 289 stop = false; 290 291 for ( var i in contents ) 292 { 293 for ( var j in contents[i] ) 294 { 295 stop = func.call( this, contents[i][j] ); 296 if ( stop ) 297 return; 298 } 299 } 300 }; 301 302 this.on( 'ok', function( evt ) 303 { 304 iterContents( function( item ) 305 { 306 if ( item.validate ) 307 { 308 var retval = item.validate( this ), 309 invalid = typeof ( retval ) == 'string' || retval === false; 310 311 if ( invalid ) 312 { 313 evt.data.hide = false; 314 evt.stop(); 315 } 316 317 handleFieldValidated.call( item, !invalid, typeof retval == 'string' ? retval : undefined ); 318 return invalid; 319 } 320 }); 321 }, this, null, 0 ); 322 323 this.on( 'cancel', function( evt ) 324 { 325 iterContents( function( item ) 326 { 327 if ( item.isChanged() ) 328 { 329 if ( !confirm( editor.lang.common.confirmCancel ) ) 330 evt.data.hide = false; 331 return true; 332 } 333 }); 334 }, this, null, 0 ); 335 336 this.parts.close.on( 'click', function( evt ) 337 { 338 if ( this.fire( 'cancel', { hide : true } ).hide !== false ) 339 this.hide(); 340 evt.data.preventDefault(); 341 }, this ); 342 343 // Sort focus list according to tab order definitions. 344 function setupFocus() 345 { 346 var focusList = me._.focusList; 347 focusList.sort( function( a, b ) 348 { 349 // Mimics browser tab order logics; 350 if ( a.tabIndex != b.tabIndex ) 351 return b.tabIndex - a.tabIndex; 352 // Sort is not stable in some browsers, 353 // fall-back the comparator to 'focusIndex'; 354 else 355 return a.focusIndex - b.focusIndex; 356 }); 357 358 var size = focusList.length; 359 for ( var i = 0; i < size; i++ ) 360 focusList[ i ].focusIndex = i; 361 } 362 363 function changeFocus( offset ) 364 { 365 var focusList = me._.focusList; 366 offset = offset || 0; 367 368 if ( focusList.length < 1 ) 369 return; 370 371 var current = me._.currentFocusIndex; 372 373 // Trigger the 'blur' event of any input element before anything, 374 // since certain UI updates may depend on it. 375 try 376 { 377 focusList[ current ].getInputElement().$.blur(); 378 } 379 catch( e ){} 380 381 var startIndex = ( current + offset + focusList.length ) % focusList.length, 382 currentIndex = startIndex; 383 while ( offset && !focusList[ currentIndex ].isFocusable() ) 384 { 385 currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length; 386 if ( currentIndex == startIndex ) 387 break; 388 } 389 390 focusList[ currentIndex ].focus(); 391 392 // Select whole field content. 393 if ( focusList[ currentIndex ].type == 'text' ) 394 focusList[ currentIndex ].select(); 395 } 396 397 this.changeFocus = changeFocus; 398 399 400 function focusKeydownHandler( evt ) 401 { 402 // If I'm not the top dialog, ignore. 403 if ( me != CKEDITOR.dialog._.currentTop ) 404 return; 405 406 var keystroke = evt.data.getKeystroke(), 407 rtl = editor.lang.dir == 'rtl'; 408 409 processed = 0; 410 if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 ) 411 { 412 var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 ); 413 414 // Handling Tab and Shift-Tab. 415 if ( me._.tabBarMode ) 416 { 417 // Change tabs. 418 var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ); 419 me.selectPage( nextId ); 420 me._.tabs[ nextId ][ 0 ].focus(); 421 } 422 else 423 { 424 // Change the focus of inputs. 425 changeFocus( shiftPressed ? -1 : 1 ); 426 } 427 428 processed = 1; 429 } 430 else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 ) 431 { 432 // Alt-F10 puts focus into the current tab item in the tab bar. 433 me._.tabBarMode = true; 434 me._.tabs[ me._.currentTabId ][ 0 ].focus(); 435 processed = 1; 436 } 437 else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode ) 438 { 439 // Arrow keys - used for changing tabs. 440 nextId = ( keystroke == ( rtl ? 39 : 37 ) ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) ); 441 me.selectPage( nextId ); 442 me._.tabs[ nextId ][ 0 ].focus(); 443 processed = 1; 444 } 445 else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode ) 446 { 447 this.selectPage( this._.currentTabId ); 448 this._.tabBarMode = false; 449 this._.currentFocusIndex = -1; 450 changeFocus( 1 ); 451 processed = 1; 452 } 453 454 if ( processed ) 455 { 456 evt.stop(); 457 evt.data.preventDefault(); 458 } 459 } 460 461 function focusKeyPressHandler( evt ) 462 { 463 processed && evt.data.preventDefault(); 464 } 465 466 var dialogElement = this._.element; 467 // Add the dialog keyboard handlers. 468 this.on( 'show', function() 469 { 470 dialogElement.on( 'keydown', focusKeydownHandler, this, null, 0 ); 471 // Some browsers instead, don't cancel key events in the keydown, but in the 472 // keypress. So we must do a longer trip in those cases. (#4531) 473 if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) 474 dialogElement.on( 'keypress', focusKeyPressHandler, this ); 475 476 } ); 477 this.on( 'hide', function() 478 { 479 dialogElement.removeListener( 'keydown', focusKeydownHandler ); 480 if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) 481 dialogElement.removeListener( 'keypress', focusKeyPressHandler ); 482 483 // Reset fields state when closing dialog. 484 iterContents( function( item ) { resetField.apply( item ); } ); 485 } ); 486 this.on( 'iframeAdded', function( evt ) 487 { 488 var doc = new CKEDITOR.dom.document( evt.data.iframe.$.contentWindow.document ); 489 doc.on( 'keydown', focusKeydownHandler, this, null, 0 ); 490 } ); 491 492 // Auto-focus logic in dialog. 493 this.on( 'show', function() 494 { 495 // Setup tabIndex on showing the dialog instead of on loading 496 // to allow dynamic tab order happen in dialog definition. 497 setupFocus(); 498 499 if ( editor.config.dialog_startupFocusTab 500 && me._.pageCount > 1 ) 501 { 502 me._.tabBarMode = true; 503 me._.tabs[ me._.currentTabId ][ 0 ].focus(); 504 } 505 else if ( !this._.hasFocus ) 506 { 507 this._.currentFocusIndex = -1; 508 509 // Decide where to put the initial focus. 510 if ( definition.onFocus ) 511 { 512 var initialFocus = definition.onFocus.call( this ); 513 // Focus the field that the user specified. 514 initialFocus && initialFocus.focus(); 515 } 516 // Focus the first field in layout order. 517 else 518 changeFocus( 1 ); 519 520 /* 521 * IE BUG: If the initial focus went into a non-text element (e.g. button), 522 * then IE would still leave the caret inside the editing area. 523 */ 524 if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) 525 { 526 var $selection = editor.document.$.selection, 527 $range = $selection.createRange(); 528 529 if ( $range ) 530 { 531 if ( $range.parentElement && $range.parentElement().ownerDocument == editor.document.$ 532 || $range.item && $range.item( 0 ).ownerDocument == editor.document.$ ) 533 { 534 var $myRange = document.body.createTextRange(); 535 $myRange.moveToElementText( this.getElement().getFirst().$ ); 536 $myRange.collapse( true ); 537 $myRange.select(); 538 } 539 } 540 } 541 } 542 }, this, null, 0xffffffff ); 543 544 // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661). 545 // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken. 546 if ( CKEDITOR.env.ie6Compat ) 547 { 548 this.on( 'load', function( evt ) 549 { 550 var outer = this.getElement(), 551 inner = outer.getFirst(); 552 inner.remove(); 553 inner.appendTo( outer ); 554 }, this ); 555 } 556 557 initDragAndDrop( this ); 558 initResizeHandles( this ); 559 560 // Insert the title. 561 ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title ); 562 563 // Insert the tabs and contents. 564 for ( i = 0 ; i < definition.contents.length ; i++ ) 565 { 566 var page = definition.contents[i]; 567 page && this.addPage( page ); 568 } 569 570 this.parts[ 'tabs' ].on( 'click', function( evt ) 571 { 572 var target = evt.data.getTarget(); 573 // If we aren't inside a tab, bail out. 574 if ( target.hasClass( 'cke_dialog_tab' ) ) 575 { 576 // Get the ID of the tab, without the 'cke_' prefix and the unique number suffix. 577 var id = target.$.id; 578 this.selectPage( id.substring( 4, id.lastIndexOf( '_' ) ) ); 579 580 if ( this._.tabBarMode ) 581 { 582 this._.tabBarMode = false; 583 this._.currentFocusIndex = -1; 584 changeFocus( 1 ); 585 } 586 evt.data.preventDefault(); 587 } 588 }, this ); 589 590 // Insert buttons. 591 var buttonsHtml = [], 592 buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this, 593 { 594 type : 'hbox', 595 className : 'cke_dialog_footer_buttons', 596 widths : [], 597 children : definition.buttons 598 }, buttonsHtml ).getChild(); 599 this.parts.footer.setHtml( buttonsHtml.join( '' ) ); 600 601 for ( i = 0 ; i < buttons.length ; i++ ) 602 this._.buttons[ buttons[i].id ] = buttons[i]; 603 }; 604 605 // Focusable interface. Use it via dialog.addFocusable. 606 function Focusable( dialog, element, index ) 607 { 608 this.element = element; 609 this.focusIndex = index; 610 // TODO: support tabIndex for focusables. 611 this.tabIndex = 0; 612 this.isFocusable = function() 613 { 614 return !element.getAttribute( 'disabled' ) && element.isVisible(); 615 }; 616 this.focus = function() 617 { 618 dialog._.currentFocusIndex = this.focusIndex; 619 this.element.focus(); 620 }; 621 // Bind events 622 element.on( 'keydown', function( e ) 623 { 624 if ( e.data.getKeystroke() in { 32:1, 13:1 } ) 625 this.fire( 'click' ); 626 } ); 627 element.on( 'focus', function() 628 { 629 this.fire( 'mouseover' ); 630 } ); 631 element.on( 'blur', function() 632 { 633 this.fire( 'mouseout' ); 634 } ); 635 } 636 637 CKEDITOR.dialog.prototype = 638 { 639 destroy : function() 640 { 641 this.hide(); 642 this._.element.remove(); 643 }, 644 645 /** 646 * Resizes the dialog. 647 * @param {Number} width The width of the dialog in pixels. 648 * @param {Number} height The height of the dialog in pixels. 649 * @function 650 * @example 651 * dialogObj.resize( 800, 640 ); 652 */ 653 resize : (function() 654 { 655 return function( width, height ) 656 { 657 if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height ) 658 return; 659 660 CKEDITOR.dialog.fire( 'resize', 661 { 662 dialog : this, 663 skin : this._.editor.skinName, 664 width : width, 665 height : height 666 }, this._.editor ); 667 668 this.fire( 'resize', 669 { 670 skin : this._.editor.skinName, 671 width : width, 672 height : height 673 }, this._.editor ); 674 675 // Update dialog position when dimension get changed in RTL. 676 if ( this._.editor.lang.dir == 'rtl' && this._.position ) 677 this._.position.x = CKEDITOR.document.getWindow().getViewPaneSize().width - 678 this._.contentSize.width - parseInt( this._.element.getFirst().getStyle( 'right' ), 10 ); 679 680 this._.contentSize = { width : width, height : height }; 681 }; 682 })(), 683 684 /** 685 * Gets the current size of the dialog in pixels. 686 * @returns {Object} An object with "width" and "height" properties. 687 * @example 688 * var width = dialogObj.getSize().width; 689 */ 690 getSize : function() 691 { 692 var element = this._.element.getFirst(); 693 return { width : element.$.offsetWidth || 0, height : element.$.offsetHeight || 0}; 694 }, 695 696 /** 697 * Moves the dialog to an (x, y) coordinate relative to the window. 698 * @function 699 * @param {Number} x The target x-coordinate. 700 * @param {Number} y The target y-coordinate. 701 * @param {Boolean} save Flag indicate whether the dialog position should be remembered on next open up. 702 * @example 703 * dialogObj.move( 10, 40 ); 704 */ 705 move : (function() 706 { 707 var isFixed; 708 return function( x, y, save ) 709 { 710 // The dialog may be fixed positioned or absolute positioned. Ask the 711 // browser what is the current situation first. 712 var element = this._.element.getFirst(), 713 rtl = this._.editor.lang.dir == 'rtl'; 714 715 if ( isFixed === undefined ) 716 isFixed = element.getComputedStyle( 'position' ) == 'fixed'; 717 718 if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y ) 719 return; 720 721 // Save the current position. 722 this._.position = { x : x, y : y }; 723 724 // If not fixed positioned, add scroll position to the coordinates. 725 if ( !isFixed ) 726 { 727 var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition(); 728 x += scrollPosition.x; 729 y += scrollPosition.y; 730 } 731 732 // Translate coordinate for RTL. 733 if ( rtl ) 734 { 735 var dialogSize = this.getSize(), 736 viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(); 737 x = viewPaneSize.width - dialogSize.width - x; 738 } 739 740 var styles = { 'top' : ( y > 0 ? y : 0 ) + 'px' }; 741 styles[ rtl ? 'right' : 'left' ] = ( x > 0 ? x : 0 ) + 'px'; 742 743 element.setStyles( styles ); 744 745 save && ( this._.moved = 1 ); 746 }; 747 })(), 748 749 /** 750 * Gets the dialog's position in the window. 751 * @returns {Object} An object with "x" and "y" properties. 752 * @example 753 * var dialogX = dialogObj.getPosition().x; 754 */ 755 getPosition : function(){ return CKEDITOR.tools.extend( {}, this._.position ); }, 756 757 /** 758 * Shows the dialog box. 759 * @example 760 * dialogObj.show(); 761 */ 762 show : function() 763 { 764 // Insert the dialog's element to the root document. 765 var element = this._.element; 766 var definition = this.definition; 767 if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) 768 element.appendTo( CKEDITOR.document.getBody() ); 769 else 770 element.setStyle( 'display', 'block' ); 771 772 // FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8. 773 if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) 774 { 775 var dialogElement = this.parts.dialog; 776 dialogElement.setStyle( 'position', 'absolute' ); 777 setTimeout( function() 778 { 779 dialogElement.setStyle( 'position', 'fixed' ); 780 }, 0 ); 781 } 782 783 784 // First, set the dialog to an appropriate size. 785 this.resize( this._.contentSize && this._.contentSize.width || definition.width || definition.minWidth, 786 this._.contentSize && this._.contentSize.height || definition.height || definition.minHeight ); 787 788 // Reset all inputs back to their default value. 789 this.reset(); 790 791 // Select the first tab by default. 792 this.selectPage( this.definition.contents[0].id ); 793 794 // Set z-index. 795 if ( CKEDITOR.dialog._.currentZIndex === null ) 796 CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex; 797 this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 ); 798 799 // Maintain the dialog ordering and dialog cover. 800 if ( CKEDITOR.dialog._.currentTop === null ) 801 { 802 CKEDITOR.dialog._.currentTop = this; 803 this._.parentDialog = null; 804 showCover( this._.editor ); 805 806 } 807 else 808 { 809 this._.parentDialog = CKEDITOR.dialog._.currentTop; 810 var parentElement = this._.parentDialog.getElement().getFirst(); 811 parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 ); 812 CKEDITOR.dialog._.currentTop = this; 813 } 814 815 element.on( 'keydown', accessKeyDownHandler ); 816 element.on( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); 817 818 // Prevent some keys from bubbling up. (#4269) 819 for ( var event in { keyup :1, keydown :1, keypress :1 } ) 820 element.on( event, preventKeyBubbling ); 821 822 // Register the Esc hotkeys. 823 registerAccessKey( this, this, '\x1b', null, function() 824 { 825 var button = this.getButton( 'cancel' ); 826 // If there's a Cancel button, click it, else just fire the cancel event and hide the dialog 827 if ( button ) 828 button.click(); 829 else 830 { 831 if ( this.fire( 'cancel', { hide : true } ).hide !== false ) 832 this.hide(); 833 } 834 } ); 835 836 // Reset the hasFocus state. 837 this._.hasFocus = false; 838 839 CKEDITOR.tools.setTimeout( function() 840 { 841 this.layout(); 842 this.parts.dialog.setStyle( 'visibility', '' ); 843 844 // Execute onLoad for the first show. 845 this.fireOnce( 'load', {} ); 846 CKEDITOR.ui.fire( 'ready', this ); 847 848 this.fire( 'show', {} ); 849 this._.editor.fire( 'dialogShow', this ); 850 851 // Save the initial values of the dialog. 852 this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); } ); 853 854 }, 855 100, this ); 856 }, 857 858 /** 859 * Rearrange the dialog to its previous position or the middle of the window. 860 * @since 3.5 861 */ 862 layout : function() 863 { 864 var viewSize = CKEDITOR.document.getWindow().getViewPaneSize(), 865 dialogSize = this.getSize(); 866 867 this.move( this._.moved ? this._.position.x : ( viewSize.width - dialogSize.width ) / 2, 868 this._.moved ? this._.position.y : ( viewSize.height - dialogSize.height ) / 2 ); 869 }, 870 871 /** 872 * Executes a function for each UI element. 873 * @param {Function} fn Function to execute for each UI element. 874 * @returns {CKEDITOR.dialog} The current dialog object. 875 */ 876 foreach : function( fn ) 877 { 878 for ( var i in this._.contents ) 879 { 880 for ( var j in this._.contents[i] ) 881 fn.call( this, this._.contents[i][j] ); 882 } 883 return this; 884 }, 885 886 /** 887 * Resets all input values in the dialog. 888 * @example 889 * dialogObj.reset(); 890 * @returns {CKEDITOR.dialog} The current dialog object. 891 */ 892 reset : (function() 893 { 894 var fn = function( widget ){ if ( widget.reset ) widget.reset( 1 ); }; 895 return function(){ this.foreach( fn ); return this; }; 896 })(), 897 898 899 /** 900 * Calls the {@link CKEDITOR.dialog.definition.uiElement#setup} method of each of the UI elements, with the arguments passed through it. 901 * It is usually being called when the dialog is opened, to put the initial value inside the field. 902 * @example 903 * dialogObj.setupContent(); 904 * @example 905 * var timestamp = ( new Date() ).valueOf(); 906 * dialogObj.setupContent( timestamp ); 907 */ 908 setupContent : function() 909 { 910 var args = arguments; 911 this.foreach( function( widget ) 912 { 913 if ( widget.setup ) 914 widget.setup.apply( widget, args ); 915 }); 916 }, 917 918 /** 919 * Calls the {@link CKEDITOR.dialog.definition.uiElement#commit} method of each of the UI elements, with the arguments passed through it. 920 * It is usually being called when the user confirms the dialog, to process the values. 921 * @example 922 * dialogObj.commitContent(); 923 * @example 924 * var timestamp = ( new Date() ).valueOf(); 925 * dialogObj.commitContent( timestamp ); 926 */ 927 commitContent : function() 928 { 929 var args = arguments; 930 this.foreach( function( widget ) 931 { 932 // Make sure IE triggers "change" event on last focused input before closing the dialog. (#7915) 933 if ( CKEDITOR.env.ie && this._.currentFocusIndex == widget.focusIndex ) 934 widget.getInputElement().$.blur(); 935 936 if ( widget.commit ) 937 widget.commit.apply( widget, args ); 938 }); 939 }, 940 941 /** 942 * Hides the dialog box. 943 * @example 944 * dialogObj.hide(); 945 */ 946 hide : function() 947 { 948 if ( !this.parts.dialog.isVisible() ) 949 return; 950 951 this.fire( 'hide', {} ); 952 this._.editor.fire( 'dialogHide', this ); 953 var element = this._.element; 954 element.setStyle( 'display', 'none' ); 955 this.parts.dialog.setStyle( 'visibility', 'hidden' ); 956 // Unregister all access keys associated with this dialog. 957 unregisterAccessKey( this ); 958 959 // Close any child(top) dialogs first. 960 while( CKEDITOR.dialog._.currentTop != this ) 961 CKEDITOR.dialog._.currentTop.hide(); 962 963 // Maintain dialog ordering and remove cover if needed. 964 if ( !this._.parentDialog ) 965 hideCover(); 966 else 967 { 968 var parentElement = this._.parentDialog.getElement().getFirst(); 969 parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) ); 970 } 971 CKEDITOR.dialog._.currentTop = this._.parentDialog; 972 973 // Deduct or clear the z-index. 974 if ( !this._.parentDialog ) 975 { 976 CKEDITOR.dialog._.currentZIndex = null; 977 978 // Remove access key handlers. 979 element.removeListener( 'keydown', accessKeyDownHandler ); 980 element.removeListener( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); 981 982 // Remove bubbling-prevention handler. (#4269) 983 for ( var event in { keyup :1, keydown :1, keypress :1 } ) 984 element.removeListener( event, preventKeyBubbling ); 985 986 var editor = this._.editor; 987 editor.focus(); 988 989 if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) 990 { 991 var selection = editor.getSelection(); 992 selection && selection.unlock( true ); 993 } 994 } 995 else 996 CKEDITOR.dialog._.currentZIndex -= 10; 997 998 delete this._.parentDialog; 999 // Reset the initial values of the dialog. 1000 this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } ); 1001 }, 1002 1003 /** 1004 * Adds a tabbed page into the dialog. 1005 * @param {Object} contents Content definition. 1006 * @example 1007 */ 1008 addPage : function( contents ) 1009 { 1010 var pageHtml = [], 1011 titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '', 1012 elements = contents.elements, 1013 vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this, 1014 { 1015 type : 'vbox', 1016 className : 'cke_dialog_page_contents', 1017 children : contents.elements, 1018 expand : !!contents.expand, 1019 padding : contents.padding, 1020 style : contents.style || 'width: 100%;height:100%' 1021 }, pageHtml ); 1022 1023 // Create the HTML for the tab and the content block. 1024 var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); 1025 page.setAttribute( 'role', 'tabpanel' ); 1026 1027 var env = CKEDITOR.env; 1028 var tabId = 'cke_' + contents.id + '_' + CKEDITOR.tools.getNextNumber(), 1029 tab = CKEDITOR.dom.element.createFromHtml( [ 1030 '<a class="cke_dialog_tab"', 1031 ( this._.pageCount > 0 ? ' cke_last' : 'cke_first' ), 1032 titleHtml, 1033 ( !!contents.hidden ? ' style="display:none"' : '' ), 1034 ' id="', tabId, '"', 1035 env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"', 1036 ' tabIndex="-1"', 1037 ' hidefocus="true"', 1038 ' role="tab">', 1039 contents.label, 1040 '</a>' 1041 ].join( '' ) ); 1042 1043 page.setAttribute( 'aria-labelledby', tabId ); 1044 1045 // Take records for the tabs and elements created. 1046 this._.tabs[ contents.id ] = [ tab, page ]; 1047 this._.tabIdList.push( contents.id ); 1048 !contents.hidden && this._.pageCount++; 1049 this._.lastTab = tab; 1050 this.updateStyle(); 1051 1052 var contentMap = this._.contents[ contents.id ] = {}, 1053 cursor, 1054 children = vbox.getChild(); 1055 1056 while ( ( cursor = children.shift() ) ) 1057 { 1058 contentMap[ cursor.id ] = cursor; 1059 if ( typeof( cursor.getChild ) == 'function' ) 1060 children.push.apply( children, cursor.getChild() ); 1061 } 1062 1063 // Attach the DOM nodes. 1064 1065 page.setAttribute( 'name', contents.id ); 1066 page.appendTo( this.parts.contents ); 1067 1068 tab.unselectable(); 1069 this.parts.tabs.append( tab ); 1070 1071 // Add access key handlers if access key is defined. 1072 if ( contents.accessKey ) 1073 { 1074 registerAccessKey( this, this, 'CTRL+' + contents.accessKey, 1075 tabAccessKeyDown, tabAccessKeyUp ); 1076 this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id; 1077 } 1078 }, 1079 1080 /** 1081 * Activates a tab page in the dialog by its id. 1082 * @param {String} id The id of the dialog tab to be activated. 1083 * @example 1084 * dialogObj.selectPage( 'tab_1' ); 1085 */ 1086 selectPage : function( id ) 1087 { 1088 if ( this._.currentTabId == id ) 1089 return; 1090 1091 // Returning true means that the event has been canceled 1092 if ( this.fire( 'selectPage', { page : id, currentPage : this._.currentTabId } ) === true ) 1093 return; 1094 1095 // Hide the non-selected tabs and pages. 1096 for ( var i in this._.tabs ) 1097 { 1098 var tab = this._.tabs[i][0], 1099 page = this._.tabs[i][1]; 1100 if ( i != id ) 1101 { 1102 tab.removeClass( 'cke_dialog_tab_selected' ); 1103 page.hide(); 1104 } 1105 page.setAttribute( 'aria-hidden', i != id ); 1106 } 1107 1108 var selected = this._.tabs[ id ]; 1109 selected[ 0 ].addClass( 'cke_dialog_tab_selected' ); 1110 1111 // [IE] an invisible input[type='text'] will enlarge it's width 1112 // if it's value is long when it shows, so we clear it's value 1113 // before it shows and then recover it (#5649) 1114 if ( CKEDITOR.env.ie6Compat || CKEDITOR.env.ie7Compat ) 1115 { 1116 clearOrRecoverTextInputValue( selected[ 1 ] ); 1117 selected[ 1 ].show(); 1118 setTimeout( function() 1119 { 1120 clearOrRecoverTextInputValue( selected[ 1 ], 1 ); 1121 }, 0 ); 1122 } 1123 else 1124 selected[ 1 ].show(); 1125 1126 this._.currentTabId = id; 1127 this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id ); 1128 }, 1129 1130 // Dialog state-specific style updates. 1131 updateStyle : function() 1132 { 1133 // If only a single page shown, a different style is used in the central pane. 1134 this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' ); 1135 }, 1136 1137 /** 1138 * Hides a page's tab away from the dialog. 1139 * @param {String} id The page's Id. 1140 * @example 1141 * dialog.hidePage( 'tab_3' ); 1142 */ 1143 hidePage : function( id ) 1144 { 1145 var tab = this._.tabs[id] && this._.tabs[id][0]; 1146 if ( !tab || this._.pageCount == 1 || !tab.isVisible() ) 1147 return; 1148 // Switch to other tab first when we're hiding the active tab. 1149 else if ( id == this._.currentTabId ) 1150 this.selectPage( getPreviousVisibleTab.call( this ) ); 1151 1152 tab.hide(); 1153 this._.pageCount--; 1154 this.updateStyle(); 1155 }, 1156 1157 /** 1158 * Unhides a page's tab. 1159 * @param {String} id The page's Id. 1160 * @example 1161 * dialog.showPage( 'tab_2' ); 1162 */ 1163 showPage : function( id ) 1164 { 1165 var tab = this._.tabs[id] && this._.tabs[id][0]; 1166 if ( !tab ) 1167 return; 1168 tab.show(); 1169 this._.pageCount++; 1170 this.updateStyle(); 1171 }, 1172 1173 /** 1174 * Gets the root DOM element of the dialog. 1175 * @returns {CKEDITOR.dom.element} The <span> element containing this dialog. 1176 * @example 1177 * var dialogElement = dialogObj.getElement().getFirst(); 1178 * dialogElement.setStyle( 'padding', '5px' ); 1179 */ 1180 getElement : function() 1181 { 1182 return this._.element; 1183 }, 1184 1185 /** 1186 * Gets the name of the dialog. 1187 * @returns {String} The name of this dialog. 1188 * @example 1189 * var dialogName = dialogObj.getName(); 1190 */ 1191 getName : function() 1192 { 1193 return this._.name; 1194 }, 1195 1196 /** 1197 * Gets a dialog UI element object from a dialog page. 1198 * @param {String} pageId id of dialog page. 1199 * @param {String} elementId id of UI element. 1200 * @example 1201 * dialogObj.getContentElement( 'tabId', 'elementId' ).setValue( 'Example' ); 1202 * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element. 1203 */ 1204 getContentElement : function( pageId, elementId ) 1205 { 1206 var page = this._.contents[ pageId ]; 1207 return page && page[ elementId ]; 1208 }, 1209 1210 /** 1211 * Gets the value of a dialog UI element. 1212 * @param {String} pageId id of dialog page. 1213 * @param {String} elementId id of UI element. 1214 * @example 1215 * alert( dialogObj.getValueOf( 'tabId', 'elementId' ) ); 1216 * @returns {Object} The value of the UI element. 1217 */ 1218 getValueOf : function( pageId, elementId ) 1219 { 1220 return this.getContentElement( pageId, elementId ).getValue(); 1221 }, 1222 1223 /** 1224 * Sets the value of a dialog UI element. 1225 * @param {String} pageId id of the dialog page. 1226 * @param {String} elementId id of the UI element. 1227 * @param {Object} value The new value of the UI element. 1228 * @example 1229 * dialogObj.setValueOf( 'tabId', 'elementId', 'Example' ); 1230 */ 1231 setValueOf : function( pageId, elementId, value ) 1232 { 1233 return this.getContentElement( pageId, elementId ).setValue( value ); 1234 }, 1235 1236 /** 1237 * Gets the UI element of a button in the dialog's button row. 1238 * @param {String} id The id of the button. 1239 * @example 1240 * @returns {CKEDITOR.ui.dialog.button} The button object. 1241 */ 1242 getButton : function( id ) 1243 { 1244 return this._.buttons[ id ]; 1245 }, 1246 1247 /** 1248 * Simulates a click to a dialog button in the dialog's button row. 1249 * @param {String} id The id of the button. 1250 * @example 1251 * @returns The return value of the dialog's "click" event. 1252 */ 1253 click : function( id ) 1254 { 1255 return this._.buttons[ id ].click(); 1256 }, 1257 1258 /** 1259 * Disables a dialog button. 1260 * @param {String} id The id of the button. 1261 * @example 1262 */ 1263 disableButton : function( id ) 1264 { 1265 return this._.buttons[ id ].disable(); 1266 }, 1267 1268 /** 1269 * Enables a dialog button. 1270 * @param {String} id The id of the button. 1271 * @example 1272 */ 1273 enableButton : function( id ) 1274 { 1275 return this._.buttons[ id ].enable(); 1276 }, 1277 1278 /** 1279 * Gets the number of pages in the dialog. 1280 * @returns {Number} Page count. 1281 */ 1282 getPageCount : function() 1283 { 1284 return this._.pageCount; 1285 }, 1286 1287 /** 1288 * Gets the editor instance which opened this dialog. 1289 * @returns {CKEDITOR.editor} Parent editor instances. 1290 */ 1291 getParentEditor : function() 1292 { 1293 return this._.editor; 1294 }, 1295 1296 /** 1297 * Gets the element that was selected when opening the dialog, if any. 1298 * @returns {CKEDITOR.dom.element} The element that was selected, or null. 1299 */ 1300 getSelectedElement : function() 1301 { 1302 return this.getParentEditor().getSelection().getSelectedElement(); 1303 }, 1304 1305 /** 1306 * Adds element to dialog's focusable list. 1307 * 1308 * @param {CKEDITOR.dom.element} element 1309 * @param {Number} [index] 1310 */ 1311 addFocusable: function( element, index ) { 1312 if ( typeof index == 'undefined' ) 1313 { 1314 index = this._.focusList.length; 1315 this._.focusList.push( new Focusable( this, element, index ) ); 1316 } 1317 else 1318 { 1319 this._.focusList.splice( index, 0, new Focusable( this, element, index ) ); 1320 for ( var i = index + 1 ; i < this._.focusList.length ; i++ ) 1321 this._.focusList[ i ].focusIndex++; 1322 } 1323 } 1324 }; 1325 1326 CKEDITOR.tools.extend( CKEDITOR.dialog, 1327 /** 1328 * @lends CKEDITOR.dialog 1329 */ 1330 { 1331 /** 1332 * Registers a dialog. 1333 * @param {String} name The dialog's name. 1334 * @param {Function|String} dialogDefinition 1335 * A function returning the dialog's definition, or the URL to the .js file holding the function. 1336 * The function should accept an argument "editor" which is the current editor instance, and 1337 * return an object conforming to {@link CKEDITOR.dialog.definition}. 1338 * @see CKEDITOR.dialog.definition 1339 * @example 1340 * // Full sample plugin, which does not only register a dialog window but also adds an item to the context menu. 1341 * // To open the dialog window, choose "Open dialog" in the context menu. 1342 * CKEDITOR.plugins.add( 'myplugin', 1343 * { 1344 * init: function( editor ) 1345 * { 1346 * editor.addCommand( 'mydialog',new CKEDITOR.dialogCommand( 'mydialog' ) ); 1347 * 1348 * if ( editor.contextMenu ) 1349 * { 1350 * editor.addMenuGroup( 'mygroup', 10 ); 1351 * editor.addMenuItem( 'My Dialog', 1352 * { 1353 * label : 'Open dialog', 1354 * command : 'mydialog', 1355 * group : 'mygroup' 1356 * }); 1357 * editor.contextMenu.addListener( function( element ) 1358 * { 1359 * return { 'My Dialog' : CKEDITOR.TRISTATE_OFF }; 1360 * }); 1361 * } 1362 * 1363 * <strong>CKEDITOR.dialog.add</strong>( 'mydialog', function( api ) 1364 * { 1365 * // CKEDITOR.dialog.definition 1366 * var <strong>dialogDefinition</strong> = 1367 * { 1368 * title : 'Sample dialog', 1369 * minWidth : 390, 1370 * minHeight : 130, 1371 * contents : [ 1372 * { 1373 * id : 'tab1', 1374 * label : 'Label', 1375 * title : 'Title', 1376 * expand : true, 1377 * padding : 0, 1378 * elements : 1379 * [ 1380 * { 1381 * type : 'html', 1382 * html : '<p>This is some sample HTML content.</p>' 1383 * }, 1384 * { 1385 * type : 'textarea', 1386 * id : 'textareaId', 1387 * rows : 4, 1388 * cols : 40 1389 * } 1390 * ] 1391 * } 1392 * ], 1393 * buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ], 1394 * onOk : function() { 1395 * // "this" is now a CKEDITOR.dialog object. 1396 * // Accessing dialog elements: 1397 * var textareaObj = this.<strong>getContentElement</strong>( 'tab1', 'textareaId' ); 1398 * alert( "You have entered: " + textareaObj.getValue() ); 1399 * } 1400 * }; 1401 * 1402 * return dialogDefinition; 1403 * } ); 1404 * } 1405 * } ); 1406 * 1407 * CKEDITOR.replace( 'editor1', { extraPlugins : 'myplugin' } ); 1408 */ 1409 add : function( name, dialogDefinition ) 1410 { 1411 // Avoid path registration from multiple instances override definition. 1412 if ( !this._.dialogDefinitions[name] 1413 || typeof dialogDefinition == 'function' ) 1414 this._.dialogDefinitions[name] = dialogDefinition; 1415 }, 1416 1417 exists : function( name ) 1418 { 1419 return !!this._.dialogDefinitions[ name ]; 1420 }, 1421 1422 getCurrent : function() 1423 { 1424 return CKEDITOR.dialog._.currentTop; 1425 }, 1426 1427 /** 1428 * The default OK button for dialogs. Fires the "ok" event and closes the dialog if the event succeeds. 1429 * @static 1430 * @field 1431 * @example 1432 * @type Function 1433 */ 1434 okButton : (function() 1435 { 1436 var retval = function( editor, override ) 1437 { 1438 override = override || {}; 1439 return CKEDITOR.tools.extend( { 1440 id : 'ok', 1441 type : 'button', 1442 label : editor.lang.common.ok, 1443 'class' : 'cke_dialog_ui_button_ok', 1444 onClick : function( evt ) 1445 { 1446 var dialog = evt.data.dialog; 1447 if ( dialog.fire( 'ok', { hide : true } ).hide !== false ) 1448 dialog.hide(); 1449 } 1450 }, override, true ); 1451 }; 1452 retval.type = 'button'; 1453 retval.override = function( override ) 1454 { 1455 return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, 1456 { type : 'button' }, true ); 1457 }; 1458 return retval; 1459 })(), 1460 1461 /** 1462 * The default cancel button for dialogs. Fires the "cancel" event and closes the dialog if no UI element value changed. 1463 * @static 1464 * @field 1465 * @example 1466 * @type Function 1467 */ 1468 cancelButton : (function() 1469 { 1470 var retval = function( editor, override ) 1471 { 1472 override = override || {}; 1473 return CKEDITOR.tools.extend( { 1474 id : 'cancel', 1475 type : 'button', 1476 label : editor.lang.common.cancel, 1477 'class' : 'cke_dialog_ui_button_cancel', 1478 onClick : function( evt ) 1479 { 1480 var dialog = evt.data.dialog; 1481 if ( dialog.fire( 'cancel', { hide : true } ).hide !== false ) 1482 dialog.hide(); 1483 } 1484 }, override, true ); 1485 }; 1486 retval.type = 'button'; 1487 retval.override = function( override ) 1488 { 1489 return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, 1490 { type : 'button' }, true ); 1491 }; 1492 return retval; 1493 })(), 1494 1495 /** 1496 * Registers a dialog UI element. 1497 * @param {String} typeName The name of the UI element. 1498 * @param {Function} builder The function to build the UI element. 1499 * @example 1500 */ 1501 addUIElement : function( typeName, builder ) 1502 { 1503 this._.uiElementBuilders[ typeName ] = builder; 1504 } 1505 }); 1506 1507 CKEDITOR.dialog._ = 1508 { 1509 uiElementBuilders : {}, 1510 1511 dialogDefinitions : {}, 1512 1513 currentTop : null, 1514 1515 currentZIndex : null 1516 }; 1517 1518 // "Inherit" (copy actually) from CKEDITOR.event. 1519 CKEDITOR.event.implementOn( CKEDITOR.dialog ); 1520 CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype, true ); 1521 1522 var defaultDialogDefinition = 1523 { 1524 resizable : CKEDITOR.DIALOG_RESIZE_BOTH, 1525 minWidth : 600, 1526 minHeight : 400, 1527 buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] 1528 }; 1529 1530 // Tool function used to return an item from an array based on its id 1531 // property. 1532 var getById = function( array, id, recurse ) 1533 { 1534 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) 1535 { 1536 if ( item.id == id ) 1537 return item; 1538 if ( recurse && item[ recurse ] ) 1539 { 1540 var retval = getById( item[ recurse ], id, recurse ) ; 1541 if ( retval ) 1542 return retval; 1543 } 1544 } 1545 return null; 1546 }; 1547 1548 // Tool function used to add an item into an array. 1549 var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound ) 1550 { 1551 if ( nextSiblingId ) 1552 { 1553 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) 1554 { 1555 if ( item.id == nextSiblingId ) 1556 { 1557 array.splice( i, 0, newItem ); 1558 return newItem; 1559 } 1560 1561 if ( recurse && item[ recurse ] ) 1562 { 1563 var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true ); 1564 if ( retval ) 1565 return retval; 1566 } 1567 } 1568 1569 if ( nullIfNotFound ) 1570 return null; 1571 } 1572 1573 array.push( newItem ); 1574 return newItem; 1575 }; 1576 1577 // Tool function used to remove an item from an array based on its id. 1578 var removeById = function( array, id, recurse ) 1579 { 1580 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) 1581 { 1582 if ( item.id == id ) 1583 return array.splice( i, 1 ); 1584 if ( recurse && item[ recurse ] ) 1585 { 1586 var retval = removeById( item[ recurse ], id, recurse ); 1587 if ( retval ) 1588 return retval; 1589 } 1590 } 1591 return null; 1592 }; 1593 1594 /** 1595 * This class is not really part of the API. It is the "definition" property value 1596 * passed to "dialogDefinition" event handlers. 1597 * @constructor 1598 * @name CKEDITOR.dialog.definitionObject 1599 * @extends CKEDITOR.dialog.definition 1600 * @example 1601 * CKEDITOR.on( 'dialogDefinition', function( evt ) 1602 * { 1603 * var definition = evt.data.definition; 1604 * var content = definition.getContents( 'page1' ); 1605 * ... 1606 * } ); 1607 */ 1608 var definitionObject = function( dialog, dialogDefinition ) 1609 { 1610 // TODO : Check if needed. 1611 this.dialog = dialog; 1612 1613 // Transform the contents entries in contentObjects. 1614 var contents = dialogDefinition.contents; 1615 for ( var i = 0, content ; ( content = contents[i] ) ; i++ ) 1616 contents[ i ] = content && new contentObject( dialog, content ); 1617 1618 CKEDITOR.tools.extend( this, dialogDefinition ); 1619 }; 1620 1621 definitionObject.prototype = 1622 /** @lends CKEDITOR.dialog.definitionObject.prototype */ 1623 { 1624 /** 1625 * Gets a content definition. 1626 * @param {String} id The id of the content definition. 1627 * @returns {CKEDITOR.dialog.definition.content} The content definition 1628 * matching id. 1629 */ 1630 getContents : function( id ) 1631 { 1632 return getById( this.contents, id ); 1633 }, 1634 1635 /** 1636 * Gets a button definition. 1637 * @param {String} id The id of the button definition. 1638 * @returns {CKEDITOR.dialog.definition.button} The button definition 1639 * matching id. 1640 */ 1641 getButton : function( id ) 1642 { 1643 return getById( this.buttons, id ); 1644 }, 1645 1646 /** 1647 * Adds a content definition object under this dialog definition. 1648 * @param {CKEDITOR.dialog.definition.content} contentDefinition The 1649 * content definition. 1650 * @param {String} [nextSiblingId] The id of an existing content 1651 * definition which the new content definition will be inserted 1652 * before. Omit if the new content definition is to be inserted as 1653 * the last item. 1654 * @returns {CKEDITOR.dialog.definition.content} The inserted content 1655 * definition. 1656 */ 1657 addContents : function( contentDefinition, nextSiblingId ) 1658 { 1659 return addById( this.contents, contentDefinition, nextSiblingId ); 1660 }, 1661 1662 /** 1663 * Adds a button definition object under this dialog definition. 1664 * @param {CKEDITOR.dialog.definition.button} buttonDefinition The 1665 * button definition. 1666 * @param {String} [nextSiblingId] The id of an existing button 1667 * definition which the new button definition will be inserted 1668 * before. Omit if the new button definition is to be inserted as 1669 * the last item. 1670 * @returns {CKEDITOR.dialog.definition.button} The inserted button 1671 * definition. 1672 */ 1673 addButton : function( buttonDefinition, nextSiblingId ) 1674 { 1675 return addById( this.buttons, buttonDefinition, nextSiblingId ); 1676 }, 1677 1678 /** 1679 * Removes a content definition from this dialog definition. 1680 * @param {String} id The id of the content definition to be removed. 1681 * @returns {CKEDITOR.dialog.definition.content} The removed content 1682 * definition. 1683 */ 1684 removeContents : function( id ) 1685 { 1686 removeById( this.contents, id ); 1687 }, 1688 1689 /** 1690 * Removes a button definition from the dialog definition. 1691 * @param {String} id The id of the button definition to be removed. 1692 * @returns {CKEDITOR.dialog.definition.button} The removed button 1693 * definition. 1694 */ 1695 removeButton : function( id ) 1696 { 1697 removeById( this.buttons, id ); 1698 } 1699 }; 1700 1701 /** 1702 * This class is not really part of the API. It is the template of the 1703 * objects representing content pages inside the 1704 * CKEDITOR.dialog.definitionObject. 1705 * @constructor 1706 * @name CKEDITOR.dialog.definition.contentObject 1707 * @example 1708 * CKEDITOR.on( 'dialogDefinition', function( evt ) 1709 * { 1710 * var definition = evt.data.definition; 1711 * var content = definition.getContents( 'page1' ); 1712 * content.remove( 'textInput1' ); 1713 * ... 1714 * } ); 1715 */ 1716 function contentObject( dialog, contentDefinition ) 1717 { 1718 this._ = 1719 { 1720 dialog : dialog 1721 }; 1722 1723 CKEDITOR.tools.extend( this, contentDefinition ); 1724 } 1725 1726 contentObject.prototype = 1727 /** @lends CKEDITOR.dialog.definition.contentObject.prototype */ 1728 { 1729 /** 1730 * Gets a UI element definition under the content definition. 1731 * @param {String} id The id of the UI element definition. 1732 * @returns {CKEDITOR.dialog.definition.uiElement} 1733 */ 1734 get : function( id ) 1735 { 1736 return getById( this.elements, id, 'children' ); 1737 }, 1738 1739 /** 1740 * Adds a UI element definition to the content definition. 1741 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition The 1742 * UI elemnet definition to be added. 1743 * @param {String} nextSiblingId The id of an existing UI element 1744 * definition which the new UI element definition will be inserted 1745 * before. Omit if the new button definition is to be inserted as 1746 * the last item. 1747 * @returns {CKEDITOR.dialog.definition.uiElement} The element 1748 * definition inserted. 1749 */ 1750 add : function( elementDefinition, nextSiblingId ) 1751 { 1752 return addById( this.elements, elementDefinition, nextSiblingId, 'children' ); 1753 }, 1754 1755 /** 1756 * Removes a UI element definition from the content definition. 1757 * @param {String} id The id of the UI element definition to be 1758 * removed. 1759 * @returns {CKEDITOR.dialog.definition.uiElement} The element 1760 * definition removed. 1761 * @example 1762 */ 1763 remove : function( id ) 1764 { 1765 removeById( this.elements, id, 'children' ); 1766 } 1767 }; 1768 1769 function initDragAndDrop( dialog ) 1770 { 1771 var lastCoords = null, 1772 abstractDialogCoords = null, 1773 element = dialog.getElement().getFirst(), 1774 editor = dialog.getParentEditor(), 1775 magnetDistance = editor.config.dialog_magnetDistance, 1776 margins = editor.skin.margins || [ 0, 0, 0, 0 ]; 1777 1778 if ( typeof magnetDistance == 'undefined' ) 1779 magnetDistance = 20; 1780 1781 function mouseMoveHandler( evt ) 1782 { 1783 var dialogSize = dialog.getSize(), 1784 viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), 1785 x = evt.data.$.screenX, 1786 y = evt.data.$.screenY, 1787 dx = x - lastCoords.x, 1788 dy = y - lastCoords.y, 1789 realX, realY; 1790 1791 lastCoords = { x : x, y : y }; 1792 abstractDialogCoords.x += dx; 1793 abstractDialogCoords.y += dy; 1794 1795 if ( abstractDialogCoords.x + margins[3] < magnetDistance ) 1796 realX = - margins[3]; 1797 else if ( abstractDialogCoords.x - margins[1] > viewPaneSize.width - dialogSize.width - magnetDistance ) 1798 realX = viewPaneSize.width - dialogSize.width + ( editor.lang.dir == 'rtl' ? 0 : margins[1] ); 1799 else 1800 realX = abstractDialogCoords.x; 1801 1802 if ( abstractDialogCoords.y + margins[0] < magnetDistance ) 1803 realY = - margins[0]; 1804 else if ( abstractDialogCoords.y - margins[2] > viewPaneSize.height - dialogSize.height - magnetDistance ) 1805 realY = viewPaneSize.height - dialogSize.height + margins[2]; 1806 else 1807 realY = abstractDialogCoords.y; 1808 1809 dialog.move( realX, realY, 1 ); 1810 1811 evt.data.preventDefault(); 1812 } 1813 1814 function mouseUpHandler( evt ) 1815 { 1816 CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); 1817 CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); 1818 1819 if ( CKEDITOR.env.ie6Compat ) 1820 { 1821 var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); 1822 coverDoc.removeListener( 'mousemove', mouseMoveHandler ); 1823 coverDoc.removeListener( 'mouseup', mouseUpHandler ); 1824 } 1825 } 1826 1827 dialog.parts.title.on( 'mousedown', function( evt ) 1828 { 1829 lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; 1830 1831 CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); 1832 CKEDITOR.document.on( 'mouseup', mouseUpHandler ); 1833 abstractDialogCoords = dialog.getPosition(); 1834 1835 if ( CKEDITOR.env.ie6Compat ) 1836 { 1837 var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); 1838 coverDoc.on( 'mousemove', mouseMoveHandler ); 1839 coverDoc.on( 'mouseup', mouseUpHandler ); 1840 } 1841 1842 evt.data.preventDefault(); 1843 }, dialog ); 1844 } 1845 1846 function initResizeHandles( dialog ) 1847 { 1848 var def = dialog.definition, 1849 resizable = def.resizable; 1850 1851 if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE ) 1852 return; 1853 1854 var editor = dialog.getParentEditor(); 1855 var wrapperWidth, wrapperHeight, 1856 viewSize, origin, startSize, 1857 dialogCover; 1858 1859 var mouseDownFn = CKEDITOR.tools.addFunction( function( $event ) 1860 { 1861 startSize = dialog.getSize(); 1862 1863 var content = dialog.parts.contents, 1864 iframeDialog = content.$.getElementsByTagName( 'iframe' ).length; 1865 1866 // Shim to help capturing "mousemove" over iframe. 1867 if ( iframeDialog ) 1868 { 1869 dialogCover = CKEDITOR.dom.element.createFromHtml( '<div class="cke_dialog_resize_cover" style="height: 100%; position: absolute; width: 100%;"></div>' ); 1870 content.append( dialogCover ); 1871 } 1872 1873 // Calculate the offset between content and chrome size. 1874 wrapperHeight = startSize.height - dialog.parts.contents.getSize( 'height', ! ( CKEDITOR.env.gecko || CKEDITOR.env.opera || CKEDITOR.env.ie && CKEDITOR.env.quirks ) ); 1875 wrapperWidth = startSize.width - dialog.parts.contents.getSize( 'width', 1 ); 1876 1877 origin = { x : $event.screenX, y : $event.screenY }; 1878 1879 viewSize = CKEDITOR.document.getWindow().getViewPaneSize(); 1880 1881 CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); 1882 CKEDITOR.document.on( 'mouseup', mouseUpHandler ); 1883 1884 if ( CKEDITOR.env.ie6Compat ) 1885 { 1886 var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); 1887 coverDoc.on( 'mousemove', mouseMoveHandler ); 1888 coverDoc.on( 'mouseup', mouseUpHandler ); 1889 } 1890 1891 $event.preventDefault && $event.preventDefault(); 1892 }); 1893 1894 // Prepend the grip to the dialog. 1895 dialog.on( 'load', function() 1896 { 1897 var direction = ''; 1898 if ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH ) 1899 direction = ' cke_resizer_horizontal'; 1900 else if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT ) 1901 direction = ' cke_resizer_vertical'; 1902 var resizer = CKEDITOR.dom.element.createFromHtml( '<div' + 1903 ' class="cke_resizer' + direction + ' cke_resizer_' + editor.lang.dir + '"' + 1904 ' title="' + CKEDITOR.tools.htmlEncode( editor.lang.resize ) + '"' + 1905 ' onmousedown="CKEDITOR.tools.callFunction(' + mouseDownFn + ', event )"></div>' ); 1906 dialog.parts.footer.append( resizer, 1 ); 1907 }); 1908 editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( mouseDownFn ); } ); 1909 1910 function mouseMoveHandler( evt ) 1911 { 1912 var rtl = editor.lang.dir == 'rtl', 1913 dx = ( evt.data.$.screenX - origin.x ) * ( rtl ? -1 : 1 ), 1914 dy = evt.data.$.screenY - origin.y, 1915 width = startSize.width, 1916 height = startSize.height, 1917 internalWidth = width + dx * ( dialog._.moved ? 1 : 2 ), 1918 internalHeight = height + dy * ( dialog._.moved ? 1 : 2 ), 1919 element = dialog._.element.getFirst(), 1920 right = rtl && element.getComputedStyle( 'right' ), 1921 position = dialog.getPosition(); 1922 1923 if ( position.y + internalHeight > viewSize.height ) 1924 internalHeight = viewSize.height - position.y; 1925 1926 if ( ( rtl ? right : position.x ) + internalWidth > viewSize.width ) 1927 internalWidth = viewSize.width - ( rtl ? right : position.x ); 1928 1929 // Make sure the dialog will not be resized to the wrong side when it's in the leftmost position for RTL. 1930 if ( ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) ) 1931 width = Math.max( def.minWidth || 0, internalWidth - wrapperWidth ); 1932 1933 if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) 1934 height = Math.max( def.minHeight || 0, internalHeight - wrapperHeight ); 1935 1936 dialog.resize( width, height ); 1937 1938 if ( !dialog._.moved ) 1939 dialog.layout(); 1940 1941 evt.data.preventDefault(); 1942 } 1943 1944 function mouseUpHandler() 1945 { 1946 CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); 1947 CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); 1948 1949 if ( dialogCover ) 1950 { 1951 dialogCover.remove(); 1952 dialogCover = null; 1953 } 1954 1955 if ( CKEDITOR.env.ie6Compat ) 1956 { 1957 var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); 1958 coverDoc.removeListener( 'mouseup', mouseUpHandler ); 1959 coverDoc.removeListener( 'mousemove', mouseMoveHandler ); 1960 } 1961 } 1962 } 1963 1964 var resizeCover; 1965 // Caching resuable covers and allowing only one cover 1966 // on screen. 1967 var covers = {}, 1968 currentCover; 1969 1970 function cancelEvent( ev ) 1971 { 1972 ev.data.preventDefault(1); 1973 } 1974 1975 function showCover( editor ) 1976 { 1977 var win = CKEDITOR.document.getWindow(); 1978 var config = editor.config, 1979 backgroundColorStyle = config.dialog_backgroundCoverColor || 'white', 1980 backgroundCoverOpacity = config.dialog_backgroundCoverOpacity, 1981 baseFloatZIndex = config.baseFloatZIndex, 1982 coverKey = CKEDITOR.tools.genKey( 1983 backgroundColorStyle, 1984 backgroundCoverOpacity, 1985 baseFloatZIndex ), 1986 coverElement = covers[ coverKey ]; 1987 1988 if ( !coverElement ) 1989 { 1990 var html = [ 1991 '<div tabIndex="-1" style="position: ', ( CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed' ), 1992 '; z-index: ', baseFloatZIndex, 1993 '; top: 0px; left: 0px; ', 1994 ( !CKEDITOR.env.ie6Compat ? 'background-color: ' + backgroundColorStyle : '' ), 1995 '" class="cke_dialog_background_cover">' 1996 ]; 1997 1998 if ( CKEDITOR.env.ie6Compat ) 1999 { 2000 // Support for custom document.domain in IE. 2001 var isCustomDomain = CKEDITOR.env.isCustomDomain(), 2002 iframeHtml = '<html><body style=\\\'background-color:' + backgroundColorStyle + ';\\\'></body></html>'; 2003 2004 html.push( 2005 '<iframe' + 2006 ' hidefocus="true"' + 2007 ' frameborder="0"' + 2008 ' id="cke_dialog_background_iframe"' + 2009 ' src="javascript:' ); 2010 2011 html.push( 'void((function(){' + 2012 'document.open();' + 2013 ( isCustomDomain ? 'document.domain=\'' + document.domain + '\';' : '' ) + 2014 'document.write( \'' + iframeHtml + '\' );' + 2015 'document.close();' + 2016 '})())' ); 2017 2018 html.push( 2019 '"' + 2020 ' style="' + 2021 'position:absolute;' + 2022 'left:0;' + 2023 'top:0;' + 2024 'width:100%;' + 2025 'height: 100%;' + 2026 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)">' + 2027 '</iframe>' ); 2028 } 2029 2030 html.push( '</div>' ); 2031 2032 coverElement = CKEDITOR.dom.element.createFromHtml( html.join( '' ) ); 2033 coverElement.setOpacity( backgroundCoverOpacity != undefined ? backgroundCoverOpacity : 0.5 ); 2034 2035 coverElement.on( 'keydown', cancelEvent ); 2036 coverElement.on( 'keypress', cancelEvent ); 2037 coverElement.on( 'keyup', cancelEvent ); 2038 2039 coverElement.appendTo( CKEDITOR.document.getBody() ); 2040 covers[ coverKey ] = coverElement; 2041 } 2042 else 2043 coverElement. show(); 2044 2045 currentCover = coverElement; 2046 var resizeFunc = function() 2047 { 2048 var size = win.getViewPaneSize(); 2049 coverElement.setStyles( 2050 { 2051 width : size.width + 'px', 2052 height : size.height + 'px' 2053 } ); 2054 }; 2055 2056 var scrollFunc = function() 2057 { 2058 var pos = win.getScrollPosition(), 2059 cursor = CKEDITOR.dialog._.currentTop; 2060 coverElement.setStyles( 2061 { 2062 left : pos.x + 'px', 2063 top : pos.y + 'px' 2064 }); 2065 2066 if ( cursor ) 2067 { 2068 do 2069 { 2070 var dialogPos = cursor.getPosition(); 2071 cursor.move( dialogPos.x, dialogPos.y ); 2072 } while ( ( cursor = cursor._.parentDialog ) ); 2073 } 2074 }; 2075 2076 resizeCover = resizeFunc; 2077 win.on( 'resize', resizeFunc ); 2078 resizeFunc(); 2079 // Using Safari/Mac, focus must be kept where it is (#7027) 2080 if ( !( CKEDITOR.env.mac && CKEDITOR.env.webkit ) ) 2081 coverElement.focus(); 2082 2083 if ( CKEDITOR.env.ie6Compat ) 2084 { 2085 // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll. 2086 // So we need to invent a really funny way to make it work. 2087 var myScrollHandler = function() 2088 { 2089 scrollFunc(); 2090 arguments.callee.prevScrollHandler.apply( this, arguments ); 2091 }; 2092 win.$.setTimeout( function() 2093 { 2094 myScrollHandler.prevScrollHandler = window.onscroll || function(){}; 2095 window.onscroll = myScrollHandler; 2096 }, 0 ); 2097 scrollFunc(); 2098 } 2099 } 2100 2101 function hideCover() 2102 { 2103 if ( !currentCover ) 2104 return; 2105 2106 var win = CKEDITOR.document.getWindow(); 2107 currentCover.hide(); 2108 win.removeListener( 'resize', resizeCover ); 2109 2110 if ( CKEDITOR.env.ie6Compat ) 2111 { 2112 win.$.setTimeout( function() 2113 { 2114 var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler; 2115 window.onscroll = prevScrollHandler || null; 2116 }, 0 ); 2117 } 2118 resizeCover = null; 2119 } 2120 2121 function removeCovers() 2122 { 2123 for ( var coverId in covers ) 2124 covers[ coverId ].remove(); 2125 covers = {}; 2126 } 2127 2128 var accessKeyProcessors = {}; 2129 2130 var accessKeyDownHandler = function( evt ) 2131 { 2132 var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, 2133 alt = evt.data.$.altKey, 2134 shift = evt.data.$.shiftKey, 2135 key = String.fromCharCode( evt.data.$.keyCode ), 2136 keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; 2137 2138 if ( !keyProcessor || !keyProcessor.length ) 2139 return; 2140 2141 keyProcessor = keyProcessor[keyProcessor.length - 1]; 2142 keyProcessor.keydown && keyProcessor.keydown.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); 2143 evt.data.preventDefault(); 2144 }; 2145 2146 var accessKeyUpHandler = function( evt ) 2147 { 2148 var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, 2149 alt = evt.data.$.altKey, 2150 shift = evt.data.$.shiftKey, 2151 key = String.fromCharCode( evt.data.$.keyCode ), 2152 keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; 2153 2154 if ( !keyProcessor || !keyProcessor.length ) 2155 return; 2156 2157 keyProcessor = keyProcessor[keyProcessor.length - 1]; 2158 if ( keyProcessor.keyup ) 2159 { 2160 keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); 2161 evt.data.preventDefault(); 2162 } 2163 }; 2164 2165 var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc ) 2166 { 2167 var procList = accessKeyProcessors[key] || ( accessKeyProcessors[key] = [] ); 2168 procList.push( { 2169 uiElement : uiElement, 2170 dialog : dialog, 2171 key : key, 2172 keyup : upFunc || uiElement.accessKeyUp, 2173 keydown : downFunc || uiElement.accessKeyDown 2174 } ); 2175 }; 2176 2177 var unregisterAccessKey = function( obj ) 2178 { 2179 for ( var i in accessKeyProcessors ) 2180 { 2181 var list = accessKeyProcessors[i]; 2182 for ( var j = list.length - 1 ; j >= 0 ; j-- ) 2183 { 2184 if ( list[j].dialog == obj || list[j].uiElement == obj ) 2185 list.splice( j, 1 ); 2186 } 2187 if ( list.length === 0 ) 2188 delete accessKeyProcessors[i]; 2189 } 2190 }; 2191 2192 var tabAccessKeyUp = function( dialog, key ) 2193 { 2194 if ( dialog._.accessKeyMap[key] ) 2195 dialog.selectPage( dialog._.accessKeyMap[key] ); 2196 }; 2197 2198 var tabAccessKeyDown = function( dialog, key ) 2199 { 2200 }; 2201 2202 // ESC, ENTER 2203 var preventKeyBubblingKeys = { 27 :1, 13 :1 }; 2204 var preventKeyBubbling = function( e ) 2205 { 2206 if ( e.data.getKeystroke() in preventKeyBubblingKeys ) 2207 e.data.stopPropagation(); 2208 }; 2209 2210 (function() 2211 { 2212 CKEDITOR.ui.dialog = 2213 { 2214 /** 2215 * The base class of all dialog UI elements. 2216 * @constructor 2217 * @param {CKEDITOR.dialog} dialog Parent dialog object. 2218 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element 2219 * definition. Accepted fields: 2220 * <ul> 2221 * <li><strong>id</strong> (Required) The id of the UI element. See {@link 2222 * CKEDITOR.dialog#getContentElement}</li> 2223 * <li><strong>type</strong> (Required) The type of the UI element. The 2224 * value to this field specifies which UI element class will be used to 2225 * generate the final widget.</li> 2226 * <li><strong>title</strong> (Optional) The popup tooltip for the UI 2227 * element.</li> 2228 * <li><strong>hidden</strong> (Optional) A flag that tells if the element 2229 * should be initially visible.</li> 2230 * <li><strong>className</strong> (Optional) Additional CSS class names 2231 * to add to the UI element. Separated by space.</li> 2232 * <li><strong>style</strong> (Optional) Additional CSS inline styles 2233 * to add to the UI element. A semicolon (;) is required after the last 2234 * style declaration.</li> 2235 * <li><strong>accessKey</strong> (Optional) The alphanumeric access key 2236 * for this element. Access keys are automatically prefixed by CTRL.</li> 2237 * <li><strong>on*</strong> (Optional) Any UI element definition field that 2238 * starts with <em>on</em> followed immediately by a capital letter and 2239 * probably more letters is an event handler. Event handlers may be further 2240 * divided into registered event handlers and DOM event handlers. Please 2241 * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and 2242 * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more 2243 * information.</li> 2244 * </ul> 2245 * @param {Array} htmlList 2246 * List of HTML code to be added to the dialog's content area. 2247 * @param {Function|String} nodeNameArg 2248 * A function returning a string, or a simple string for the node name for 2249 * the root DOM node. Default is 'div'. 2250 * @param {Function|Object} stylesArg 2251 * A function returning an object, or a simple object for CSS styles applied 2252 * to the DOM node. Default is empty object. 2253 * @param {Function|Object} attributesArg 2254 * A fucntion returning an object, or a simple object for attributes applied 2255 * to the DOM node. Default is empty object. 2256 * @param {Function|String} contentsArg 2257 * A function returning a string, or a simple string for the HTML code inside 2258 * the root DOM node. Default is empty string. 2259 * @example 2260 */ 2261 uiElement : function( dialog, elementDefinition, htmlList, nodeNameArg, stylesArg, attributesArg, contentsArg ) 2262 { 2263 if ( arguments.length < 4 ) 2264 return; 2265 2266 var nodeName = ( nodeNameArg.call ? nodeNameArg( elementDefinition ) : nodeNameArg ) || 'div', 2267 html = [ '<', nodeName, ' ' ], 2268 styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {}, 2269 attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {}, 2270 innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '', 2271 domId = this.domId = attributes.id || CKEDITOR.tools.getNextId() + '_uiElement', 2272 id = this.id = elementDefinition.id, 2273 i; 2274 2275 // Set the id, a unique id is required for getElement() to work. 2276 attributes.id = domId; 2277 2278 // Set the type and definition CSS class names. 2279 var classes = {}; 2280 if ( elementDefinition.type ) 2281 classes[ 'cke_dialog_ui_' + elementDefinition.type ] = 1; 2282 if ( elementDefinition.className ) 2283 classes[ elementDefinition.className ] = 1; 2284 if ( elementDefinition.disabled ) 2285 classes[ 'cke_disabled' ] = 1; 2286 2287 var attributeClasses = ( attributes['class'] && attributes['class'].split ) ? attributes['class'].split( ' ' ) : []; 2288 for ( i = 0 ; i < attributeClasses.length ; i++ ) 2289 { 2290 if ( attributeClasses[i] ) 2291 classes[ attributeClasses[i] ] = 1; 2292 } 2293 var finalClasses = []; 2294 for ( i in classes ) 2295 finalClasses.push( i ); 2296 attributes['class'] = finalClasses.join( ' ' ); 2297 2298 // Set the popup tooltop. 2299 if ( elementDefinition.title ) 2300 attributes.title = elementDefinition.title; 2301 2302 // Write the inline CSS styles. 2303 var styleStr = ( elementDefinition.style || '' ).split( ';' ); 2304 2305 // Element alignment support. 2306 if ( elementDefinition.align ) 2307 { 2308 var align = elementDefinition.align; 2309 styles[ 'margin-left' ] = align == 'left' ? 0 : 'auto'; 2310 styles[ 'margin-right' ] = align == 'right' ? 0 : 'auto'; 2311 } 2312 2313 for ( i in styles ) 2314 styleStr.push( i + ':' + styles[i] ); 2315 if ( elementDefinition.hidden ) 2316 styleStr.push( 'display:none' ); 2317 for ( i = styleStr.length - 1 ; i >= 0 ; i-- ) 2318 { 2319 if ( styleStr[i] === '' ) 2320 styleStr.splice( i, 1 ); 2321 } 2322 if ( styleStr.length > 0 ) 2323 attributes.style = ( attributes.style ? ( attributes.style + '; ' ) : '' ) + styleStr.join( '; ' ); 2324 2325 // Write the attributes. 2326 for ( i in attributes ) 2327 html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" '); 2328 2329 // Write the content HTML. 2330 html.push( '>', innerHTML, '</', nodeName, '>' ); 2331 2332 // Add contents to the parent HTML array. 2333 htmlList.push( html.join( '' ) ); 2334 2335 ( this._ || ( this._ = {} ) ).dialog = dialog; 2336 2337 // Override isChanged if it is defined in element definition. 2338 if ( typeof( elementDefinition.isChanged ) == 'boolean' ) 2339 this.isChanged = function(){ return elementDefinition.isChanged; }; 2340 if ( typeof( elementDefinition.isChanged ) == 'function' ) 2341 this.isChanged = elementDefinition.isChanged; 2342 2343 // Overload 'get(set)Value' on definition. 2344 if ( typeof( elementDefinition.setValue ) == 'function' ) 2345 { 2346 this.setValue = CKEDITOR.tools.override( this.setValue, function( org ) 2347 { 2348 return function( val ){ org.call( this, elementDefinition.setValue.call( this, val ) ); }; 2349 } ); 2350 } 2351 2352 if ( typeof( elementDefinition.getValue ) == 'function' ) 2353 { 2354 this.getValue = CKEDITOR.tools.override( this.getValue, function( org ) 2355 { 2356 return function(){ return elementDefinition.getValue.call( this, org.call( this ) ); }; 2357 } ); 2358 } 2359 2360 // Add events. 2361 CKEDITOR.event.implementOn( this ); 2362 2363 this.registerEvents( elementDefinition ); 2364 if ( this.accessKeyUp && this.accessKeyDown && elementDefinition.accessKey ) 2365 registerAccessKey( this, dialog, 'CTRL+' + elementDefinition.accessKey ); 2366 2367 var me = this; 2368 dialog.on( 'load', function() 2369 { 2370 var input = me.getInputElement(); 2371 if ( input ) 2372 { 2373 var focusClass = me.type in { 'checkbox' : 1, 'ratio' : 1 } && CKEDITOR.env.ie && CKEDITOR.env.version < 8 ? 'cke_dialog_ui_focused' : ''; 2374 input.on( 'focus', function() 2375 { 2376 dialog._.tabBarMode = false; 2377 dialog._.hasFocus = true; 2378 me.fire( 'focus' ); 2379 focusClass && this.addClass( focusClass ); 2380 2381 }); 2382 2383 input.on( 'blur', function() 2384 { 2385 me.fire( 'blur' ); 2386 focusClass && this.removeClass( focusClass ); 2387 }); 2388 } 2389 } ); 2390 2391 // Register the object as a tab focus if it can be included. 2392 if ( this.keyboardFocusable ) 2393 { 2394 this.tabIndex = elementDefinition.tabIndex || 0; 2395 2396 this.focusIndex = dialog._.focusList.push( this ) - 1; 2397 this.on( 'focus', function() 2398 { 2399 dialog._.currentFocusIndex = me.focusIndex; 2400 } ); 2401 } 2402 2403 // Completes this object with everything we have in the 2404 // definition. 2405 CKEDITOR.tools.extend( this, elementDefinition ); 2406 }, 2407 2408 /** 2409 * Horizontal layout box for dialog UI elements, auto-expends to available width of container. 2410 * @constructor 2411 * @extends CKEDITOR.ui.dialog.uiElement 2412 * @param {CKEDITOR.dialog} dialog 2413 * Parent dialog object. 2414 * @param {Array} childObjList 2415 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this 2416 * container. 2417 * @param {Array} childHtmlList 2418 * Array of HTML code that correspond to the HTML output of all the 2419 * objects in childObjList. 2420 * @param {Array} htmlList 2421 * Array of HTML code that this element will output to. 2422 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition 2423 * The element definition. Accepted fields: 2424 * <ul> 2425 * <li><strong>widths</strong> (Optional) The widths of child cells.</li> 2426 * <li><strong>height</strong> (Optional) The height of the layout.</li> 2427 * <li><strong>padding</strong> (Optional) The padding width inside child 2428 * cells.</li> 2429 * <li><strong>align</strong> (Optional) The alignment of the whole layout 2430 * </li> 2431 * </ul> 2432 * @example 2433 */ 2434 hbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) 2435 { 2436 if ( arguments.length < 4 ) 2437 return; 2438 2439 this._ || ( this._ = {} ); 2440 2441 var children = this._.children = childObjList, 2442 widths = elementDefinition && elementDefinition.widths || null, 2443 height = elementDefinition && elementDefinition.height || null, 2444 styles = {}, 2445 i; 2446 /** @ignore */ 2447 var innerHTML = function() 2448 { 2449 var html = [ '<tbody><tr class="cke_dialog_ui_hbox">' ]; 2450 for ( i = 0 ; i < childHtmlList.length ; i++ ) 2451 { 2452 var className = 'cke_dialog_ui_hbox_child', 2453 styles = []; 2454 if ( i === 0 ) 2455 className = 'cke_dialog_ui_hbox_first'; 2456 if ( i == childHtmlList.length - 1 ) 2457 className = 'cke_dialog_ui_hbox_last'; 2458 html.push( '<td class="', className, '" role="presentation" ' ); 2459 if ( widths ) 2460 { 2461 if ( widths[i] ) 2462 styles.push( 'width:' + cssLength( widths[i] ) ); 2463 } 2464 else 2465 styles.push( 'width:' + Math.floor( 100 / childHtmlList.length ) + '%' ); 2466 if ( height ) 2467 styles.push( 'height:' + cssLength( height ) ); 2468 if ( elementDefinition && elementDefinition.padding != undefined ) 2469 styles.push( 'padding:' + cssLength( elementDefinition.padding ) ); 2470 // In IE Quirks alignment has to be done on table cells. (#7324) 2471 if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && children[ i ].align ) 2472 styles.push( 'text-align:' + children[ i ].align ); 2473 if ( styles.length > 0 ) 2474 html.push( 'style="' + styles.join('; ') + '" ' ); 2475 html.push( '>', childHtmlList[i], '</td>' ); 2476 } 2477 html.push( '</tr></tbody>' ); 2478 return html.join( '' ); 2479 }; 2480 2481 var attribs = { role : 'presentation' }; 2482 elementDefinition && elementDefinition.align && ( attribs.align = elementDefinition.align ); 2483 2484 CKEDITOR.ui.dialog.uiElement.call( 2485 this, 2486 dialog, 2487 elementDefinition || { type : 'hbox' }, 2488 htmlList, 2489 'table', 2490 styles, 2491 attribs, 2492 innerHTML ); 2493 }, 2494 2495 /** 2496 * Vertical layout box for dialog UI elements. 2497 * @constructor 2498 * @extends CKEDITOR.ui.dialog.hbox 2499 * @param {CKEDITOR.dialog} dialog 2500 * Parent dialog object. 2501 * @param {Array} childObjList 2502 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this 2503 * container. 2504 * @param {Array} childHtmlList 2505 * Array of HTML code that correspond to the HTML output of all the 2506 * objects in childObjList. 2507 * @param {Array} htmlList 2508 * Array of HTML code that this element will output to. 2509 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition 2510 * The element definition. Accepted fields: 2511 * <ul> 2512 * <li><strong>width</strong> (Optional) The width of the layout.</li> 2513 * <li><strong>heights</strong> (Optional) The heights of individual cells. 2514 * </li> 2515 * <li><strong>align</strong> (Optional) The alignment of the layout.</li> 2516 * <li><strong>padding</strong> (Optional) The padding width inside child 2517 * cells.</li> 2518 * <li><strong>expand</strong> (Optional) Whether the layout should expand 2519 * vertically to fill its container.</li> 2520 * </ul> 2521 * @example 2522 */ 2523 vbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) 2524 { 2525 if ( arguments.length < 3 ) 2526 return; 2527 2528 this._ || ( this._ = {} ); 2529 2530 var children = this._.children = childObjList, 2531 width = elementDefinition && elementDefinition.width || null, 2532 heights = elementDefinition && elementDefinition.heights || null; 2533 /** @ignore */ 2534 var innerHTML = function() 2535 { 2536 var html = [ '<table role="presentation" cellspacing="0" border="0" ' ]; 2537 html.push( 'style="' ); 2538 if ( elementDefinition && elementDefinition.expand ) 2539 html.push( 'height:100%;' ); 2540 html.push( 'width:' + cssLength( width || '100%' ), ';' ); 2541 html.push( '"' ); 2542 html.push( 'align="', CKEDITOR.tools.htmlEncode( 2543 ( elementDefinition && elementDefinition.align ) || ( dialog.getParentEditor().lang.dir == 'ltr' ? 'left' : 'right' ) ), '" ' ); 2544 2545 html.push( '><tbody>' ); 2546 for ( var i = 0 ; i < childHtmlList.length ; i++ ) 2547 { 2548 var styles = []; 2549 html.push( '<tr><td role="presentation" ' ); 2550 if ( width ) 2551 styles.push( 'width:' + cssLength( width || '100%' ) ); 2552 if ( heights ) 2553 styles.push( 'height:' + cssLength( heights[i] ) ); 2554 else if ( elementDefinition && elementDefinition.expand ) 2555 styles.push( 'height:' + Math.floor( 100 / childHtmlList.length ) + '%' ); 2556 if ( elementDefinition && elementDefinition.padding != undefined ) 2557 styles.push( 'padding:' + cssLength( elementDefinition.padding ) ); 2558 // In IE Quirks alignment has to be done on table cells. (#7324) 2559 if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && children[ i ].align ) 2560 styles.push( 'text-align:' + children[ i ].align ); 2561 if ( styles.length > 0 ) 2562 html.push( 'style="', styles.join( '; ' ), '" ' ); 2563 html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[i], '</td></tr>' ); 2564 } 2565 html.push( '</tbody></table>' ); 2566 return html.join( '' ); 2567 }; 2568 CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML ); 2569 } 2570 }; 2571 })(); 2572 2573 CKEDITOR.ui.dialog.uiElement.prototype = 2574 { 2575 /** 2576 * Gets the root DOM element of this dialog UI object. 2577 * @returns {CKEDITOR.dom.element} Root DOM element of UI object. 2578 * @example 2579 * uiElement.getElement().hide(); 2580 */ 2581 getElement : function() 2582 { 2583 return CKEDITOR.document.getById( this.domId ); 2584 }, 2585 2586 /** 2587 * Gets the DOM element that the user inputs values. 2588 * This function is used by setValue(), getValue() and focus(). It should 2589 * be overrided in child classes where the input element isn't the root 2590 * element. 2591 * @returns {CKEDITOR.dom.element} The element where the user input values. 2592 * @example 2593 * var rawValue = textInput.getInputElement().$.value; 2594 */ 2595 getInputElement : function() 2596 { 2597 return this.getElement(); 2598 }, 2599 2600 /** 2601 * Gets the parent dialog object containing this UI element. 2602 * @returns {CKEDITOR.dialog} Parent dialog object. 2603 * @example 2604 * var dialog = uiElement.getDialog(); 2605 */ 2606 getDialog : function() 2607 { 2608 return this._.dialog; 2609 }, 2610 2611 /** 2612 * Sets the value of this dialog UI object. 2613 * @param {Object} value The new value. 2614 * @param {Boolean} noChangeEvent Internal commit, to supress 'change' event on this element. 2615 * @returns {CKEDITOR.dialog.uiElement} The current UI element. 2616 * @example 2617 * uiElement.setValue( 'Dingo' ); 2618 */ 2619 setValue : function( value, noChangeEvent ) 2620 { 2621 this.getInputElement().setValue( value ); 2622 !noChangeEvent && this.fire( 'change', { value : value } ); 2623 return this; 2624 }, 2625 2626 /** 2627 * Gets the current value of this dialog UI object. 2628 * @returns {Object} The current value. 2629 * @example 2630 * var myValue = uiElement.getValue(); 2631 */ 2632 getValue : function() 2633 { 2634 return this.getInputElement().getValue(); 2635 }, 2636 2637 /** 2638 * Tells whether the UI object's value has changed. 2639 * @returns {Boolean} true if changed, false if not changed. 2640 * @example 2641 * if ( uiElement.isChanged() ) 2642 * confirm( 'Value changed! Continue?' ); 2643 */ 2644 isChanged : function() 2645 { 2646 // Override in input classes. 2647 return false; 2648 }, 2649 2650 /** 2651 * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods. 2652 * @returns {CKEDITOR.dialog.uiElement} The current UI element. 2653 * @example 2654 * focus : function() 2655 * { 2656 * this.selectParentTab(); 2657 * // do something else. 2658 * } 2659 */ 2660 selectParentTab : function() 2661 { 2662 var element = this.getInputElement(), 2663 cursor = element, 2664 tabId; 2665 while ( ( cursor = cursor.getParent() ) && cursor.$.className.search( 'cke_dialog_page_contents' ) == -1 ) 2666 { /*jsl:pass*/ } 2667 2668 // Some widgets don't have parent tabs (e.g. OK and Cancel buttons). 2669 if ( !cursor ) 2670 return this; 2671 2672 tabId = cursor.getAttribute( 'name' ); 2673 // Avoid duplicate select. 2674 if ( this._.dialog._.currentTabId != tabId ) 2675 this._.dialog.selectPage( tabId ); 2676 return this; 2677 }, 2678 2679 /** 2680 * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page. 2681 * @returns {CKEDITOR.dialog.uiElement} The current UI element. 2682 * @example 2683 * uiElement.focus(); 2684 */ 2685 focus : function() 2686 { 2687 this.selectParentTab().getInputElement().focus(); 2688 return this; 2689 }, 2690 2691 /** 2692 * Registers the on* event handlers defined in the element definition. 2693 * The default behavior of this function is: 2694 * <ol> 2695 * <li> 2696 * If the on* event is defined in the class's eventProcesors list, 2697 * then the registration is delegated to the corresponding function 2698 * in the eventProcessors list. 2699 * </li> 2700 * <li> 2701 * If the on* event is not defined in the eventProcessors list, then 2702 * register the event handler under the corresponding DOM event of 2703 * the UI element's input DOM element (as defined by the return value 2704 * of {@link CKEDITOR.ui.dialog.uiElement#getInputElement}). 2705 * </li> 2706 * </ol> 2707 * This function is only called at UI element instantiation, but can 2708 * be overridded in child classes if they require more flexibility. 2709 * @param {CKEDITOR.dialog.definition.uiElement} definition The UI element 2710 * definition. 2711 * @returns {CKEDITOR.dialog.uiElement} The current UI element. 2712 * @example 2713 */ 2714 registerEvents : function( definition ) 2715 { 2716 var regex = /^on([A-Z]\w+)/, 2717 match; 2718 2719 var registerDomEvent = function( uiElement, dialog, eventName, func ) 2720 { 2721 dialog.on( 'load', function() 2722 { 2723 uiElement.getInputElement().on( eventName, func, uiElement ); 2724 }); 2725 }; 2726 2727 for ( var i in definition ) 2728 { 2729 if ( !( match = i.match( regex ) ) ) 2730 continue; 2731 if ( this.eventProcessors[i] ) 2732 this.eventProcessors[i].call( this, this._.dialog, definition[i] ); 2733 else 2734 registerDomEvent( this, this._.dialog, match[1].toLowerCase(), definition[i] ); 2735 } 2736 2737 return this; 2738 }, 2739 2740 /** 2741 * The event processor list used by 2742 * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element 2743 * instantiation. The default list defines three on* events: 2744 * <ol> 2745 * <li>onLoad - Called when the element's parent dialog opens for the 2746 * first time</li> 2747 * <li>onShow - Called whenever the element's parent dialog opens.</li> 2748 * <li>onHide - Called whenever the element's parent dialog closes.</li> 2749 * </ol> 2750 * @field 2751 * @type Object 2752 * @example 2753 * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick 2754 * // handlers in the UI element's definitions. 2755 * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {}, 2756 * CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, 2757 * { onClick : function( dialog, func ) { this.on( 'click', func ); } }, 2758 * true ); 2759 */ 2760 eventProcessors : 2761 { 2762 onLoad : function( dialog, func ) 2763 { 2764 dialog.on( 'load', func, this ); 2765 }, 2766 2767 onShow : function( dialog, func ) 2768 { 2769 dialog.on( 'show', func, this ); 2770 }, 2771 2772 onHide : function( dialog, func ) 2773 { 2774 dialog.on( 'hide', func, this ); 2775 } 2776 }, 2777 2778 /** 2779 * The default handler for a UI element's access key down event, which 2780 * tries to put focus to the UI element.<br /> 2781 * Can be overridded in child classes for more sophisticaed behavior. 2782 * @param {CKEDITOR.dialog} dialog The parent dialog object. 2783 * @param {String} key The key combination pressed. Since access keys 2784 * are defined to always include the CTRL key, its value should always 2785 * include a 'CTRL+' prefix. 2786 * @example 2787 */ 2788 accessKeyDown : function( dialog, key ) 2789 { 2790 this.focus(); 2791 }, 2792 2793 /** 2794 * The default handler for a UI element's access key up event, which 2795 * does nothing.<br /> 2796 * Can be overridded in child classes for more sophisticated behavior. 2797 * @param {CKEDITOR.dialog} dialog The parent dialog object. 2798 * @param {String} key The key combination pressed. Since access keys 2799 * are defined to always include the CTRL key, its value should always 2800 * include a 'CTRL+' prefix. 2801 * @example 2802 */ 2803 accessKeyUp : function( dialog, key ) 2804 { 2805 }, 2806 2807 /** 2808 * Disables a UI element. 2809 * @example 2810 */ 2811 disable : function() 2812 { 2813 var element = this.getElement(), 2814 input = this.getInputElement(); 2815 input.setAttribute( 'disabled', 'true' ); 2816 element.addClass( 'cke_disabled' ); 2817 }, 2818 2819 /** 2820 * Enables a UI element. 2821 * @example 2822 */ 2823 enable : function() 2824 { 2825 var element = this.getElement(), 2826 input = this.getInputElement(); 2827 input.removeAttribute( 'disabled' ); 2828 element.removeClass( 'cke_disabled' ); 2829 }, 2830 2831 /** 2832 * Determines whether an UI element is enabled or not. 2833 * @returns {Boolean} Whether the UI element is enabled. 2834 * @example 2835 */ 2836 isEnabled : function() 2837 { 2838 return !this.getElement().hasClass( 'cke_disabled' ); 2839 }, 2840 2841 /** 2842 * Determines whether an UI element is visible or not. 2843 * @returns {Boolean} Whether the UI element is visible. 2844 * @example 2845 */ 2846 isVisible : function() 2847 { 2848 return this.getInputElement().isVisible(); 2849 }, 2850 2851 /** 2852 * Determines whether an UI element is focus-able or not. 2853 * Focus-able is defined as being both visible and enabled. 2854 * @returns {Boolean} Whether the UI element can be focused. 2855 * @example 2856 */ 2857 isFocusable : function() 2858 { 2859 if ( !this.isEnabled() || !this.isVisible() ) 2860 return false; 2861 return true; 2862 } 2863 }; 2864 2865 CKEDITOR.ui.dialog.hbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, 2866 /** 2867 * @lends CKEDITOR.ui.dialog.hbox.prototype 2868 */ 2869 { 2870 /** 2871 * Gets a child UI element inside this container. 2872 * @param {Array|Number} indices An array or a single number to indicate the child's 2873 * position in the container's descendant tree. Omit to get all the children in an array. 2874 * @returns {Array|CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container 2875 * if no argument given, or the specified UI element if indices is given. 2876 * @example 2877 * var checkbox = hbox.getChild( [0,1] ); 2878 * checkbox.setValue( true ); 2879 */ 2880 getChild : function( indices ) 2881 { 2882 // If no arguments, return a clone of the children array. 2883 if ( arguments.length < 1 ) 2884 return this._.children.concat(); 2885 2886 // If indices isn't array, make it one. 2887 if ( !indices.splice ) 2888 indices = [ indices ]; 2889 2890 // Retrieve the child element according to tree position. 2891 if ( indices.length < 2 ) 2892 return this._.children[ indices[0] ]; 2893 else 2894 return ( this._.children[ indices[0] ] && this._.children[ indices[0] ].getChild ) ? 2895 this._.children[ indices[0] ].getChild( indices.slice( 1, indices.length ) ) : 2896 null; 2897 } 2898 }, true ); 2899 2900 CKEDITOR.ui.dialog.vbox.prototype = new CKEDITOR.ui.dialog.hbox(); 2901 2902 2903 2904 (function() 2905 { 2906 var commonBuilder = { 2907 build : function( dialog, elementDefinition, output ) 2908 { 2909 var children = elementDefinition.children, 2910 child, 2911 childHtmlList = [], 2912 childObjList = []; 2913 for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ ) 2914 { 2915 var childHtml = []; 2916 childHtmlList.push( childHtml ); 2917 childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); 2918 } 2919 return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, childObjList, childHtmlList, output, elementDefinition ); 2920 } 2921 }; 2922 2923 CKEDITOR.dialog.addUIElement( 'hbox', commonBuilder ); 2924 CKEDITOR.dialog.addUIElement( 'vbox', commonBuilder ); 2925 })(); 2926 2927 /** 2928 * Generic dialog command. It opens a specific dialog when executed. 2929 * @constructor 2930 * @augments CKEDITOR.commandDefinition 2931 * @param {string} dialogName The name of the dialog to open when executing 2932 * this command. 2933 * @example 2934 * // Register the "link" command, which opens the "link" dialog. 2935 * editor.addCommand( 'link', <b>new CKEDITOR.dialogCommand( 'link' )</b> ); 2936 */ 2937 CKEDITOR.dialogCommand = function( dialogName ) 2938 { 2939 this.dialogName = dialogName; 2940 }; 2941 2942 CKEDITOR.dialogCommand.prototype = 2943 { 2944 /** @ignore */ 2945 exec : function( editor ) 2946 { 2947 // Special treatment for Opera. (#8031) 2948 CKEDITOR.env.opera ? 2949 CKEDITOR.tools.setTimeout( function() { editor.openDialog( this.dialogName ); }, 0, this ) 2950 : editor.openDialog( this.dialogName ); 2951 }, 2952 2953 // Dialog commands just open a dialog ui, thus require no undo logic, 2954 // undo support should dedicate to specific dialog implementation. 2955 canUndo: false, 2956 2957 editorFocus : CKEDITOR.env.ie || CKEDITOR.env.webkit 2958 }; 2959 2960 (function() 2961 { 2962 var notEmptyRegex = /^([a]|[^a])+$/, 2963 integerRegex = /^\d*$/, 2964 numberRegex = /^\d*(?:\.\d+)?$/, 2965 htmlLengthRegex = /^(((\d*(\.\d+))|(\d*))(px|\%)?)?$/, 2966 cssLengthRegex = /^(((\d*(\.\d+))|(\d*))(px|em|ex|in|cm|mm|pt|pc|\%)?)?$/i, 2967 inlineStyleRegex = /^(\s*[\w-]+\s*:\s*[^:;]+(?:;|$))*$/; 2968 2969 CKEDITOR.VALIDATE_OR = 1; 2970 CKEDITOR.VALIDATE_AND = 2; 2971 2972 CKEDITOR.dialog.validate = 2973 { 2974 functions : function() 2975 { 2976 var args = arguments; 2977 return function() 2978 { 2979 /** 2980 * It's important for validate functions to be able to accept the value 2981 * as argument in addition to this.getValue(), so that it is possible to 2982 * combine validate functions together to make more sophisticated 2983 * validators. 2984 */ 2985 var value = this && this.getValue ? this.getValue() : args[ 0 ]; 2986 2987 var msg = undefined, 2988 relation = CKEDITOR.VALIDATE_AND, 2989 functions = [], i; 2990 2991 for ( i = 0 ; i < args.length ; i++ ) 2992 { 2993 if ( typeof( args[i] ) == 'function' ) 2994 functions.push( args[i] ); 2995 else 2996 break; 2997 } 2998 2999 if ( i < args.length && typeof( args[i] ) == 'string' ) 3000 { 3001 msg = args[i]; 3002 i++; 3003 } 3004 3005 if ( i < args.length && typeof( args[i]) == 'number' ) 3006 relation = args[i]; 3007 3008 var passed = ( relation == CKEDITOR.VALIDATE_AND ? true : false ); 3009 for ( i = 0 ; i < functions.length ; i++ ) 3010 { 3011 if ( relation == CKEDITOR.VALIDATE_AND ) 3012 passed = passed && functions[i]( value ); 3013 else 3014 passed = passed || functions[i]( value ); 3015 } 3016 3017 return !passed ? msg : true; 3018 }; 3019 }, 3020 3021 regex : function( regex, msg ) 3022 { 3023 /* 3024 * Can be greatly shortened by deriving from functions validator if code size 3025 * turns out to be more important than performance. 3026 */ 3027 return function() 3028 { 3029 var value = this && this.getValue ? this.getValue() : arguments[0]; 3030 return !regex.test( value ) ? msg : true; 3031 }; 3032 }, 3033 3034 notEmpty : function( msg ) 3035 { 3036 return this.regex( notEmptyRegex, msg ); 3037 }, 3038 3039 integer : function( msg ) 3040 { 3041 return this.regex( integerRegex, msg ); 3042 }, 3043 3044 'number' : function( msg ) 3045 { 3046 return this.regex( numberRegex, msg ); 3047 }, 3048 3049 'cssLength' : function( msg ) 3050 { 3051 return this.functions( function( val ){ return cssLengthRegex.test( CKEDITOR.tools.trim( val ) ); }, msg ); 3052 }, 3053 3054 'htmlLength' : function( msg ) 3055 { 3056 return this.functions( function( val ){ return htmlLengthRegex.test( CKEDITOR.tools.trim( val ) ); }, msg ); 3057 }, 3058 3059 'inlineStyle' : function( msg ) 3060 { 3061 return this.functions( function( val ){ return inlineStyleRegex.test( CKEDITOR.tools.trim( val ) ); }, msg ); 3062 }, 3063 3064 equals : function( value, msg ) 3065 { 3066 return this.functions( function( val ){ return val == value; }, msg ); 3067 }, 3068 3069 notEqual : function( value, msg ) 3070 { 3071 return this.functions( function( val ){ return val != value; }, msg ); 3072 } 3073 }; 3074 3075 CKEDITOR.on( 'instanceDestroyed', function( evt ) 3076 { 3077 // Remove dialog cover on last instance destroy. 3078 if ( CKEDITOR.tools.isEmpty( CKEDITOR.instances ) ) 3079 { 3080 var currentTopDialog; 3081 while ( ( currentTopDialog = CKEDITOR.dialog._.currentTop ) ) 3082 currentTopDialog.hide(); 3083 removeCovers(); 3084 } 3085 3086 var dialogs = evt.editor._.storedDialogs; 3087 for ( var name in dialogs ) 3088 dialogs[ name ].destroy(); 3089 3090 }); 3091 3092 })(); 3093 3094 // Extend the CKEDITOR.editor class with dialog specific functions. 3095 CKEDITOR.tools.extend( CKEDITOR.editor.prototype, 3096 /** @lends CKEDITOR.editor.prototype */ 3097 { 3098 /** 3099 * Loads and opens a registered dialog. 3100 * @param {String} dialogName The registered name of the dialog. 3101 * @param {Function} callback The function to be invoked after dialog instance created. 3102 * @see CKEDITOR.dialog.add 3103 * @example 3104 * CKEDITOR.instances.editor1.openDialog( 'smiley' ); 3105 * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed. null if the dialog name is not registered. 3106 */ 3107 openDialog : function( dialogName, callback ) 3108 { 3109 if ( this.mode == 'wysiwyg' && CKEDITOR.env.ie ) 3110 { 3111 var selection = this.getSelection(); 3112 selection && selection.lock(); 3113 } 3114 3115 var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], 3116 dialogSkin = this.skin.dialog; 3117 3118 if ( CKEDITOR.dialog._.currentTop === null ) 3119 showCover( this ); 3120 3121 // If the dialogDefinition is already loaded, open it immediately. 3122 if ( typeof dialogDefinitions == 'function' && dialogSkin._isLoaded ) 3123 { 3124 var storedDialogs = this._.storedDialogs || 3125 ( this._.storedDialogs = {} ); 3126 3127 var dialog = storedDialogs[ dialogName ] || 3128 ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) ); 3129 3130 callback && callback.call( dialog, dialog ); 3131 dialog.show(); 3132 3133 return dialog; 3134 } 3135 else if ( dialogDefinitions == 'failed' ) 3136 { 3137 hideCover(); 3138 throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName + '" failed when loading definition.' ); 3139 } 3140 3141 var me = this; 3142 3143 function onDialogFileLoaded( success ) 3144 { 3145 var dialogDefinition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], 3146 skin = me.skin.dialog; 3147 3148 // Check if both skin part and definition is loaded. 3149 if ( !skin._isLoaded || loadDefinition && typeof success == 'undefined' ) 3150 return; 3151 3152 // In case of plugin error, mark it as loading failed. 3153 if ( typeof dialogDefinition != 'function' ) 3154 CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed'; 3155 3156 me.openDialog( dialogName, callback ); 3157 } 3158 3159 if ( typeof dialogDefinitions == 'string' ) 3160 { 3161 var loadDefinition = 1; 3162 CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), onDialogFileLoaded, null, 0, 1 ); 3163 } 3164 3165 CKEDITOR.skins.load( this, 'dialog', onDialogFileLoaded ); 3166 3167 return null; 3168 } 3169 }); 3170 })(); 3171 3172 CKEDITOR.plugins.add( 'dialog', 3173 { 3174 requires : [ 'dialogui' ] 3175 }); 3176 3177 // Dialog related configurations. 3178 3179 /** 3180 * The color of the dialog background cover. It should be a valid CSS color 3181 * string. 3182 * @name CKEDITOR.config.dialog_backgroundCoverColor 3183 * @type String 3184 * @default 'white' 3185 * @example 3186 * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)'; 3187 */ 3188 3189 /** 3190 * The opacity of the dialog background cover. It should be a number within the 3191 * range [0.0, 1.0]. 3192 * @name CKEDITOR.config.dialog_backgroundCoverOpacity 3193 * @type Number 3194 * @default 0.5 3195 * @example 3196 * config.dialog_backgroundCoverOpacity = 0.7; 3197 */ 3198 3199 /** 3200 * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened. 3201 * @name CKEDITOR.config.dialog_startupFocusTab 3202 * @type Boolean 3203 * @default false 3204 * @example 3205 * config.dialog_startupFocusTab = true; 3206 */ 3207 3208 /** 3209 * The distance of magnetic borders used in moving and resizing dialogs, 3210 * measured in pixels. 3211 * @name CKEDITOR.config.dialog_magnetDistance 3212 * @type Number 3213 * @default 20 3214 * @example 3215 * config.dialog_magnetDistance = 30; 3216 */ 3217 3218 /** 3219 * The guideline to follow when generating the dialog buttons. There are 3 possible options: 3220 * <ul> 3221 * <li>'OS' - the buttons will be displayed in the default order of the user's OS;</li> 3222 * <li>'ltr' - for Left-To-Right order;</li> 3223 * <li>'rtl' - for Right-To-Left order.</li> 3224 * </ul> 3225 * @name CKEDITOR.config.dialog_buttonsOrder 3226 * @type String 3227 * @default 'OS' 3228 * @since 3.5 3229 * @example 3230 * config.dialog_buttonsOrder = 'rtl'; 3231 */ 3232 3233 /** 3234 * The dialog contents to removed. It's a string composed by dialog name and tab name with a colon between them. 3235 * Separate each pair with semicolon (see example). 3236 * <b>Note: All names are case-sensitive.</b> 3237 * <b>Note: Be cautious when specifying dialog tabs that are mandatory, like "info", dialog functionality might be broken because of this!</b> 3238 * @name CKEDITOR.config.removeDialogTabs 3239 * @type String 3240 * @since 3.5 3241 * @default '' 3242 * @example 3243 * config.removeDialogTabs = 'flash:advanced;image:Link'; 3244 */ 3245 3246 /** 3247 * Fired when a dialog definition is about to be used to create a dialog into 3248 * an editor instance. This event makes it possible to customize the definition 3249 * before creating it. 3250 * <p>Note that this event is called only the first time a specific dialog is 3251 * opened. Successive openings will use the cached dialog, and this event will 3252 * not get fired.</p> 3253 * @name CKEDITOR#dialogDefinition 3254 * @event 3255 * @param {CKEDITOR.dialog.definition} data The dialog defination that 3256 * is being loaded. 3257 * @param {CKEDITOR.editor} editor The editor instance that will use the 3258 * dialog. 3259 */ 3260 3261 /** 3262 * Fired when a tab is going to be selected in a dialog 3263 * @name CKEDITOR.dialog#selectPage 3264 * @event 3265 * @param {String} page The id of the page that it's gonna be selected. 3266 * @param {String} currentPage The id of the current page. 3267 */ 3268 3269 /** 3270 * Fired when the user tries to dismiss a dialog 3271 * @name CKEDITOR.dialog#cancel 3272 * @event 3273 * @param {Boolean} hide Whether the event should proceed or not. 3274 */ 3275 3276 /** 3277 * Fired when the user tries to confirm a dialog 3278 * @name CKEDITOR.dialog#ok 3279 * @event 3280 * @param {Boolean} hide Whether the event should proceed or not. 3281 */ 3282 3283 /** 3284 * Fired when a dialog is shown 3285 * @name CKEDITOR.dialog#show 3286 * @event 3287 */ 3288 3289 /** 3290 * Fired when a dialog is shown 3291 * @name CKEDITOR.editor#dialogShow 3292 * @event 3293 */ 3294 3295 /** 3296 * Fired when a dialog is hidden 3297 * @name CKEDITOR.dialog#hide 3298 * @event 3299 */ 3300 3301 /** 3302 * Fired when a dialog is hidden 3303 * @name CKEDITOR.editor#dialogHide 3304 * @event 3305 */ 3306 3307 /** 3308 * Fired when a dialog is being resized. The event is fired on 3309 * both the 'CKEDITOR.dialog' object and the dialog instance 3310 * since 3.5.3, previously it's available only in the global object. 3311 * @name CKEDITOR.dialog#resize 3312 * @since 3.5 3313 * @event 3314 * @param {CKEDITOR.dialog} dialog The dialog being resized (if 3315 * it's fired on the dialog itself, this parameter isn't sent). 3316 * @param {String} skin The skin name. 3317 * @param {Number} width The new width. 3318 * @param {Number} height The new height. 3319 */ 3320