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