source: trunk/admin/inc/ckeditor/_source/plugins/dialog/plugin.js@ 345

Last change on this file since 345 was 239, checked in by luc, 9 years ago

Admin: correzione visulaizzazione immissione dati spoglio per Chrome e Safari - Aggiornamento dell'editor da FCKeditor a CKeditor , accessibili anche a Chrome e Safari.

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