1 /* 2 Copyright (c) 2003-2009, 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 CKEDITOR.plugins.add( 'dialog', 11 { 12 requires : [ 'dialogui' ] 13 }); 14 15 /** 16 * No resize for this dialog. 17 * @constant 18 */ 19 CKEDITOR.DIALOG_RESIZE_NONE = 0; 20 21 /** 22 * Only allow horizontal resizing for this dialog, disable vertical resizing. 23 * @constant 24 */ 25 CKEDITOR.DIALOG_RESIZE_WIDTH = 1; 26 27 /** 28 * Only allow vertical resizing for this dialog, disable horizontal resizing. 29 * @constant 30 */ 31 CKEDITOR.DIALOG_RESIZE_HEIGHT = 2; 32 33 /* 34 * Allow the dialog to be resized in both directions. 35 * @constant 36 */ 37 CKEDITOR.DIALOG_RESIZE_BOTH = 3; 38 39 (function() 40 { 41 function isTabVisible( tabId ) 42 { 43 return !!this._.tabs[ tabId ][ 0 ].$.offsetHeight; 44 } 45 46 function getPreviousVisibleTab() 47 { 48 var tabId = this._.currentTabId, 49 length = this._.tabIdList.length, 50 tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ) + length; 51 52 for ( var i = tabIndex - 1 ; i > tabIndex - length ; i-- ) 53 { 54 if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) 55 return this._.tabIdList[ i % length ]; 56 } 57 58 return null; 59 } 60 61 function getNextVisibleTab() 62 { 63 var tabId = this._.currentTabId, 64 length = this._.tabIdList.length, 65 tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ); 66 67 for ( var i = tabIndex + 1 ; i < tabIndex + length ; i++ ) 68 { 69 if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) 70 return this._.tabIdList[ i % length ]; 71 } 72 73 return null; 74 } 75 76 /** 77 * This is the base class for runtime dialog objects. An instance of this 78 * class represents a single named dialog for a single editor instance. 79 * @param {Object} editor The editor which created the dialog. 80 * @param {String} dialogName The dialog's registered name. 81 * @constructor 82 * @example 83 * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' ); 84 */ 85 CKEDITOR.dialog = function( editor, dialogName ) 86 { 87 // Load the dialog definition. 88 var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ]; 89 if ( !definition ) 90 { 91 console.log( 'Error: The dialog "' + dialogName + '" is not defined.' ); 92 return; 93 } 94 95 // Completes the definition with the default values. 96 definition = CKEDITOR.tools.extend( definition( editor ), defaultDialogDefinition ); 97 98 // Create a complex definition object, extending it with the API 99 // functions. 100 definition = new definitionObject( this, definition ); 101 102 // Fire the "dialogDefinition" event, making it possible to customize 103 // the dialog definition. 104 this.definition = definition = CKEDITOR.fire( 'dialogDefinition', 105 { 106 name : dialogName, 107 definition : definition 108 } 109 , editor ).definition; 110 111 var themeBuilt = editor.theme.buildDialog( editor ); 112 113 // Initialize some basic parameters. 114 this._ = 115 { 116 editor : editor, 117 element : themeBuilt.element, 118 name : dialogName, 119 size : { width : 0, height : 0 }, 120 contents : {}, 121 buttons : {}, 122 accessKeyMap : {}, 123 124 // Initialize the tab and page map. 125 tabs : {}, 126 tabIdList : [], 127 currentTabId : null, 128 currentTabIndex : null, 129 pageCount : 0, 130 lastTab : null, 131 tabBarMode : false, 132 133 // Initialize the tab order array for input widgets. 134 focusList : [], 135 currentFocusIndex : 0, 136 hasFocus : false 137 }; 138 139 /** 140 * An associative map of elements in the dialog. It has the following members: 141 * <ul> 142 * <li>tl - top left corner.</li> 143 * <li>tl_resize - resize handle at the top left corner.</li> 144 * <li>t - top side.</li> 145 * <li>t_resize - resize handle at the top.</li> 146 * <li>tr - top right corner.</li> 147 * <li>tr_resize - resize handle at the top right.</li> 148 * <li>l - left side.</li> 149 * <li>l_resize - resize handle at the left side.</li> 150 * <li>c - center area.</li> 151 * <li>r - right side.</li> 152 * <li>r_resize - resize handle at the right side.</li> 153 * <li>bl - bottom left corner.</li> 154 * <li>bl_resize - resize handle at the bottom left.</li> 155 * <li>b - bottom side.</li> 156 * <li>b_resize - resize handle at the bottom.</li> 157 * <li>br - bottom right corner.</li> 158 * <li>br_resize - resize handle at the bottom right.</li> 159 * <li>title - title area.</li> 160 * <li>close - close button.</li> 161 * <li>tabs - tabs area.</li> 162 * <li>contents - the content page area.</li> 163 * <li>footer - the footer area.</li> 164 * </ul> 165 * @type Object 166 * @field 167 */ 168 this.parts = { 169 'tl' : [0,0], 170 'tl_resize' : [0,0,0], 171 't' : [0,1], 172 't_resize' : [0,1,0], 173 'tr' : [0,2], 174 'tr_resize' : [0,2,0], 175 'l' : [1,0], 176 'l_resize' : [1,0,0], 177 'c' : [1,1], 178 'r' : [1,2], 179 'r_resize' : [1,2,0], 180 'bl' : [2,0], 181 'bl_resize' : [2,0,0], 182 'b' : [2,1], 183 'b_resize' : [2,1,0], 184 'br' : [2,2], 185 'br_resize' : [2,2,0], 186 'title' : [1,1,0], 187 'close' : [1,1,0,0], 188 'tabs' : [1,1,1,0,0], 189 'tabs_table' : [1,1,1], 190 'contents' : [1,1,2], 191 'footer' : [1,1,3] 192 }; 193 194 // Initialize the parts map. 195 var element = this._.element.getFirst(); 196 for ( var i in this.parts ) 197 this.parts[i] = element.getChild( this.parts[i] ); 198 199 // Call the CKEDITOR.event constructor to initialize this instance. 200 CKEDITOR.event.call( this ); 201 202 // Initialize load, show, hide, ok and cancel events. 203 if ( definition.onLoad ) 204 this.on( 'load', definition.onLoad ); 205 206 if ( definition.onShow ) 207 this.on( 'show', definition.onShow ); 208 209 if ( definition.onHide ) 210 this.on( 'hide', definition.onHide ); 211 212 if ( definition.onOk ) 213 { 214 this.on( 'ok', function( evt ) 215 { 216 if ( definition.onOk.call( this, evt ) === false ) 217 evt.data.hide = false; 218 }); 219 } 220 221 if ( definition.onCancel ) 222 { 223 this.on( 'cancel', function( evt ) 224 { 225 if ( definition.onCancel.call( this, evt ) === false ) 226 evt.data.hide = false; 227 }); 228 } 229 230 var me = this; 231 232 // Iterates over all items inside all content in the dialog, calling a 233 // function for each of them. 234 var iterContents = function( func ) 235 { 236 var contents = me._.contents, 237 stop = false; 238 239 for ( var i in contents ) 240 { 241 for ( var j in contents[i] ) 242 { 243 stop = func.call( this, contents[i][j] ); 244 if ( stop ) 245 return; 246 } 247 } 248 }; 249 250 this.on( 'ok', function( evt ) 251 { 252 iterContents( function( item ) 253 { 254 if ( item.validate ) 255 { 256 var isValid = item.validate( this ); 257 258 if ( typeof isValid == 'string' ) 259 { 260 alert( isValid ); 261 isValid = false; 262 } 263 264 if ( isValid === false ) 265 { 266 if ( item.select ) 267 item.select(); 268 else 269 item.focus(); 270 271 evt.data.hide = false; 272 evt.stop(); 273 return true; 274 } 275 } 276 }); 277 }, this, null, 0 ); 278 279 this.on( 'cancel', function( evt ) 280 { 281 iterContents( function( item ) 282 { 283 if ( item.isChanged() ) 284 { 285 if ( !confirm( editor.lang.common.confirmCancel ) ) 286 evt.data.hide = false; 287 return true; 288 } 289 }); 290 }, this, null, 0 ); 291 292 this.parts.close.on( 'click', function( evt ) 293 { 294 if ( this.fire( 'cancel', { hide : true } ).hide !== false ) 295 this.hide(); 296 }, this ); 297 298 function changeFocus( forward ) 299 { 300 var focusList = me._.focusList, 301 offset = forward ? 1 : -1; 302 if ( focusList.length < 1 ) 303 return; 304 305 var currentIndex = ( me._.currentFocusIndex + offset + focusList.length ) % focusList.length; 306 while ( !focusList[ currentIndex ].isFocusable() ) 307 { 308 currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length; 309 if ( currentIndex == me._.currentFocusIndex ) 310 break; 311 } 312 focusList[ currentIndex ].focus(); 313 } 314 315 function focusKeydownHandler( evt ) 316 { 317 // If I'm not the top dialog, ignore. 318 if ( me != CKEDITOR.dialog._.currentTop ) 319 return; 320 321 var keystroke = evt.data.getKeystroke(), 322 processed = false; 323 if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 ) 324 { 325 var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 ); 326 327 // Handling Tab and Shift-Tab. 328 if ( me._.tabBarMode ) 329 { 330 // Change tabs. 331 var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ); 332 me.selectPage( nextId ); 333 me._.tabs[ nextId ][ 0 ].getFirst().focus(); 334 } 335 else 336 { 337 // Change the focus of inputs. 338 changeFocus( !shiftPressed ); 339 } 340 341 processed = true; 342 } 343 else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode ) 344 { 345 // Alt-F10 puts focus into the current tab item in the tab bar. 346 me._.tabBarMode = true; 347 me._.tabs[ me._.currentTabId ][ 0 ].getFirst().focus(); 348 processed = true; 349 } 350 else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode ) 351 { 352 // Arrow keys - used for changing tabs. 353 var nextId = ( keystroke == 37 ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) ); 354 me.selectPage( nextId ); 355 me._.tabs[ nextId ][ 0 ].getFirst().focus(); 356 processed = true; 357 } 358 359 if ( processed ) 360 { 361 evt.stop(); 362 evt.data.preventDefault(); 363 } 364 } 365 366 // Add the dialog keyboard handlers. 367 this.on( 'show', function() 368 { 369 CKEDITOR.document.on( 'keydown', focusKeydownHandler, this, null, 0 ); 370 if ( CKEDITOR.env.ie6Compat ) 371 { 372 var coverDoc = new CKEDITOR.dom.document( frames( 'cke_dialog_background_iframe' ).document ); 373 coverDoc.on( 'keydown', focusKeydownHandler, this, null, 0 ); 374 } 375 } ); 376 this.on( 'hide', function() 377 { 378 CKEDITOR.document.removeListener( 'keydown', focusKeydownHandler ); 379 } ); 380 381 // Auto-focus logic in dialog. 382 this.on( 'show', function() 383 { 384 if ( !this._.hasFocus ) 385 { 386 this._.currentFocusIndex = -1; 387 changeFocus( true ); 388 } 389 }, this, null, 0xffffffff ); 390 391 // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661). 392 // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken. 393 if ( CKEDITOR.env.ie6Compat ) 394 { 395 this.on( 'load', function( evt ) 396 { 397 var outer = this.getElement(), 398 inner = outer.getFirst(); 399 inner.remove(); 400 inner.appendTo( outer ); 401 }, this ); 402 } 403 404 initDragAndDrop( this ); 405 initResizeHandles( this ); 406 407 // Insert the title. 408 ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title ); 409 410 // Insert the tabs and contents. 411 for ( i = 0 ; i < definition.contents.length ; i++ ) 412 this.addPage( definition.contents[i] ); 413 414 var tabRegex = /cke_dialog_tab(\s|$|_)/, 415 tabOuterRegex = /cke_dialog_tab(\s|$)/; 416 this.parts['tabs'].on( 'click', function( evt ) 417 { 418 var target = evt.data.getTarget(), firstNode = target, id, page; 419 420 // If we aren't inside a tab, bail out. 421 if ( !( tabRegex.test( target.$.className ) || target.getName() == 'a' ) ) 422 return; 423 424 // Find the outer <td> container of the tab. 425 while ( target.getName() != 'td' || !tabOuterRegex.test( target.$.className ) ) 426 { 427 target = target.getParent(); 428 } 429 id = target.$.id.substr( 0, target.$.id.lastIndexOf( '_' ) ); 430 this.selectPage( id ); 431 432 if ( this._.tabBarMode ) 433 { 434 this._.tabBarMode = false; 435 this._.currentFocusIndex = -1; 436 changeFocus( true ); 437 } 438 }, this ); 439 440 // Insert buttons. 441 var buttonsHtml = [], 442 buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this, 443 { 444 type : 'hbox', 445 className : 'cke_dialog_footer_buttons', 446 widths : [], 447 children : definition.buttons 448 }, buttonsHtml ).getChild(); 449 this.parts.footer.setHtml( buttonsHtml.join( '' ) ); 450 451 for ( i = 0 ; i < buttons.length ; i++ ) 452 this._.buttons[ buttons[i].id ] = buttons[i]; 453 454 // Insert dummy text box for grabbing focus away from the editing area. 455 this._.dummyText = CKEDITOR.dom.element.createFromHtml( '<input type="text" style="position: absolute; left: -100000px; top: -100000px" />' ); 456 this._.dummyText.appendTo( element ); 457 458 CKEDITOR.skins.load( editor.config.skin, 'dialog' ); 459 }; 460 461 CKEDITOR.dialog.prototype = 462 { 463 /** 464 * Resizes the dialog. 465 * @param {Number} width The width of the dialog in pixels. 466 * @param {Number} height The height of the dialog in pixels. 467 * @function 468 * @example 469 * dialogObj.resize( 800, 640 ); 470 */ 471 resize : (function() 472 { 473 return function( width, height ) 474 { 475 if ( this._.size && this._.size.width == width && this._.size.height == height ) 476 return; 477 478 CKEDITOR.dialog.fire( 'resize', 479 { 480 dialog : this, 481 skin : this._.editor.config.skin, 482 width : width, 483 height : height 484 }, this._.editor ); 485 486 this._.size = { width : width, height : height }; 487 }; 488 })(), 489 490 /** 491 * Gets the current size of the dialog in pixels. 492 * @returns {Object} An object with "width" and "height" properties. 493 * @example 494 * var width = dialogObj.getSize().width; 495 */ 496 getSize : function() 497 { 498 return CKEDITOR.tools.extend( {}, this._.size ); 499 }, 500 501 /** 502 * Moves the dialog to an (x, y) coordinate relative to the window. 503 * @function 504 * @param {Number} x The target x-coordinate. 505 * @param {Number} y The target y-coordinate. 506 * @example 507 * dialogObj.move( 10, 40 ); 508 */ 509 move : (function() 510 { 511 var isFixed; 512 return function( x, y ) 513 { 514 // The dialog may be fixed positioned or absolute positioned. Ask the 515 // browser what is the current situation first. 516 if ( isFixed === undefined ) 517 isFixed = this._.element.getFirst().getComputedStyle( 'position' ) == 'fixed'; 518 519 if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y ) 520 return; 521 522 // Save the current position. 523 this._.position = { x : x, y : y }; 524 525 // If not fixed positioned, add scroll position to the coordinates. 526 if ( !isFixed ) 527 { 528 var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition(); 529 x += scrollPosition.x; 530 y += scrollPosition.y; 531 } 532 533 this._.element.getFirst().setStyles( 534 { 535 'left' : x + 'px', 536 'top' : y + 'px' 537 }); 538 }; 539 })(), 540 541 /** 542 * Gets the dialog's position in the window. 543 * @returns {Object} An object with "x" and "y" properties. 544 * @example 545 * var dialogX = dialogObj.getPosition().x; 546 */ 547 getPosition : function(){ return CKEDITOR.tools.extend( {}, this._.position ); }, 548 549 /** 550 * Shows the dialog box. 551 * @example 552 * dialogObj.show(); 553 */ 554 show : function() 555 { 556 // Insert the dialog's element to the root document. 557 var element = this._.element; 558 var definition = this.definition; 559 if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) 560 element.appendTo( CKEDITOR.document.getBody() ); 561 else 562 return; 563 564 // First, set the dialog to an appropriate size. 565 this.resize( definition.minWidth, definition.minHeight ); 566 567 // Rearrange the dialog to the middle of the window. 568 var viewSize = CKEDITOR.document.getWindow().getViewPaneSize(); 569 this.move( ( viewSize.width - this._.size.width ) / 2, ( viewSize.height - this._.size.height ) / 2 ); 570 571 // Select the first tab by default. 572 this.selectPage( this.definition.contents[0].id ); 573 574 // Reset all inputs back to their default value. 575 this.reset(); 576 577 // Set z-index. 578 if ( CKEDITOR.dialog._.currentZIndex === null ) 579 CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex; 580 this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 ); 581 582 // Maintain the dialog ordering and dialog cover. 583 // Also register key handlers if first dialog. 584 if ( CKEDITOR.dialog._.currentTop === null ) 585 { 586 CKEDITOR.dialog._.currentTop = this; 587 this._.parentDialog = null; 588 addCover( this._.editor ); 589 590 CKEDITOR.document.on( 'keydown', accessKeyDownHandler ); 591 CKEDITOR.document.on( 'keyup', accessKeyUpHandler ); 592 } 593 else 594 { 595 this._.parentDialog = CKEDITOR.dialog._.currentTop; 596 var parentElement = this._.parentDialog.getElement().getFirst(); 597 parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 ); 598 CKEDITOR.dialog._.currentTop = this; 599 } 600 601 // Register the Esc hotkeys. 602 registerAccessKey( this, this, '\x1b', null, function() 603 { 604 this.getButton( 'cancel' ) && this.getButton( 'cancel' ).click(); 605 } ); 606 607 // Save editor selection and grab the focus. 608 if ( !this._.parentDialog ) 609 this.saveSelection(); 610 this._.dummyText.focus(); 611 this._.dummyText.$.select(); 612 613 // Reset the hasFocus state. 614 this._.hasFocus = false; 615 616 // Execute onLoad for the first show. 617 this.fireOnce( 'load', {} ); 618 this.fire( 'show', {} ); 619 620 // Save the initial values of the dialog. 621 this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); } ); 622 }, 623 624 /** 625 * Executes a function for each UI element. 626 * @param {Function} fn Function to execute for each UI element. 627 * @returns {CKEDITOR.dialog} The current dialog object. 628 */ 629 foreach : function( fn ) 630 { 631 for ( var i in this._.contents ) 632 { 633 for ( var j in this._.contents[i] ) 634 fn( this._.contents[i][j]); 635 } 636 return this; 637 }, 638 639 /** 640 * Resets all input values in the dialog. 641 * @example 642 * dialogObj.reset(); 643 * @returns {CKEDITOR.dialog} The current dialog object. 644 */ 645 reset : (function() 646 { 647 var fn = function( widget ){ if ( widget.reset ) widget.reset(); }; 648 return function(){ this.foreach( fn ); return this; }; 649 })(), 650 651 setupContent : function() 652 { 653 var args = arguments; 654 this.foreach( function( widget ) 655 { 656 if ( widget.setup ) 657 widget.setup.apply( widget, args ); 658 }); 659 }, 660 661 commitContent : function() 662 { 663 var args = arguments; 664 this.foreach( function( widget ) 665 { 666 if ( widget.commit ) 667 widget.commit.apply( widget, args ); 668 }); 669 }, 670 671 /** 672 * Hides the dialog box. 673 * @example 674 * dialogObj.hide(); 675 */ 676 hide : function() 677 { 678 // Remove the dialog's element from the root document. 679 var element = this._.element; 680 if ( !element.getParent() ) 681 return; 682 element.remove(); 683 684 // Unregister all access keys associated with this dialog. 685 unregisterAccessKey( this ); 686 687 // Maintain dialog ordering and remove cover if needed. 688 if ( !this._.parentDialog ) 689 removeCover(); 690 else 691 { 692 var parentElement = this._.parentDialog.getElement().getFirst(); 693 parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) ); 694 } 695 CKEDITOR.dialog._.currentTop = this._.parentDialog; 696 697 // Deduct or clear the z-index. 698 if ( !this._.parentDialog ) 699 { 700 CKEDITOR.dialog._.currentZIndex = null; 701 702 // Remove access key handlers. 703 CKEDITOR.document.removeListener( 'keydown', accessKeyDownHandler ); 704 CKEDITOR.document.removeListener( 'keyup', accessKeyUpHandler ); 705 706 // Restore focus and (if not already restored) selection in the editing area. 707 this.restoreSelection(); 708 this._.editor.focus(); 709 } 710 else 711 CKEDITOR.dialog._.currentZIndex -= 10; 712 713 this.fire( 'hide', {} ); 714 715 // Reset the initial values of the dialog. 716 this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } ); 717 }, 718 719 /** 720 * Adds a tabbed page into the dialog. 721 * @param {Object} contents Content definition. 722 * @example 723 */ 724 addPage : function( contents ) 725 { 726 var pageHtml = [], 727 titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '', 728 elements = contents.elements, 729 vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this, 730 { 731 type : 'vbox', 732 className : 'cke_dialog_page_contents', 733 children : contents.elements, 734 expand : !!contents.expand 735 }, pageHtml ); 736 737 // Create the HTML for the tab and the content block. 738 var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); 739 var tab = CKEDITOR.dom.element.createFromHtml( [ 740 '<table><tbody><tr><td class="cke_dialog_tab">', 741 '<a href="javascript: void(0)"', titleHtml, ' style="display: block; outline: none;" hidefocus="true">', 742 '<table border="0" cellspacing="0" cellpadding="0"><tbody><tr>', 743 '<td class="cke_dialog_tab_left"></td>', 744 '<td class="cke_dialog_tab_center">', 745 CKEDITOR.tools.htmlEncode( contents.label.replace( / /g, '\xa0' ) ), 746 '</td>', 747 '<td class="cke_dialog_tab_right"></td>', 748 '</tr></tbody></table></a></td></tr></tbody></table>' 749 ].join( '' ) ); 750 tab = tab.getChild( [0,0,0] ); 751 752 // First and last tab styles classes. 753 if ( this._.lastTab ) 754 this._.lastTab.removeClass( 'last' ); 755 tab.addClass( this._.pageCount > 0 ? 'last' : 'first' ); 756 757 // If only a single page exist, a different style is used in the central pane. 758 if ( this._.pageCount === 0 ) 759 this.parts.c.addClass( 'single_page' ); 760 else 761 this.parts.c.removeClass( 'single_page' ); 762 763 // Take records for the tabs and elements created. 764 this._.tabs[ contents.id ] = [ tab, page ]; 765 this._.tabIdList.push( contents.id ); 766 this._.pageCount++; 767 this._.lastTab = tab; 768 var contentMap = this._.contents[ contents.id ] = {}, 769 cursor, 770 children = vbox.getChild(); 771 while ( ( cursor = children.shift() ) ) 772 { 773 contentMap[ cursor.id ] = cursor; 774 if ( typeof( cursor.getChild ) == 'function' ) 775 children.push.apply( children, cursor.getChild() ); 776 } 777 778 // Attach the DOM nodes. 779 tab.unselectable(); 780 page.appendTo( this.parts.contents ); 781 tab.insertBefore( this.parts.tabs.getChild( this.parts.tabs.getChildCount() - 1 ) ); 782 tab.setAttribute( 'id', contents.id + '_' + CKEDITOR.tools.getNextNumber() ); 783 page.setAttribute( 'name', contents.id ); 784 785 // Add access key handlers if access key is defined. 786 if ( contents.accessKey ) 787 { 788 registerAccessKey( this, this, 'CTRL+' + contents.accessKey, 789 tabAccessKeyDown, tabAccessKeyUp ); 790 this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id; 791 } 792 }, 793 794 /** 795 * Activates a tab page in the dialog by its id. 796 * @param {String} id The id of the dialog tab to be activated. 797 * @example 798 * dialogObj.selectPage( 'tab_1' ); 799 */ 800 selectPage : function( id ) 801 { 802 // Hide the non-selected tabs and pages. 803 for ( var i in this._.tabs ) 804 { 805 var tab = this._.tabs[i][0], 806 page = this._.tabs[i][1]; 807 if ( i != id ) 808 { 809 tab.removeClass( 'cke_dialog_tab_selected' ); 810 page.hide(); 811 } 812 } 813 814 var selected = this._.tabs[id]; 815 selected[0].addClass( 'cke_dialog_tab_selected' ); 816 selected[1].show(); 817 this._.currentTabId = id; 818 this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id ); 819 }, 820 821 /** 822 * Hides a page's tab away from the dialog. 823 * @param {String} id The page's Id. 824 * @example 825 * dialog.hidePage( 'tab_3' ); 826 */ 827 hidePage : function( id ) 828 { 829 var tab = this._.tabs[id] && this._.tabs[id][0]; 830 if ( !tab ) 831 return; 832 tab.hide(); 833 }, 834 835 /** 836 * Unhides a page's tab. 837 * @param {String} id The page's Id. 838 * @example 839 * dialog.showPage( 'tab_2' ); 840 */ 841 showPage : function( id ) 842 { 843 var tab = this._.tabs[id] && this._.tabs[id][0]; 844 if ( !tab ) 845 return; 846 tab.show(); 847 }, 848 849 /** 850 * Gets the root DOM element of the dialog. 851 * @returns {CKEDITOR.dom.element} The <span> element containing this dialog. 852 * @example 853 * var dialogElement = dialogObj.getElement().getFirst(); 854 * dialogElement.setStyle( 'padding', '5px' ); 855 */ 856 getElement : function() 857 { 858 return this._.element; 859 }, 860 861 /** 862 * Gets a dialog UI element object from a dialog page. 863 * @param {String} pageId id of dialog page. 864 * @param {String} elementId id of UI element. 865 * @example 866 * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element. 867 */ 868 getContentElement : function( pageId, elementId ) 869 { 870 return this._.contents[pageId][elementId]; 871 }, 872 873 /** 874 * Gets the value of a dialog UI element. 875 * @param {String} pageId id of dialog page. 876 * @param {String} elementId id of UI element. 877 * @example 878 * @returns {Object} The value of the UI element. 879 */ 880 getValueOf : function( pageId, elementId ) 881 { 882 return this.getContentElement( pageId, elementId ).getValue(); 883 }, 884 885 /** 886 * Sets the value of a dialog UI element. 887 * @param {String} pageId id of the dialog page. 888 * @param {String} elementId id of the UI element. 889 * @param {Object} value The new value of the UI element. 890 * @example 891 */ 892 setValueOf : function( pageId, elementId, value ) 893 { 894 return this.getContentElement( pageId, elementId ).setValue( value ); 895 }, 896 897 /** 898 * Gets the UI element of a button in the dialog's button row. 899 * @param {String} id The id of the button. 900 * @example 901 * @returns {CKEDITOR.ui.dialog.button} The button object. 902 */ 903 getButton : function( id ) 904 { 905 return this._.buttons[ id ]; 906 }, 907 908 /** 909 * Simulates a click to a dialog button in the dialog's button row. 910 * @param {String} id The id of the button. 911 * @example 912 * @returns The return value of the dialog's "click" event. 913 */ 914 click : function( id ) 915 { 916 return this._.buttons[ id ].click(); 917 }, 918 919 /** 920 * Disables a dialog button. 921 * @param {String} id The id of the button. 922 * @example 923 */ 924 disableButton : function( id ) 925 { 926 return this._.buttons[ id ].disable(); 927 }, 928 929 /** 930 * Enables a dialog button. 931 * @param {String} id The id of the button. 932 * @example 933 */ 934 enableButton : function( id ) 935 { 936 return this._.buttons[ id ].enable(); 937 }, 938 939 /** 940 * Gets the number of pages in the dialog. 941 * @returns {Number} Page count. 942 */ 943 getPageCount : function() 944 { 945 return this._.pageCount; 946 }, 947 948 /** 949 * Gets the editor instance which opened this dialog. 950 * @returns {CKEDITOR.editor} Parent editor instances. 951 */ 952 getParentEditor : function() 953 { 954 return this._.editor; 955 }, 956 957 /** 958 * Saves the current selection position in the editor. 959 * This function is automatically called when a non-nested dialog is opened, 960 * but it may also be called by event handlers in dialog definition. 961 * @example 962 */ 963 saveSelection : function() 964 { 965 if ( this._.editor.mode ) 966 { 967 this._.editor.focus(); 968 969 var selection = new CKEDITOR.dom.selection( this._.editor.document ); 970 this._.selectedRanges = selection.getRanges(); 971 this._.selectedElement = selection.getSelectedElement(); 972 } 973 }, 974 975 /** 976 * Clears the saved selection in the dialog object. 977 * This function should be called if the dialog's code has already changed the 978 * current selection position because the dialog closed. (e.g. at onOk()) 979 * @example 980 */ 981 clearSavedSelection : function() 982 { 983 delete this._.selectedRanges; 984 delete this._.selectedElement; 985 }, 986 987 /** 988 * Gets the saved control selection. Control selections should be retrieved 989 * with this function instead of from restoreSelection() because 990 * restoreSelection() does not properly restore control selections. 991 * @returns {CKEDITOR.dom.element} The element that was selected. 992 * @example 993 */ 994 getSelectedElement : function() 995 { 996 return this._.selectedElement; 997 }, 998 999 /** 1000 * Restores the editor's selection from the previously saved position in this 1001 * dialog. 1002 * This function is automatically called when a non-nested dialog is closed, 1003 * but it may also be called by event handlers in dialog definition. 1004 * @example 1005 */ 1006 restoreSelection : function() 1007 { 1008 if ( this._.editor.mode && this._.selectedRanges ) 1009 ( new CKEDITOR.dom.selection( this._.editor.document ) ).selectRanges( this._.selectedRanges ); 1010 } 1011 }; 1012 1013 CKEDITOR.tools.extend( CKEDITOR.dialog, 1014 /** 1015 * @lends CKEDITOR.dialog 1016 */ 1017 { 1018 /** 1019 * Registers a dialog. 1020 * @param {String} name The dialog's name. 1021 * @param {Function|String} dialogDefinition 1022 * A function returning the dialog's definition, or the URL to the .js file holding the function. 1023 * The function should accept an argument "editor" which is the current editor instance, and 1024 * return an object conforming to {@link CKEDITOR.dialog.dialogDefinition}. 1025 * @example 1026 * @see CKEDITOR.dialog.dialogDefinition 1027 */ 1028 add : function( name, dialogDefinition ) 1029 { 1030 // Avoid path registration from multiple instances override definition. 1031 if ( !this._.dialogDefinitions[name] 1032 || typeof dialogDefinition == 'function' ) 1033 this._.dialogDefinitions[name] = dialogDefinition; 1034 }, 1035 1036 exists : function( name ) 1037 { 1038 return !!this._.dialogDefinitions[ name ]; 1039 }, 1040 1041 getCurrent : function() 1042 { 1043 return CKEDITOR.dialog._.currentTop; 1044 }, 1045 1046 /** 1047 * The default OK button for dialogs. Fires the "ok" event and closes the dialog if the event succeeds. 1048 * @static 1049 * @field 1050 * @example 1051 * @type Function 1052 */ 1053 okButton : (function() 1054 { 1055 var retval = function( editor, override ) 1056 { 1057 override = override || {}; 1058 return CKEDITOR.tools.extend( { 1059 id : 'ok', 1060 type : 'button', 1061 label : editor.lang.common.ok, 1062 style : 'width: 60px', 1063 onClick : function( evt ) 1064 { 1065 var dialog = evt.data.dialog; 1066 if ( dialog.fire( 'ok', { hide : true } ).hide !== false ) 1067 dialog.hide(); 1068 } 1069 }, override, true ); 1070 }; 1071 retval.type = 'button'; 1072 retval.override = function( override ) 1073 { 1074 return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, 1075 { type : 'button' }, true ); 1076 }; 1077 return retval; 1078 })(), 1079 1080 /** 1081 * The default cancel button for dialogs. Fires the "cancel" event and closes the dialog if no UI element value changed. 1082 * @static 1083 * @field 1084 * @example 1085 * @type Function 1086 */ 1087 cancelButton : (function() 1088 { 1089 var retval = function( editor, override ) 1090 { 1091 override = override || {}; 1092 return CKEDITOR.tools.extend( { 1093 id : 'cancel', 1094 type : 'button', 1095 label : editor.lang.common.cancel, 1096 style : 'width: 60px', 1097 onClick : function( evt ) 1098 { 1099 var dialog = evt.data.dialog; 1100 if ( dialog.fire( 'cancel', { hide : true } ).hide !== false ) 1101 dialog.hide(); 1102 } 1103 }, override, true ); 1104 }; 1105 retval.type = 'button'; 1106 retval.override = function( override ) 1107 { 1108 return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, 1109 { type : 'button' }, true ); 1110 }; 1111 return retval; 1112 })(), 1113 1114 /** 1115 * Registers a dialog UI element. 1116 * @param {String} typeName The name of the UI element. 1117 * @param {Function} builder The function to build the UI element. 1118 * @example 1119 */ 1120 addUIElement : function( typeName, builder ) 1121 { 1122 this._.uiElementBuilders[typeName] = builder; 1123 }, 1124 1125 /** 1126 * Sets the width of margins of dialogs, which is used for the dialog moving and resizing logic. 1127 * The margin here means the area between the dialog's container <div> and the visual boundary of the dialog. 1128 * Typically this area is used for dialog shadows. 1129 * This function is typically called in a skin's JavaScript files. 1130 * @param {Number} top The top margin in pixels. 1131 * @param {Number} right The right margin in pixels. 1132 * @param {Number} bottom The bottom margin in pixels. 1133 * @param {Number} left The left margin in pixels. 1134 * @example 1135 */ 1136 setMargins : function( top, right, bottom, left ) 1137 { 1138 this._.margins = [ top, right, bottom, left ]; 1139 } 1140 }); 1141 1142 CKEDITOR.dialog._ = 1143 { 1144 uiElementBuilders : {}, 1145 1146 dialogDefinitions : {}, 1147 1148 currentTop : null, 1149 1150 currentZIndex : null, 1151 1152 margins : [0, 0, 0, 0] 1153 }; 1154 1155 // "Inherit" (copy actually) from CKEDITOR.event. 1156 CKEDITOR.event.implementOn( CKEDITOR.dialog ); 1157 CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype, true ); 1158 1159 var defaultDialogDefinition = 1160 { 1161 resizable : CKEDITOR.DIALOG_RESIZE_NONE, 1162 minWidth : 600, 1163 minHeight : 400, 1164 buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] 1165 }; 1166 1167 // Tool function used to return an item from an array based on its id 1168 // property. 1169 var getById = function( array, id, recurse ) 1170 { 1171 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) 1172 { 1173 if ( item.id == id ) 1174 return item; 1175 if ( recurse && item[ recurse ] ) 1176 { 1177 var retval = getById( item[ recurse ], id, recurse ) ; 1178 if ( retval ) 1179 return retval; 1180 } 1181 } 1182 return null; 1183 }; 1184 1185 // Tool function used to add an item into an array. 1186 var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound ) 1187 { 1188 if ( nextSiblingId ) 1189 { 1190 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) 1191 { 1192 if ( item.id == nextSiblingId ) 1193 { 1194 array.splice( i, 0, newItem ); 1195 return newItem; 1196 } 1197 1198 if ( recurse && item[ recurse ] ) 1199 { 1200 var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true ); 1201 if ( retval ) 1202 return retval; 1203 } 1204 } 1205 1206 if ( nullIfNotFound ) 1207 return null; 1208 } 1209 1210 array.push( newItem ); 1211 return newItem; 1212 }; 1213 1214 // Tool function used to remove an item from an array based on its id. 1215 var removeById = function( array, id, recurse ) 1216 { 1217 for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) 1218 { 1219 if ( item.id == id ) 1220 return array.splice( i, 1 ); 1221 if ( recurse && item[ recurse ] ) 1222 { 1223 var retval = removeById( item[ recurse ], id, recurse ); 1224 if ( retval ) 1225 return retval; 1226 } 1227 } 1228 return null; 1229 }; 1230 1231 /** 1232 * This class is not really part of the API. It is the "definition" property value 1233 * passed to "dialogDefinition" event handlers. 1234 * @constructor 1235 * @name CKEDITOR.dialog.dialogDefinitionObject 1236 * @extends CKEDITOR.dialog.dialogDefinition 1237 * @example 1238 * CKEDITOR.on( 'dialogDefinition', function( evt ) 1239 * { 1240 * var definition = evt.data.definition; 1241 * var content = definition.getContents( 'page1' ); 1242 * ... 1243 * } ); 1244 */ 1245 var definitionObject = function( dialog, dialogDefinition ) 1246 { 1247 // TODO : Check if needed. 1248 this.dialog = dialog; 1249 1250 // Transform the contents entries in contentObjects. 1251 var contents = dialogDefinition.contents; 1252 for ( var i = 0, content ; ( content = contents[i] ) ; i++ ) 1253 contents[ i ] = new contentObject( dialog, content ); 1254 1255 CKEDITOR.tools.extend( this, dialogDefinition ); 1256 }; 1257 1258 definitionObject.prototype = 1259 /** @lends CKEDITOR.dialog.dialogDefinitionObject.prototype */ 1260 { 1261 /** 1262 * Gets a content definition. 1263 * @param {String} id The id of the content definition. 1264 * @returns {CKEDITOR.dialog.contentDefinition} The content definition 1265 * matching id. 1266 */ 1267 getContents : function( id ) 1268 { 1269 return getById( this.contents, id ); 1270 }, 1271 1272 /** 1273 * Gets a button definition. 1274 * @param {String} id The id of the button definition. 1275 * @returns {CKEDITOR.dialog.buttonDefinition} The button definition 1276 * matching id. 1277 */ 1278 getButton : function( id ) 1279 { 1280 return getById( this.buttons, id ); 1281 }, 1282 1283 /** 1284 * Adds a content definition object under this dialog definition. 1285 * @param {CKEDITOR.dialog.contentDefinition} contentDefinition The 1286 * content definition. 1287 * @param {String} [nextSiblingId] The id of an existing content 1288 * definition which the new content definition will be inserted 1289 * before. Omit if the new content definition is to be inserted as 1290 * the last item. 1291 * @returns {CKEDITOR.dialog.contentDefinition} The inserted content 1292 * definition. 1293 */ 1294 addContents : function( contentDefinition, nextSiblingId ) 1295 { 1296 return addById( this.contents, contentDefinition, nextSiblingId ); 1297 }, 1298 1299 /** 1300 * Adds a button definition object under this dialog definition. 1301 * @param {CKEDITOR.dialog.buttonDefinition} buttonDefinition The 1302 * button definition. 1303 * @param {String} [nextSiblingId] The id of an existing button 1304 * definition which the new button definition will be inserted 1305 * before. Omit if the new button definition is to be inserted as 1306 * the last item. 1307 * @returns {CKEDITOR.dialog.buttonDefinition} The inserted button 1308 * definition. 1309 */ 1310 addButton : function( buttonDefinition, nextSiblingId ) 1311 { 1312 return addById( this.buttons, buttonDefinition, nextSiblingId ); 1313 }, 1314 1315 /** 1316 * Removes a content definition from this dialog definition. 1317 * @param {String} id The id of the content definition to be removed. 1318 * @returns {CKEDITOR.dialog.contentDefinition} The removed content 1319 * definition. 1320 */ 1321 removeContents : function( id ) 1322 { 1323 removeById( this.contents, id ); 1324 }, 1325 1326 /** 1327 * Removes a button definition from the dialog definition. 1328 * @param {String} id The id of the button definition to be removed. 1329 * @returns {CKEDITOR.dialog.buttonDefinition} The removed button 1330 * definition. 1331 */ 1332 removeButton : function( id ) 1333 { 1334 removeById( this.buttons, id ); 1335 } 1336 }; 1337 1338 /** 1339 * This class is not really part of the API. It is the template of the 1340 * objects representing content pages inside the 1341 * CKEDITOR.dialog.dialogDefinitionObject. 1342 * @constructor 1343 * @name CKEDITOR.dialog.contentDefinitionObject 1344 * @example 1345 * CKEDITOR.on( 'dialogDefinition', function( evt ) 1346 * { 1347 * var definition = evt.data.definition; 1348 * var content = definition.getContents( 'page1' ); 1349 * content.remove( 'textInput1' ); 1350 * ... 1351 * } ); 1352 */ 1353 var contentObject = function( dialog, contentDefinition ) 1354 { 1355 this._ = 1356 { 1357 dialog : dialog 1358 }; 1359 1360 CKEDITOR.tools.extend( this, contentDefinition ); 1361 }; 1362 1363 contentObject.prototype = 1364 /** @lends CKEDITOR.dialog.contentDefinitionObject.prototype */ 1365 { 1366 /** 1367 * Gets a UI element definition under the content definition. 1368 * @param {String} id The id of the UI element definition. 1369 * @returns {CKEDITOR.dialog.uiElementDefinition} 1370 */ 1371 get : function( id ) 1372 { 1373 return getById( this.elements, id, 'children' ); 1374 }, 1375 1376 /** 1377 * Adds a UI element definition to the content definition. 1378 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition The 1379 * UI elemnet definition to be added. 1380 * @param {String} nextSiblingId The id of an existing UI element 1381 * definition which the new UI element definition will be inserted 1382 * before. Omit if the new button definition is to be inserted as 1383 * the last item. 1384 * @returns {CKEDITOR.dialog.uiElementDefinition} The element 1385 * definition inserted. 1386 */ 1387 add : function( elementDefinition, nextSiblingId ) 1388 { 1389 return addById( this.elements, elementDefinition, nextSiblingId, 'children' ); 1390 }, 1391 1392 /** 1393 * Removes a UI element definition from the content definition. 1394 * @param {String} id The id of the UI element definition to be 1395 * removed. 1396 * @returns {CKEDITOR.dialog.uiElementDefinition} The element 1397 * definition removed. 1398 * @example 1399 */ 1400 remove : function( id ) 1401 { 1402 removeById( this.elements, id, 'children' ); 1403 } 1404 }; 1405 1406 var initDragAndDrop = function( dialog ) 1407 { 1408 var lastCoords = null, 1409 abstractDialogCoords = null, 1410 element = dialog.getElement().getFirst(), 1411 magnetDistance = dialog._.editor.config.dialog_magnetDistance, 1412 mouseMoveHandler = function( evt ) 1413 { 1414 var dialogSize = dialog.getSize(), 1415 viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), 1416 x = evt.data.$.screenX, 1417 y = evt.data.$.screenY, 1418 dx = x - lastCoords.x, 1419 dy = y - lastCoords.y, 1420 realX, realY; 1421 1422 lastCoords = { x : x, y : y }; 1423 abstractDialogCoords.x += dx; 1424 abstractDialogCoords.y += dy; 1425 1426 if ( abstractDialogCoords.x + CKEDITOR.dialog._.margins[3] < magnetDistance ) 1427 realX = - CKEDITOR.dialog._.margins[3]; 1428 else if ( abstractDialogCoords.x - CKEDITOR.dialog._.margins[1] > viewPaneSize.width - dialogSize.width - magnetDistance ) 1429 realX = viewPaneSize.width - dialogSize.width + CKEDITOR.dialog._.margins[1]; 1430 else 1431 realX = abstractDialogCoords.x; 1432 1433 if ( abstractDialogCoords.y + CKEDITOR.dialog._.margins[0] < magnetDistance ) 1434 realY = - CKEDITOR.dialog._.margins[0]; 1435 else if ( abstractDialogCoords.y - CKEDITOR.dialog._.margins[2] > viewPaneSize.height - dialogSize.height - magnetDistance ) 1436 realY = viewPaneSize.height - dialogSize.height + CKEDITOR.dialog._.margins[2]; 1437 else 1438 realY = abstractDialogCoords.y; 1439 1440 dialog.move( realX, realY ); 1441 1442 evt.data.preventDefault(); 1443 }, 1444 mouseUpHandler = function( evt ) 1445 { 1446 CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); 1447 CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); 1448 1449 if ( CKEDITOR.env.ie6Compat ) 1450 { 1451 var coverDoc = new CKEDITOR.dom.document( frames( 'cke_dialog_background_iframe' ).document ); 1452 coverDoc.removeListener( 'mousemove', mouseMoveHandler ); 1453 coverDoc.removeListener( 'mouseup', mouseUpHandler ); 1454 } 1455 }; 1456 1457 dialog.parts.title.on( 'mousedown', function( evt ) 1458 { 1459 lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; 1460 1461 CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); 1462 CKEDITOR.document.on( 'mouseup', mouseUpHandler ); 1463 abstractDialogCoords = dialog.getPosition(); 1464 1465 if ( CKEDITOR.env.ie6Compat ) 1466 { 1467 var coverDoc = new CKEDITOR.dom.document( frames( 'cke_dialog_background_iframe' ).document ); 1468 coverDoc.on( 'mousemove', mouseMoveHandler ); 1469 coverDoc.on( 'mouseup', mouseUpHandler ); 1470 } 1471 1472 evt.data.preventDefault(); 1473 }, dialog ); 1474 }; 1475 1476 var initResizeHandles = function( dialog ) 1477 { 1478 var definition = dialog.definition, 1479 minWidth = definition.minWidth || 0, 1480 minHeight = definition.minHeight || 0, 1481 resizable = definition.resizable, 1482 topSizer = function( coords, dy ) 1483 { 1484 coords.y += dy; 1485 }, 1486 rightSizer = function( coords, dx ) 1487 { 1488 coords.x2 += dx; 1489 }, 1490 bottomSizer = function( coords, dy ) 1491 { 1492 coords.y2 += dy; 1493 }, 1494 leftSizer = function( coords, dx ) 1495 { 1496 coords.x += dx; 1497 }, 1498 lastCoords = null, 1499 abstractDialogCoords = null, 1500 magnetDistance = dialog._.editor.config.magnetDistance, 1501 parts = [ 'tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br' ], 1502 mouseDownHandler = function( evt ) 1503 { 1504 var partName = evt.listenerData.part, size = dialog.getSize(); 1505 abstractDialogCoords = dialog.getPosition(); 1506 CKEDITOR.tools.extend( abstractDialogCoords, 1507 { 1508 x2 : abstractDialogCoords.x + size.width, 1509 y2 : abstractDialogCoords.y + size.height 1510 } ); 1511 lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; 1512 1513 CKEDITOR.document.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } ); 1514 CKEDITOR.document.on( 'mouseup', mouseUpHandler, dialog, { part : partName } ); 1515 1516 if ( CKEDITOR.env.ie6Compat ) 1517 { 1518 var coverDoc = new CKEDITOR.dom.document( frames( 'cke_dialog_background_iframe' ).document ); 1519 coverDoc.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } ); 1520 coverDoc.on( 'mouseup', mouseUpHandler, dialog, { part : partName } ); 1521 } 1522 1523 evt.data.preventDefault(); 1524 }, 1525 mouseMoveHandler = function( evt ) 1526 { 1527 var x = evt.data.$.screenX, 1528 y = evt.data.$.screenY, 1529 dx = x - lastCoords.x, 1530 dy = y - lastCoords.y, 1531 viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), 1532 partName = evt.listenerData.part; 1533 1534 if ( partName.search( 't' ) != -1 ) 1535 topSizer( abstractDialogCoords, dy ); 1536 if ( partName.search( 'l' ) != -1 ) 1537 leftSizer( abstractDialogCoords, dx ); 1538 if ( partName.search( 'b' ) != -1 ) 1539 bottomSizer( abstractDialogCoords, dy ); 1540 if ( partName.search( 'r' ) != -1 ) 1541 rightSizer( abstractDialogCoords, dx ); 1542 1543 lastCoords = { x : x, y : y }; 1544 1545 var realX, realY, realX2, realY2; 1546 1547 if ( abstractDialogCoords.x + CKEDITOR.dialog._.margins[3] < magnetDistance ) 1548 realX = - CKEDITOR.dialog._.margins[3]; 1549 else if ( partName.search( 'l' ) != -1 && abstractDialogCoords.x2 - abstractDialogCoords.x < minWidth + magnetDistance ) 1550 realX = abstractDialogCoords.x2 - minWidth; 1551 else 1552 realX = abstractDialogCoords.x; 1553 1554 if ( abstractDialogCoords.y + CKEDITOR.dialog._.margins[0] < magnetDistance ) 1555 realY = - CKEDITOR.dialog._.margins[0]; 1556 else if ( partName.search( 't' ) != -1 && abstractDialogCoords.y2 - abstractDialogCoords.y < minHeight + magnetDistance ) 1557 realY = abstractDialogCoords.y2 - minHeight; 1558 else 1559 realY = abstractDialogCoords.y; 1560 1561 if ( abstractDialogCoords.x2 - CKEDITOR.dialog._.margins[1] > viewPaneSize.width - magnetDistance ) 1562 realX2 = viewPaneSize.width + CKEDITOR.dialog._.margins[1] ; 1563 else if ( partName.search( 'r' ) != -1 && abstractDialogCoords.x2 - abstractDialogCoords.x < minWidth + magnetDistance ) 1564 realX2 = abstractDialogCoords.x + minWidth; 1565 else 1566 realX2 = abstractDialogCoords.x2; 1567 1568 if ( abstractDialogCoords.y2 - CKEDITOR.dialog._.margins[2] > viewPaneSize.height - magnetDistance ) 1569 realY2= viewPaneSize.height + CKEDITOR.dialog._.margins[2] ; 1570 else if ( partName.search( 'b' ) != -1 && abstractDialogCoords.y2 - abstractDialogCoords.y < minHeight + magnetDistance ) 1571 realY2 = abstractDialogCoords.y + minHeight; 1572 else 1573 realY2 = abstractDialogCoords.y2 ; 1574 1575 dialog.move( realX, realY ); 1576 dialog.resize( realX2 - realX, realY2 - realY ); 1577 1578 evt.data.preventDefault(); 1579 }, 1580 mouseUpHandler = function( evt ) 1581 { 1582 CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); 1583 CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); 1584 1585 if ( CKEDITOR.env.ie6Compat ) 1586 { 1587 var coverDoc = new CKEDITOR.dom.document( frames( 'cke_dialog_background_iframe' ).document ); 1588 coverDoc.removeListener( 'mouseup', mouseUpHandler ); 1589 coverDoc.removeListener( 'mousemove', mouseMoveHandler ); 1590 } 1591 }; 1592 1593 var widthTest = /[lr]/, 1594 heightTest = /[tb]/; 1595 for ( var i = 0 ; i < parts.length ; i++ ) 1596 { 1597 var element = dialog.parts[ parts[i] + '_resize' ]; 1598 if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE || 1599 resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT && widthTest.test( parts[i] ) || 1600 resizable == CKEDITOR.DIALOG_RESIZE_WIDTH && heightTest.test( parts[i] ) ) 1601 { 1602 element.hide(); 1603 continue; 1604 } 1605 element.on( 'mousedown', mouseDownHandler, dialog, { part : parts[i] } ); 1606 } 1607 }; 1608 1609 var resizeCover; 1610 1611 var addCover = function( editor ) 1612 { 1613 var win = CKEDITOR.document.getWindow(); 1614 1615 var html = [ 1616 '<div style="position: ', ( CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed' ), 1617 '; z-index: ', editor.config.baseFloatZIndex, 1618 '; top: 0px; left: 0px; ', 1619 'background-color: ', editor.config.dialog_backgroundCoverColor, 1620 '" id="cke_dialog_background_cover">' 1621 ]; 1622 1623 if ( CKEDITOR.env.ie6Compat ) 1624 { 1625 html.push( '<iframe hidefocus="true" frameborder="0" name="cke_dialog_background_iframe" src="javascript: \'\'" ', 1626 'style="position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; ', 1627 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)" ></iframe>' ); 1628 } 1629 1630 html.push( '</div>' ); 1631 1632 var element = CKEDITOR.dom.element.createFromHtml( html.join( '' ) ); 1633 1634 var resizeFunc = function() 1635 { 1636 var size = win.getViewPaneSize(); 1637 element.setStyles( 1638 { 1639 width : size.width + 'px', 1640 height : size.height + 'px' 1641 }); 1642 }; 1643 1644 var scrollFunc = function() 1645 { 1646 var pos = win.getScrollPosition(), 1647 cursor = CKEDITOR.dialog._.currentTop; 1648 element.setStyles( 1649 { 1650 left : pos.x + 'px', 1651 top : pos.y + 'px' 1652 }); 1653 1654 do 1655 { 1656 var dialogPos = cursor.getPosition(); 1657 cursor.move( dialogPos.x, dialogPos.y ); 1658 } while( ( cursor = cursor._.parentDialog ) ); 1659 }; 1660 1661 resizeCover = resizeFunc; 1662 win.on( 'resize', resizeFunc ); 1663 resizeFunc(); 1664 if ( CKEDITOR.env.ie6Compat ) 1665 { 1666 // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll. 1667 // So we need to invent a really funny way to make it work. 1668 var myScrollHandler = function() 1669 { 1670 scrollFunc(); 1671 arguments.callee.prevScrollHandler.apply( this, arguments ); 1672 }; 1673 win.$.setTimeout( function() 1674 { 1675 myScrollHandler.prevScrollHandler = window.onscroll || function(){}; 1676 window.onscroll = myScrollHandler; 1677 }, 0 ); 1678 scrollFunc(); 1679 } 1680 element.setOpacity( editor.config.dialog_backgroundCoverOpacity ); 1681 element.appendTo( CKEDITOR.document.getBody() ); 1682 }; 1683 1684 var removeCover = function() 1685 { 1686 var element = CKEDITOR.document.getById( 'cke_dialog_background_cover' ), 1687 win = CKEDITOR.document.getWindow(); 1688 if ( element ) 1689 { 1690 element.remove(); 1691 win.removeListener( 'resize', resizeCover ); 1692 1693 if ( CKEDITOR.env.ie6Compat ) 1694 { 1695 win.$.setTimeout( function() 1696 { 1697 var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler; 1698 window.onscroll = prevScrollHandler || null; 1699 }, 0 ); 1700 } 1701 resizeCover = null; 1702 } 1703 }; 1704 1705 var accessKeyProcessors = {}; 1706 1707 var accessKeyDownHandler = function( evt ) 1708 { 1709 var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, 1710 alt = evt.data.$.altKey, 1711 shift = evt.data.$.shiftKey, 1712 key = String.fromCharCode( evt.data.$.keyCode ), 1713 keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; 1714 1715 if ( !keyProcessor || !keyProcessor.length ) 1716 return; 1717 1718 keyProcessor = keyProcessor[keyProcessor.length - 1]; 1719 keyProcessor.keydown && keyProcessor.keydown.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); 1720 evt.data.preventDefault(); 1721 }; 1722 1723 var accessKeyUpHandler = function( evt ) 1724 { 1725 var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, 1726 alt = evt.data.$.altKey, 1727 shift = evt.data.$.shiftKey, 1728 key = String.fromCharCode( evt.data.$.keyCode ), 1729 keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; 1730 1731 if ( !keyProcessor || !keyProcessor.length ) 1732 return; 1733 1734 keyProcessor = keyProcessor[keyProcessor.length - 1]; 1735 keyProcessor.keyup && keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); 1736 evt.data.preventDefault(); 1737 }; 1738 1739 var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc ) 1740 { 1741 var procList = accessKeyProcessors[key] || ( accessKeyProcessors[key] = [] ); 1742 procList.push( { 1743 uiElement : uiElement, 1744 dialog : dialog, 1745 key : key, 1746 keyup : upFunc || uiElement.accessKeyUp, 1747 keydown : downFunc || uiElement.accessKeyDown 1748 } ); 1749 }; 1750 1751 var unregisterAccessKey = function( obj ) 1752 { 1753 for ( var i in accessKeyProcessors ) 1754 { 1755 var list = accessKeyProcessors[i]; 1756 for ( var j = list.length - 1 ; j >= 0 ; j-- ) 1757 { 1758 if ( list[j].dialog == obj || list[j].uiElement == obj ) 1759 list.splice( j, 1 ); 1760 } 1761 if ( list.length === 0 ) 1762 delete accessKeyProcessors[i]; 1763 } 1764 }; 1765 1766 var tabAccessKeyUp = function( dialog, key ) 1767 { 1768 if ( dialog._.accessKeyMap[key] ) 1769 dialog.selectPage( dialog._.accessKeyMap[key] ); 1770 }; 1771 1772 var tabAccessKeyDown = function( dialog, key ) 1773 { 1774 }; 1775 1776 (function() 1777 { 1778 CKEDITOR.ui.dialog = 1779 { 1780 /** 1781 * The base class of all dialog UI elements. 1782 * @constructor 1783 * @param {CKEDITOR.dialog} dialog Parent dialog object. 1784 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition Element 1785 * definition. Accepted fields: 1786 * <ul> 1787 * <li><strong>id</strong> (Required) The id of the UI element. See {@link 1788 * CKEDITOR.dialog#getContentElement}</li> 1789 * <li><strong>type</strong> (Required) The type of the UI element. The 1790 * value to this field specifies which UI element class will be used to 1791 * generate the final widget.</li> 1792 * <li><strong>title</strong> (Optional) The popup tooltip for the UI 1793 * element.</li> 1794 * <li><strong>className</strong> (Optional) Additional CSS class names 1795 * to add to the UI element. Separated by space.</li> 1796 * <li><strong>style</strong> (Optional) Additional CSS inline styles 1797 * to add to the UI element. A semicolon (;) is required after the last 1798 * style declaration.</li> 1799 * <li><strong>accessKey</strong> (Optional) The alphanumeric access key 1800 * for this element. Access keys are automatically prefixed by CTRL.</li> 1801 * <li><strong>on*</strong> (Optional) Any UI element definition field that 1802 * starts with <em>on</em> followed immediately by a capital letter and 1803 * probably more letters is an event handler. Event handlers may be further 1804 * divided into registered event handlers and DOM event handlers. Please 1805 * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and 1806 * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more 1807 * information.</li> 1808 * </ul> 1809 * @param {Array} htmlList 1810 * List of HTML code to be added to the dialog's content area. 1811 * @param {Function|String} nodeNameArg 1812 * A function returning a string, or a simple string for the node name for 1813 * the root DOM node. Default is 'div'. 1814 * @param {Function|Object} stylesArg 1815 * A function returning an object, or a simple object for CSS styles applied 1816 * to the DOM node. Default is empty object. 1817 * @param {Function|Object} attributesArg 1818 * A fucntion returning an object, or a simple object for attributes applied 1819 * to the DOM node. Default is empty object. 1820 * @param {Function|String} contentsArg 1821 * A function returning a string, or a simple string for the HTML code inside 1822 * the root DOM node. Default is empty string. 1823 * @example 1824 */ 1825 uiElement : function( dialog, elementDefinition, htmlList, nodeNameArg, stylesArg, attributesArg, contentsArg ) 1826 { 1827 if ( arguments.length < 4 ) 1828 return; 1829 1830 var nodeName = ( nodeNameArg.call ? nodeNameArg( elementDefinition ) : nodeNameArg ) || 'div', 1831 html = [ '<', nodeName, ' ' ], 1832 styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {}, 1833 attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {}, 1834 innerHTML = ( contentsArg && contentsArg.call ? contentsArg( dialog, elementDefinition ) : contentsArg ) || '', 1835 domId = this.domId = attributes.id || CKEDITOR.tools.getNextNumber() + '_uiElement', 1836 id = this.id = elementDefinition.id, 1837 i; 1838 1839 // Set the id, a unique id is required for getElement() to work. 1840 attributes.id = domId; 1841 1842 // Set the type and definition CSS class names. 1843 var classes = {}; 1844 if ( elementDefinition.type ) 1845 classes[ 'cke_dialog_ui_' + elementDefinition.type ] = 1; 1846 if ( elementDefinition.className ) 1847 classes[ elementDefinition.className ] = 1; 1848 var attributeClasses = ( attributes['class'] && attributes['class'].split ) ? attributes['class'].split( ' ' ) : []; 1849 for ( i = 0 ; i < attributeClasses.length ; i++ ) 1850 { 1851 if ( attributeClasses[i] ) 1852 classes[ attributeClasses[i] ] = 1; 1853 } 1854 var finalClasses = []; 1855 for ( i in classes ) 1856 finalClasses.push( i ); 1857 attributes['class'] = finalClasses.join( ' ' ); 1858 1859 // Set the popup tooltop. 1860 if ( elementDefinition.title ) 1861 attributes.title = elementDefinition.title; 1862 1863 // Write the inline CSS styles. 1864 var styleStr = ( elementDefinition.style || '' ).split( ';' ); 1865 for ( i in styles ) 1866 styleStr.push( i + ':' + styles[i] ); 1867 for ( i = styleStr.length - 1 ; i >= 0 ; i-- ) 1868 { 1869 if ( styleStr[i] === '' ) 1870 styleStr.splice( i, 1 ); 1871 } 1872 if ( styleStr.length > 0 ) 1873 attributes.style = ( attributes.style || '' ) + styleStr.join( '; ' ); 1874 1875 // Write the attributes. 1876 for ( i in attributes ) 1877 html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" '); 1878 1879 // Write the content HTML. 1880 html.push( '>', innerHTML, '</', nodeName, '>' ); 1881 1882 // Add contents to the parent HTML array. 1883 htmlList.push( html.join( '' ) ); 1884 1885 ( this._ || ( this._ = {} ) ).dialog = dialog; 1886 1887 // Override isChanged if it is defined in element definition. 1888 if ( typeof( elementDefinition.isChanged ) == 'boolean' ) 1889 this.isChanged = function(){ return elementDefinition.isChanged; }; 1890 if ( typeof( elementDefinition.isChanged ) == 'function' ) 1891 this.isChanged = elementDefinition.isChanged; 1892 1893 // Add events. 1894 CKEDITOR.event.implementOn( this ); 1895 1896 this.registerEvents( elementDefinition ); 1897 if ( this.accessKeyUp && this.accessKeyDown && elementDefinition.accessKey ) 1898 registerAccessKey( this, dialog, 'CTRL+' + elementDefinition.accessKey ); 1899 1900 var me = this; 1901 dialog.on( 'load', function() 1902 { 1903 if ( me.getInputElement() ) 1904 { 1905 me.getInputElement().on( 'focus', function() 1906 { 1907 dialog._.tabBarMode = false; 1908 dialog._.hasFocus = true; 1909 me.fire( 'focus' ); 1910 }, me ); 1911 } 1912 } ); 1913 1914 // Register the object as a tab focus if it can be included. 1915 if ( this.keyboardFocusable ) 1916 { 1917 this.focusIndex = dialog._.focusList.push( this ) - 1; 1918 this.on( 'focus', function() 1919 { 1920 dialog._.currentFocusIndex = me.focusIndex; 1921 } ); 1922 } 1923 1924 // Completes this object with everything we have in the 1925 // definition. 1926 CKEDITOR.tools.extend( this, elementDefinition ); 1927 }, 1928 1929 /** 1930 * Horizontal layout box for dialog UI elements, auto-expends to available width of container. 1931 * @constructor 1932 * @extends CKEDITOR.ui.dialog.uiElement 1933 * @param {CKEDITOR.dialog} dialog 1934 * Parent dialog object. 1935 * @param {Array} childObjList 1936 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this 1937 * container. 1938 * @param {Array} childHtmlList 1939 * Array of HTML code that correspond to the HTML output of all the 1940 * objects in childObjList. 1941 * @param {Array} htmlList 1942 * Array of HTML code that this element will output to. 1943 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition 1944 * The element definition. Accepted fields: 1945 * <ul> 1946 * <li><strong>widths</strong> (Optional) The widths of child cells.</li> 1947 * <li><strong>height</strong> (Optional) The height of the layout.</li> 1948 * <li><strong>padding</strong> (Optional) The padding width inside child 1949 * cells.</li> 1950 * <li><strong>align</strong> (Optional) The alignment of the whole layout 1951 * </li> 1952 * </ul> 1953 * @example 1954 */ 1955 hbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) 1956 { 1957 if ( arguments.length < 4 ) 1958 return; 1959 1960 this._ || ( this._ = {} ); 1961 1962 var children = this._.children = childObjList, 1963 widths = elementDefinition && elementDefinition.widths || null, 1964 height = elementDefinition && elementDefinition.height || null, 1965 styles = {}, 1966 i; 1967 /** @ignore */ 1968 var innerHTML = function() 1969 { 1970 var html = [ '<tbody><tr class="cke_dialog_ui_hbox">' ]; 1971 for ( i = 0 ; i < childHtmlList.length ; i++ ) 1972 { 1973 var className = 'cke_dialog_ui_hbox_child', 1974 styles = []; 1975 if ( i === 0 ) 1976 className = 'cke_dialog_ui_hbox_first'; 1977 if ( i == childHtmlList.length - 1 ) 1978 className = 'cke_dialog_ui_hbox_last'; 1979 html.push( '<td class="', className, '" ' ); 1980 if ( widths ) 1981 { 1982 if ( widths[i] ) 1983 styles.push( 'width:' + CKEDITOR.tools.cssLength( widths[i] ) ); 1984 } 1985 else 1986 styles.push( 'width:' + Math.floor( 100 / childHtmlList.length ) + '%' ); 1987 if ( height ) 1988 styles.push( 'height:' + CKEDITOR.tools.cssLength( height ) ); 1989 if ( elementDefinition && elementDefinition.padding != undefined ) 1990 styles.push( 'padding:' + CKEDITOR.tools.cssLength( elementDefinition.padding ) ); 1991 if ( styles.length > 0 ) 1992 html.push( 'style="' + styles.join('; ') + '" ' ); 1993 html.push( '>', childHtmlList[i], '</td>' ); 1994 } 1995 html.push( '</tr></tbody>' ); 1996 return html.join( '' ); 1997 }; 1998 1999 CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'hbox' }, htmlList, 'table', styles, 2000 { align : ( elementDefinition && elementDefinition.align ) || 2001 ( dialog.getParentEditor().lang.dir == 'ltr' ? 'left' : 'right' ) }, innerHTML ); 2002 }, 2003 2004 /** 2005 * Vertical layout box for dialog UI elements. 2006 * @constructor 2007 * @extends CKEDITOR.ui.dialog.hbox 2008 * @param {CKEDITOR.dialog} dialog 2009 * Parent dialog object. 2010 * @param {Array} childObjList 2011 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this 2012 * container. 2013 * @param {Array} childHtmlList 2014 * Array of HTML code that correspond to the HTML output of all the 2015 * objects in childObjList. 2016 * @param {Array} htmlList 2017 * Array of HTML code that this element will output to. 2018 * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition 2019 * The element definition. Accepted fields: 2020 * <ul> 2021 * <li><strong>width</strong> (Optional) The width of the layout.</li> 2022 * <li><strong>heights</strong> (Optional) The heights of individual cells. 2023 * </li> 2024 * <li><strong>align</strong> (Optional) The alignment of the layout.</li> 2025 * <li><strong>padding</strong> (Optional) The padding width inside child 2026 * cells.</li> 2027 * <li><strong>expand</strong> (Optional) Whether the layout should expand 2028 * vertically to fill its container.</li> 2029 * </ul> 2030 * @example 2031 */ 2032 vbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) 2033 { 2034 if (arguments.length < 3 ) 2035 return; 2036 2037 this._ || ( this._ = {} ); 2038 2039 var children = this._.children = childObjList, 2040 width = elementDefinition && elementDefinition.width || null, 2041 heights = elementDefinition && elementDefinition.heights || null; 2042 /** @ignore */ 2043 var innerHTML = function() 2044 { 2045 var html = [ '<table cellspacing="0" border="0" ' ]; 2046 html.push( 'style="' ); 2047 if ( elementDefinition && elementDefinition.expand ) 2048 html.push( 'height:100%;' ); 2049 html.push( 'width:' + CKEDITOR.tools.cssLength( width || '100%' ), ';' ); 2050 html.push( '"' ); 2051 html.push( 'align="', CKEDITOR.tools.htmlEncode( 2052 ( elementDefinition && elementDefinition.align ) || ( dialog.getParentEditor().lang.dir == 'ltr' ? 'left' : 'right' ) ), '" ' ); 2053 2054 html.push( '><tbody>' ); 2055 for ( var i = 0 ; i < childHtmlList.length ; i++ ) 2056 { 2057 var styles = []; 2058 html.push( '<tr><td ' ); 2059 if ( width ) 2060 styles.push( 'width:' + CKEDITOR.tools.cssLength( width || '100%' ) ); 2061 if ( heights ) 2062 styles.push( 'height:' + CKEDITOR.tools.cssLength( heights[i] ) ); 2063 else if ( elementDefinition && elementDefinition.expand ) 2064 styles.push( 'height:' + Math.floor( 100 / childHtmlList.length ) + '%' ); 2065 if ( elementDefinition && elementDefinition.padding != undefined ) 2066 styles.push( 'padding:' + CKEDITOR.tools.cssLength( elementDefinition.padding ) ); 2067 if ( styles.length > 0 ) 2068 html.push( 'style="', styles.join( '; ' ), '" ' ); 2069 html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[i], '</td></tr>' ); 2070 } 2071 html.push( '</tbody></table>' ); 2072 return html.join( '' ); 2073 }; 2074 CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, null, innerHTML ); 2075 } 2076 }; 2077 })(); 2078 2079 CKEDITOR.ui.dialog.uiElement.prototype = 2080 { 2081 /** 2082 * Gets the root DOM element of this dialog UI object. 2083 * @returns {CKEDITOR.dom.element} Root DOM element of UI object. 2084 * @example 2085 * uiElement.getElement().hide(); 2086 */ 2087 getElement : function() 2088 { 2089 return CKEDITOR.document.getById( this.domId ); 2090 }, 2091 2092 /** 2093 * Gets the DOM element that the user inputs values. 2094 * This function is used by setValue(), getValue() and focus(). It should 2095 * be overrided in child classes where the input element isn't the root 2096 * element. 2097 * @returns {CKEDITOR.dom.element} The element where the user input values. 2098 * @example 2099 * var rawValue = textInput.getInputElement().$.value; 2100 */ 2101 getInputElement : function() 2102 { 2103 return this.getElement(); 2104 }, 2105 2106 /** 2107 * Gets the parent dialog object containing this UI element. 2108 * @returns {CKEDITOR.dialog} Parent dialog object. 2109 * @example 2110 * var dialog = uiElement.getDialog(); 2111 */ 2112 getDialog : function() 2113 { 2114 return this._.dialog; 2115 }, 2116 2117 /** 2118 * Sets the value of this dialog UI object. 2119 * @param {Object} value The new value. 2120 * @returns {CKEDITOR.dialog.uiElement} The current UI element. 2121 * @example 2122 * uiElement.setValue( 'Dingo' ); 2123 */ 2124 setValue : function( value ) 2125 { 2126 this.getInputElement().setValue( value ); 2127 this.fire( 'change', { value : value } ); 2128 return this; 2129 }, 2130 2131 /** 2132 * Gets the current value of this dialog UI object. 2133 * @returns {Object} The current value. 2134 * @example 2135 * var myValue = uiElement.getValue(); 2136 */ 2137 getValue : function() 2138 { 2139 return this.getInputElement().getValue(); 2140 }, 2141 2142 /** 2143 * Tells whether the UI object's value has changed. 2144 * @returns {Boolean} true if changed, false if not changed. 2145 * @example 2146 * if ( uiElement.isChanged() ) 2147 * confirm( 'Value changed! Continue?' ); 2148 */ 2149 isChanged : function() 2150 { 2151 // Override in input classes. 2152 return false; 2153 }, 2154 2155 /** 2156 * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods. 2157 * @returns {CKEDITOR.dialog.uiElement} The current UI element. 2158 * @example 2159 * focus : function() 2160 * { 2161 * this.selectParentTab(); 2162 * // do something else. 2163 * } 2164 */ 2165 selectParentTab : function() 2166 { 2167 var element = this.getInputElement(), 2168 cursor = element, 2169 tabId; 2170 while ( ( cursor = cursor.getParent() ) && cursor.$.className.search( 'cke_dialog_page_contents' ) == -1 ) 2171 { /*jsl:pass*/ } 2172 2173 // Some widgets don't have parent tabs (e.g. OK and Cancel buttons). 2174 if ( !cursor ) 2175 return this; 2176 2177 tabId = cursor.getAttribute( 'name' ); 2178 2179 this._.dialog.selectPage( tabId ); 2180 return this; 2181 }, 2182 2183 /** 2184 * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page. 2185 * @returns {CKEDITOR.dialog.uiElement} The current UI element. 2186 * @example 2187 * uiElement.focus(); 2188 */ 2189 focus : function() 2190 { 2191 this.selectParentTab().getInputElement().focus(); 2192 return this; 2193 }, 2194 2195 /** 2196 * Registers the on* event handlers defined in the element definition. 2197 * The default behavior of this function is: 2198 * <ol> 2199 * <li> 2200 * If the on* event is defined in the class's eventProcesors list, 2201 * then the registration is delegated to the corresponding function 2202 * in the eventProcessors list. 2203 * </li> 2204 * <li> 2205 * If the on* event is not defined in the eventProcessors list, then 2206 * register the event handler under the corresponding DOM event of 2207 * the UI element's input DOM element (as defined by the return value 2208 * of {@link CKEDITOR.ui.dialog.uiElement#getInputElement}). 2209 * </li> 2210 * </ol> 2211 * This function is only called at UI element instantiation, but can 2212 * be overridded in child classes if they require more flexibility. 2213 * @param {CKEDITOR.dialog.uiElementDefinition} definition The UI element 2214 * definition. 2215 * @returns {CKEDITOR.dialog.uiElement} The current UI element. 2216 * @example 2217 */ 2218 registerEvents : function( definition ) 2219 { 2220 var regex = /^on([A-Z]\w+)/, 2221 match; 2222 2223 var registerDomEvent = function( uiElement, dialog, eventName, func ) 2224 { 2225 dialog.on( 'load', function() 2226 { 2227 uiElement.getInputElement().on( eventName, func, uiElement ); 2228 }); 2229 }; 2230 2231 for ( var i in definition ) 2232 { 2233 if ( !( match = i.match( regex ) ) ) 2234 continue; 2235 if ( this.eventProcessors[i] ) 2236 this.eventProcessors[i].call( this, this._.dialog, definition[i] ); 2237 else 2238 registerDomEvent( this, this._.dialog, match[1].toLowerCase(), definition[i] ); 2239 } 2240 2241 return this; 2242 }, 2243 2244 /** 2245 * The event processor list used by 2246 * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element 2247 * instantiation. The default list defines three on* events: 2248 * <ol> 2249 * <li>onLoad - Called when the element's parent dialog opens for the 2250 * first time</li> 2251 * <li>onShow - Called whenever the element's parent dialog opens.</li> 2252 * <li>onHide - Called whenever the element's parent dialog closes.</li> 2253 * </ol> 2254 * @field 2255 * @type Object 2256 * @example 2257 * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick 2258 * // handlers in the UI element's definitions. 2259 * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {}, 2260 * CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, 2261 * { onClick : function( dialog, func ) { this.on( 'click', func ); } }, 2262 * true ); 2263 */ 2264 eventProcessors : 2265 { 2266 onLoad : function( dialog, func ) 2267 { 2268 dialog.on( 'load', func, this ); 2269 }, 2270 2271 onShow : function( dialog, func ) 2272 { 2273 dialog.on( 'show', func, this ); 2274 }, 2275 2276 onHide : function( dialog, func ) 2277 { 2278 dialog.on( 'hide', func, this ); 2279 } 2280 }, 2281 2282 /** 2283 * The default handler for a UI element's access key down event, which 2284 * tries to put focus to the UI element.<br /> 2285 * Can be overridded in child classes for more sophisticaed behavior. 2286 * @param {CKEDITOR.dialog} dialog The parent dialog object. 2287 * @param {String} key The key combination pressed. Since access keys 2288 * are defined to always include the CTRL key, its value should always 2289 * include a 'CTRL+' prefix. 2290 * @example 2291 */ 2292 accessKeyDown : function( dialog, key ) 2293 { 2294 this.focus(); 2295 }, 2296 2297 /** 2298 * The default handler for a UI element's access key up event, which 2299 * does nothing.<br /> 2300 * Can be overridded in child classes for more sophisticated behavior. 2301 * @param {CKEDITOR.dialog} dialog The parent dialog object. 2302 * @param {String} key The key combination pressed. Since access keys 2303 * are defined to always include the CTRL key, its value should always 2304 * include a 'CTRL+' prefix. 2305 * @example 2306 */ 2307 accessKeyUp : function( dialog, key ) 2308 { 2309 }, 2310 2311 /** 2312 * Disables a UI element. 2313 * @example 2314 */ 2315 disable : function() 2316 { 2317 var element = this.getInputElement(); 2318 element.setAttribute( 'disabled', 'true' ); 2319 element.addClass( 'cke_disabled' ); 2320 }, 2321 2322 /** 2323 * Enables a UI element. 2324 * @example 2325 */ 2326 enable : function() 2327 { 2328 var element = this.getInputElement(); 2329 element.removeAttribute( 'disabled' ); 2330 element.removeClass( 'cke_disabled' ); 2331 }, 2332 2333 /** 2334 * Determines whether an UI element is enabled or not. 2335 * @returns {Boolean} Whether the UI element is enabled. 2336 * @example 2337 */ 2338 isEnabled : function() 2339 { 2340 return !this.getInputElement().getAttribute( 'disabled' ); 2341 }, 2342 2343 /** 2344 * Determines whether an UI element is visible or not. 2345 * @returns {Boolean} Whether the UI element is visible. 2346 * @example 2347 */ 2348 isVisible : function() 2349 { 2350 return !!this.getInputElement().$.offsetHeight; 2351 }, 2352 2353 /** 2354 * Determines whether an UI element is focus-able or not. 2355 * Focus-able is defined as being both visible and enabled. 2356 * @returns {Boolean} Whether the UI element can be focused. 2357 * @example 2358 */ 2359 isFocusable : function() 2360 { 2361 if ( !this.isEnabled() || !this.isVisible() ) 2362 return false; 2363 return true; 2364 } 2365 }; 2366 2367 CKEDITOR.ui.dialog.hbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, 2368 /** 2369 * @lends CKEDITOR.ui.dialog.hbox.prototype 2370 */ 2371 { 2372 /** 2373 * Gets a child UI element inside this container. 2374 * @param {Array|Number} indices An array or a single number to indicate the child's 2375 * position in the container's descendant tree. Omit to get all the children in an array. 2376 * @returns {Array|CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container 2377 * if no argument given, or the specified UI element if indices is given. 2378 * @example 2379 * var checkbox = hbox.getChild( [0,1] ); 2380 * checkbox.setValue( true ); 2381 */ 2382 getChild : function( indices ) 2383 { 2384 // If no arguments, return a clone of the children array. 2385 if ( arguments.length < 1 ) 2386 return this._.children.concat(); 2387 2388 // If indices isn't array, make it one. 2389 if ( !indices.splice ) 2390 indices = [ indices ]; 2391 2392 // Retrieve the child element according to tree position. 2393 if ( indices.length < 2 ) 2394 return this._.children[ indices[0] ]; 2395 else 2396 return ( this._.children[ indices[0] ] && this._.children[ indices[0] ].getChild ) ? 2397 this._.children[ indices[0] ].getChild( indices.slice( 1, indices.length ) ) : 2398 null; 2399 } 2400 }, true ); 2401 2402 CKEDITOR.ui.dialog.vbox.prototype = new CKEDITOR.ui.dialog.hbox(); 2403 2404 2405 2406 (function() 2407 { 2408 var commonBuilder = { 2409 build : function( dialog, elementDefinition, output ) 2410 { 2411 var children = elementDefinition.children, 2412 child, 2413 childHtmlList = [], 2414 childObjList = []; 2415 for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ ) 2416 { 2417 var childHtml = []; 2418 childHtmlList.push( childHtml ); 2419 childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); 2420 } 2421 return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, childObjList, childHtmlList, output, elementDefinition ); 2422 } 2423 }; 2424 2425 CKEDITOR.dialog.addUIElement( 'hbox', commonBuilder ); 2426 CKEDITOR.dialog.addUIElement( 'vbox', commonBuilder ); 2427 })(); 2428 2429 /** 2430 * Generic dialog command. It opens a specific dialog when executed. 2431 * @constructor 2432 * @augments CKEDITOR.commandDefinition 2433 * @param {string} dialogName The name of the dialog to open when executing 2434 * this command. 2435 * @example 2436 * // Register the "link" command, which opens the "link" dialog. 2437 * editor.addCommand( 'link', <b>new CKEDITOR.dialogCommand( 'link' )</b> ); 2438 */ 2439 CKEDITOR.dialogCommand = function( dialogName ) 2440 { 2441 this.dialogName = dialogName; 2442 }; 2443 2444 CKEDITOR.dialogCommand.prototype = 2445 { 2446 /** @ignore */ 2447 exec : function( editor ) 2448 { 2449 editor.openDialog( this.dialogName ); 2450 } 2451 }; 2452 2453 (function() 2454 { 2455 var notEmptyRegex = /^([a]|[^a])+$/, 2456 integerRegex = /^\d*$/, 2457 numberRegex = /^\d*(?:\.\d+)?$/; 2458 2459 CKEDITOR.VALIDATE_OR = 1; 2460 CKEDITOR.VALIDATE_AND = 2; 2461 2462 CKEDITOR.dialog.validate = 2463 { 2464 functions : function() 2465 { 2466 return function() 2467 { 2468 /** 2469 * It's important for validate functions to be able to accept the value 2470 * as argument in addition to this.getValue(), so that it is possible to 2471 * combine validate functions together to make more sophisticated 2472 * validators. 2473 */ 2474 var value = this && this.getValue ? this.getValue() : arguments[0]; 2475 2476 var msg = undefined, 2477 relation = CKEDITOR.VALIDATE_AND, 2478 functions = [], i; 2479 2480 for ( i = 0 ; i < arguments.length ; i++ ) 2481 { 2482 if ( typeof( arguments[i] ) == 'function' ) 2483 functions.push( arguments[i] ); 2484 else 2485 break; 2486 } 2487 2488 if ( i < arguments.length && typeof( arguments[i] ) == 'string' ) 2489 { 2490 msg = arguments[i]; 2491 i++; 2492 } 2493 2494 if ( i < arguments.length && typeof( arguments[i]) == 'number' ) 2495 relation = arguments[i]; 2496 2497 var passed = ( relation == CKEDITOR.VALIDATE_AND ? true : false ); 2498 for ( i = 0 ; i < functions.length ; i++ ) 2499 { 2500 if ( relation == CKEDITOR.VALIDATE_AND ) 2501 passed = passed && functions[i]( value ); 2502 else 2503 passed = passed || functions[i]( value ); 2504 } 2505 2506 if ( !passed ) 2507 { 2508 if ( msg !== undefined ) 2509 alert( msg ); 2510 if ( this && ( this.select || this.focus ) ) 2511 ( this.select || this.focus )(); 2512 return false; 2513 } 2514 2515 return true; 2516 }; 2517 }, 2518 2519 regex : function( regex, msg ) 2520 { 2521 /* 2522 * Can be greatly shortened by deriving from functions validator if code size 2523 * turns out to be more important than performance. 2524 */ 2525 return function() 2526 { 2527 var value = this && this.getValue ? this.getValue() : arguments[0]; 2528 if ( !regex.test( value ) ) 2529 { 2530 if ( msg !== undefined ) 2531 alert( msg ); 2532 if ( this && ( this.select || this.focus ) ) 2533 { 2534 if ( this.select ) 2535 this.select(); 2536 else 2537 this.focus(); 2538 } 2539 return false; 2540 } 2541 return true; 2542 }; 2543 }, 2544 2545 notEmpty : function( msg ) 2546 { 2547 return this.regex( notEmptyRegex, msg ); 2548 }, 2549 2550 integer : function( msg ) 2551 { 2552 return this.regex( integerRegex, msg ); 2553 }, 2554 2555 'number' : function( msg ) 2556 { 2557 return this.regex( numberRegex, msg ); 2558 }, 2559 2560 equals : function( value, msg ) 2561 { 2562 return this.functions( function( val ){ return val == value; }, msg ); 2563 }, 2564 2565 notEqual : function( value, msg ) 2566 { 2567 return this.functions( function( val ){ return val != value; }, msg ); 2568 } 2569 }; 2570 })(); 2571 2572 })(); 2573 2574 // Extend the CKEDITOR.editor class with dialog specific functions. 2575 CKEDITOR.tools.extend( CKEDITOR.editor.prototype, 2576 /** @lends CKEDITOR.editor.prototype */ 2577 { 2578 /** 2579 * Loads and opens a registered dialog. 2580 * @param {String} dialogName The registered name of the dialog. 2581 * @see CKEDITOR.dialog.add 2582 * @example 2583 * CKEDITOR.instances.editor1.openDialog( 'smiley' ); 2584 * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed. null if the dialog name is not registered. 2585 */ 2586 openDialog : function( dialogName ) 2587 { 2588 var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ]; 2589 2590 // If the dialogDefinition is already loaded, open it immediately. 2591 if ( typeof dialogDefinitions == 'function' ) 2592 { 2593 var storedDialogs = this._.storedDialogs || 2594 ( this._.storedDialogs = {} ); 2595 2596 var dialog = storedDialogs[ dialogName ] || 2597 ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) ); 2598 2599 dialog.show(); 2600 2601 return dialog; 2602 } 2603 2604 // Not loaded? Load the .js file first. 2605 var body = CKEDITOR.document.getBody(), 2606 cursor = body.$.style.cursor, 2607 me = this; 2608 2609 body.setStyle( 'cursor', 'wait' ); 2610 CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), function() 2611 { 2612 me.openDialog( dialogName ); 2613 body.setStyle( 'cursor', cursor ); 2614 } ); 2615 2616 return null; 2617 } 2618 }); 2619 2620 // Dialog related configurations. 2621 2622 /** 2623 * The color of the dialog background cover. It should be a valid CSS color 2624 * string. 2625 * @type String 2626 * @default white 2627 * @example 2628 * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)'; 2629 */ 2630 CKEDITOR.config.dialog_backgroundCoverColor = 'white'; 2631 2632 /** 2633 * The opacity of the dialog background cover. It should be a number within the 2634 * range [0.0, 1.0]. 2635 * @type Number 2636 * @default 0.5 2637 * @example 2638 * config.dialog_backgroundCoverOpacity = 0.7; 2639 */ 2640 CKEDITOR.config.dialog_backgroundCoverOpacity = 0.5; 2641 2642 /** 2643 * The distance of magnetic borders used in moving and resizing dialogs, 2644 * measured in pixels. 2645 * @type Number 2646 * @default 20 2647 * @example 2648 * config.dialog_magnetDistance = 30; 2649 */ 2650 CKEDITOR.config.dialog_magnetDistance = 20; 2651