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