2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
6 CKEDITOR
.plugins
.add( 'floatpanel',
14 var isShowing
= false;
16 function getPanel( editor
, doc
, parentElement
, definition
, level
)
18 // Generates the panel key: docId-eleId-skinName-langDir[-uiColor][-CSSs][-level]
19 var key
= CKEDITOR
.tools
.genKey( doc
.getUniqueId(), parentElement
.getUniqueId(), editor
.skinName
, editor
.lang
.dir
,
20 editor
.uiColor
|| '', definition
.css
|| '', level
|| '' );
22 var panel
= panels
[ key
];
26 panel
= panels
[ key
] = new CKEDITOR
.ui
.panel( doc
, definition
);
27 panel
.element
= parentElement
.append( CKEDITOR
.dom
.element
.createFromHtml( panel
.renderHtml( editor
), doc
) );
29 panel
.element
.setStyles(
39 CKEDITOR
.ui
.floatPanel
= CKEDITOR
.tools
.createClass(
41 $ : function( editor
, parentElement
, definition
, level
)
43 definition
.forceIFrame
= 1;
45 var doc
= parentElement
.getDocument(),
46 panel
= getPanel( editor
, doc
, parentElement
, definition
, level
|| 0 ),
47 element
= panel
.element
,
48 iframe
= element
.getFirst().getFirst();
50 this.element
= element
;
55 // The panel that will be floating.
57 parentElement
: parentElement
,
58 definition
: definition
,
65 editor
.on( 'mode', function(){ this.hide(); }, this );
70 addBlock : function( name
, block
)
72 return this._
.panel
.addBlock( name
, block
);
75 addListBlock : function( name
, multiSelect
)
77 return this._
.panel
.addListBlock( name
, multiSelect
);
80 getBlock : function( name
)
82 return this._
.panel
.getBlock( name
);
98 showBlock : function( name
, offsetParent
, corner
, offsetX
, offsetY
)
100 var panel
= this._
.panel
,
101 block
= panel
.showBlock( name
);
103 this.allowBlur( false );
106 // Record from where the focus is when open panel.
107 this._
.returnFocus
= this._
.editor
.focusManager
.hasFocus
? this._
.editor
: new CKEDITOR
.dom
.element( CKEDITOR
.document
.$.activeElement
);
110 var element
= this.element
,
111 iframe
= this._
.iframe
,
112 definition
= this._
.definition
,
113 position
= offsetParent
.getDocumentPosition( element
.getDocument() ),
114 rtl
= this._
.dir
== 'rtl';
116 var left
= position
.x
+ ( offsetX
|| 0 ),
117 top
= position
.y
+ ( offsetY
|| 0 );
119 // Floating panels are off by (-1px, 0px) in RTL mode. (#3438)
120 if ( rtl
&& ( corner
== 1 || corner
== 4 ) )
121 left
+= offsetParent
.$.offsetWidth
;
122 else if ( !rtl
&& ( corner
== 2 || corner
== 3 ) )
123 left
+= offsetParent
.$.offsetWidth
- 1;
125 if ( corner
== 3 || corner
== 4 )
126 top
+= offsetParent
.$.offsetHeight
- 1;
128 // Memorize offsetParent by it's ID.
129 this._
.panel
._
.offsetParentId
= offsetParent
.getId();
138 // Don't use display or visibility style because we need to
139 // calculate the rendering layout later and focus the element.
140 element
.setOpacity( 0 );
142 // To allow the context menu to decrease back their width
143 element
.getFirst().removeStyle( 'width' );
145 // Configure the IFrame blur event. Do that only once.
146 if ( !this._
.blurSet
)
148 // Non IE prefer the event into a window object.
149 var focused
= CKEDITOR
.env
.ie
? iframe
: new CKEDITOR
.dom
.window( iframe
.$.contentWindow
);
151 // With addEventListener compatible browsers, we must
152 // useCapture when registering the focus/blur events to
153 // guarantee they will be firing in all situations. (#3068, #3222 )
154 CKEDITOR
.event
.useCapture
= true;
156 focused
.on( 'blur', function( ev
)
158 if ( !this.allowBlur() )
161 // As we are using capture to register the listener,
162 // the blur event may get fired even when focusing
163 // inside the window itself, so we must ensure the
164 // target is out of it.
165 var target
= ev
.data
.getTarget() ;
166 if ( target
.getName
&& target
.getName() != 'iframe' )
169 if ( this.visible
&& !this._
.activeChild
&& !isShowing
)
171 // Panel close is caused by user's navigating away the focus, e.g. click outside the panel.
172 // DO NOT restore focus in this case.
173 delete this._
.returnFocus
;
179 focused
.on( 'focus', function()
181 this._
.focused
= true;
183 this.allowBlur( true );
187 CKEDITOR
.event
.useCapture
= false;
192 panel
.onEscape
= CKEDITOR
.tools
.bind( function( keystroke
)
194 if ( this.onEscape
&& this.onEscape( keystroke
) === false )
199 CKEDITOR
.tools
.setTimeout( function()
202 left
-= element
.$.offsetWidth
;
204 var panelLoad
= CKEDITOR
.tools
.bind( function ()
206 var target
= element
.getFirst();
208 if ( block
.autoSize
)
210 // We must adjust first the width or IE6 could include extra lines in the height computation
211 var widthNode
= block
.element
.$;
213 if ( CKEDITOR
.env
.gecko
|| CKEDITOR
.env
.opera
)
214 widthNode
= widthNode
.parentNode
;
216 if ( CKEDITOR
.env
.ie
)
217 widthNode
= widthNode
.document
.body
;
219 var width
= widthNode
.scrollWidth
;
220 // Account for extra height needed due to IE quirks box model bug:
221 // http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug
223 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
&& width
> 0 )
224 width
+= ( target
.$.offsetWidth
|| 0 ) - ( target
.$.clientWidth
|| 0 ) + 3;
225 // A little extra at the end.
226 // If not present, IE6 might break into the next line, but also it looks better this way
229 target
.setStyle( 'width', width
+ 'px' );
231 // IE doesn't compute the scrollWidth if a filter is applied previously
232 block
.element
.addClass( 'cke_frameLoaded' );
234 var height
= block
.element
.$.scrollHeight
;
236 // Account for extra height needed due to IE quirks box model bug:
237 // http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug
239 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
&& height
> 0 )
240 height
+= ( target
.$.offsetHeight
|| 0 ) - ( target
.$.clientHeight
|| 0 ) + 3;
242 target
.setStyle( 'height', height
+ 'px' );
244 // Fix IE < 8 visibility.
245 panel
._
.currentBlock
.element
.setStyle( 'display', 'none' ).removeStyle( 'display' );
248 target
.removeStyle( 'height' );
250 var panelElement
= panel
.element
,
251 panelWindow
= panelElement
.getWindow(),
252 windowScroll
= panelWindow
.getScrollPosition(),
253 viewportSize
= panelWindow
.getViewPaneSize(),
256 'height' : panelElement
.$.offsetHeight
,
257 'width' : panelElement
.$.offsetWidth
260 // If the menu is horizontal off, shift it toward
261 // the opposite language direction.
262 if ( rtl
? left
< 0 : left
+ panelSize
.width
> viewportSize
.width
+ windowScroll
.x
)
263 left
+= ( panelSize
.width
* ( rtl
? 1 : -1 ) );
265 // Vertical off screen is simpler.
266 if ( top
+ panelSize
.height
> viewportSize
.height
+ windowScroll
.y
)
267 top
-= panelSize
.height
;
269 // If IE is in RTL, we have troubles with absolute
270 // position and horizontal scrolls. Here we have a
271 // series of hacks to workaround it. (#6146)
272 if ( CKEDITOR
.env
.ie
)
274 var offsetParent
= new CKEDITOR
.dom
.element( element
.$.offsetParent
),
275 scrollParent
= offsetParent
;
277 // Quirks returns <body>, but standards returns <html>.
278 if ( scrollParent
.getName() == 'html' )
279 scrollParent
= scrollParent
.getDocument().getBody();
281 if ( scrollParent
.getComputedStyle( 'direction' ) == 'rtl' )
283 // For IE8, there is not much logic on this, but it works.
284 if ( CKEDITOR
.env
.ie8Compat
)
285 left
-= element
.getDocument().getDocumentElement().$.scrollLeft
* 2;
287 left
-= ( offsetParent
.$.scrollWidth
- offsetParent
.$.clientWidth
);
291 // Trigger the onHide event of the previously active panel to prevent
292 // incorrect styles from being applied (#6170)
293 var innerElement
= element
.getFirst(),
295 if ( ( activePanel
= innerElement
.getCustomData( 'activePanel' ) ) )
296 activePanel
.onHide
&& activePanel
.onHide
.call( this, 1 );
297 innerElement
.setCustomData( 'activePanel', this );
304 element
.setOpacity( 1 );
307 panel
.isLoaded
? panelLoad() : panel
.onLoad
= panelLoad
;
309 // Set the panel frame focus, so the blur event gets fired.
310 CKEDITOR
.tools
.setTimeout( function()
312 iframe
.$.contentWindow
.focus();
313 // We need this get fired manually because of unfired focus() function.
314 this.allowBlur( true );
316 }, CKEDITOR
.env
.air
? 200 : 0, this);
320 this.onShow
.call( this );
325 hide : function( returnFocus
)
327 if ( this.visible
&& ( !this.onHide
|| this.onHide
.call( this ) !== true ) )
330 // Blur previously focused element. (#6671)
331 CKEDITOR
.env
.gecko
&& this._
.iframe
.getFrameDocument().$.activeElement
.blur();
332 this.element
.setStyle( 'display', 'none' );
334 this.element
.getFirst().removeCustomData( 'activePanel' );
336 // Return focus properly. (#6247)
337 var focusReturn
= returnFocus
!== false && this._
.returnFocus
;
340 // Webkit requires focus moved out panel iframe first.
341 if ( CKEDITOR
.env
.webkit
&& focusReturn
.type
)
342 focusReturn
.getWindow().$.focus();
349 allowBlur : function( allow
) // Prevent editor from hiding the panel. #3222.
351 var panel
= this._
.panel
;
352 if ( allow
!= undefined )
353 panel
.allowBlur
= allow
;
355 return panel
.allowBlur
;
358 showAsChild : function( panel
, blockName
, offsetParent
, corner
, offsetX
, offsetY
)
360 // Skip reshowing of child which is already visible.
361 if ( this._
.activeChild
== panel
&& panel
._
.panel
._
.offsetParentId
== offsetParent
.getId() )
366 panel
.onHide
= CKEDITOR
.tools
.bind( function()
368 // Use a timeout, so we give time for this menu to get
369 // potentially focused.
370 CKEDITOR
.tools
.setTimeout( function()
372 if ( !this._
.focused
)
379 this._
.activeChild
= panel
;
380 this._
.focused
= false;
382 panel
.showBlock( blockName
, offsetParent
, corner
, offsetX
, offsetY
);
384 /* #3767 IE: Second level menu may not have borders */
385 if ( CKEDITOR
.env
.ie7Compat
|| ( CKEDITOR
.env
.ie8
&& CKEDITOR
.env
.ie6Compat
) )
387 setTimeout(function()
389 panel
.element
.getChild( 0 ).$.style
.cssText
+= '';
394 hideChild : function()
396 var activeChild
= this._
.activeChild
;
400 delete activeChild
.onHide
;
401 // Sub panels don't manage focus. (#7881)
402 delete activeChild
._
.returnFocus
;
403 delete this._
.activeChild
;
410 CKEDITOR
.on( 'instanceDestroyed', function()
412 var isLastInstance
= CKEDITOR
.tools
.isEmpty( CKEDITOR
.instances
);
414 for ( var i
in panels
)
416 var panel
= panels
[ i
];
417 // Safe to destroy it since there're no more instances.(#4241)
418 if ( isLastInstance
)
420 // Panel might be used by other instances, just hide them.(#4552)
422 panel
.element
.hide();
424 // Remove the registration.
425 isLastInstance
&& ( panels
= {} );