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