1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 CKEDITOR.plugins.add( 'floatpanel',
  7 {
  8 	requires : [ 'panel' ]
  9 });
 10
 11 (function()
 12 {
 13 	var panels = {};
 14 	var isShowing = false;
 15
 16 	function getPanel( editor, doc, parentElement, definition, level )
 17 	{
 18 		// Generates the panel key: docId-eleId-skinName-langDir[-uiColor][-CSSs][-level]
 19 		var key =
 20 			doc.getUniqueId() +
 21 			'-' + parentElement.getUniqueId() +
 22 			'-' + editor.skinName +
 23 			'-' + editor.lang.dir +
 24 			( ( editor.uiColor && ( '-' + editor.uiColor ) ) || '' ) +
 25 			( ( definition.css && ( '-' + definition.css ) ) || '' ) +
 26 			( ( level && ( '-' + level ) ) || '' );
 27
 28 		var panel = panels[ key ];
 29
 30 		if ( !panel )
 31 		{
 32 			panel = panels[ key ] = new CKEDITOR.ui.panel( doc, definition );
 33 			panel.element = parentElement.append( CKEDITOR.dom.element.createFromHtml( panel.renderHtml( editor ), doc ) );
 34
 35 			panel.element.setStyles(
 36 				{
 37 					display : 'none',
 38 					position : 'absolute'
 39 				});
 40 		}
 41
 42 		return panel;
 43 	}
 44
 45 	CKEDITOR.ui.floatPanel = CKEDITOR.tools.createClass(
 46 	{
 47 		$ : function( editor, parentElement, definition, level )
 48 		{
 49 			definition.forceIFrame = true;
 50
 51 			var doc = parentElement.getDocument(),
 52 				panel = getPanel( editor, doc, parentElement, definition, level || 0 ),
 53 				element = panel.element,
 54 				iframe = element.getFirst().getFirst();
 55
 56 			this.element = element;
 57
 58 			this._ =
 59 			{
 60 				// The panel that will be floating.
 61 				panel : panel,
 62 				parentElement : parentElement,
 63 				definition : definition,
 64 				document : doc,
 65 				iframe : iframe,
 66 				children : [],
 67 				dir : editor.lang.dir
 68 			};
 69 		},
 70
 71 		proto :
 72 		{
 73 			addBlock : function( name, block )
 74 			{
 75 				return this._.panel.addBlock( name, block );
 76 			},
 77
 78 			addListBlock : function( name, multiSelect )
 79 			{
 80 				return this._.panel.addListBlock( name, multiSelect );
 81 			},
 82
 83 			getBlock : function( name )
 84 			{
 85 				return this._.panel.getBlock( name );
 86 			},
 87
 88 			/*
 89 				corner (LTR):
 90 					1 = top-left
 91 					2 = top-right
 92 					3 = bottom-right
 93 					4 = bottom-left
 94
 95 				corner (RTL):
 96 					1 = top-right
 97 					2 = top-left
 98 					3 = bottom-left
 99 					4 = bottom-right
100 			 */
101 			showBlock : function( name, offsetParent, corner, offsetX, offsetY )
102 			{
103 				var panel = this._.panel,
104 					block = panel.showBlock( name );
105
106 				this.allowBlur( false );
107 				isShowing = true;
108
109 				var element = this.element,
110 					iframe = this._.iframe,
111 					definition = this._.definition,
112 					position = offsetParent.getDocumentPosition( element.getDocument() ),
113 					rtl = this._.dir == 'rtl';
114
115 				var left	= position.x + ( offsetX || 0 ),
116 					top		= position.y + ( offsetY || 0 );
117
118 				if ( ( rtl && ( corner == 1 || corner == 4 ) ) || ( !rtl && ( corner == 2 || corner == 3 ) ) )
119 					left += offsetParent.$.offsetWidth - 1;
120
121 				if ( corner == 3 || corner == 4 )
122 					top += offsetParent.$.offsetHeight - 1;
123
124 				element.setStyles(
125 					{
126 						top : top + 'px',
127 						left : '-3000px',
128 						visibility : 'hidden',
129 						opacity : '0',	// FF3 is ignoring "visibility"
130 						display	: ''
131 					});
132
133 				// Configure the IFrame blur event. Do that only once.
134 				if ( !this._.blurSet )
135 				{
136 					// Non IE prefer the event into a window object.
137 					var focused = CKEDITOR.env.ie ? iframe : new CKEDITOR.dom.window( iframe.$.contentWindow );
138
139 					// With addEventListener compatible browsers, we must
140 					// useCapture when registering the focus/blur events to
141 					// guarantee they will be firing in all situations. (#3068, #3222 )
142 					CKEDITOR.event.useCapture = true;
143
144 					focused.on( 'blur', function( ev )
145 						{
146 							if ( CKEDITOR.env.ie && !this.allowBlur() )
147 								return;
148
149 							// As we are using capture to register the listener,
150 							// the blur event may get fired even when focusing
151 							// inside the window itself, so we must ensure the
152 							// target is out of it.
153 							var target = ev.data.getTarget(),
154 								targetWindow = target.getWindow && target.getWindow();
155
156 							if ( targetWindow && targetWindow.equals( focused ) )
157 								return;
158
159 							if ( this.visible && !this._.activeChild && !isShowing )
160 								this.hide();
161 						},
162 						this );
163
164 					focused.on( 'focus', function()
165 						{
166 							this._.focused = true;
167 							this.hideChild();
168 							this.allowBlur( true );
169 						},
170 						this );
171
172 					CKEDITOR.event.useCapture = false;
173
174 					this._.blurSet = 1;
175 				}
176
177 				panel.onEscape = CKEDITOR.tools.bind( function()
178 					{
179 						this.onEscape && this.onEscape();
180 					},
181 					this );
182
183 				setTimeout( function()
184 					{
185 						if ( rtl )
186 							left -= element.$.offsetWidth;
187
188 						element.setStyles(
189 							{
190 								left : left + 'px',
191 								visibility	: '',
192 								opacity : '1'	// FF3 is ignoring "visibility"
193 							});
194
195 						if ( block.autoSize )
196 						{
197 							function setHeight()
198 							{
199 								var target = element.getFirst();
200 								var height = block.element.$.scrollHeight;
201
202 								// Account for extra height needed due to IE quirks box model bug:
203 								// http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug
204 								// (#3426)
205 								if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && height > 0 )
206 									height += ( target.$.offsetHeight || 0 ) - ( target.$.clientHeight || 0 );
207
208 								target.setStyle( 'height', height + 'px' );
209
210 								// Fix IE < 8 visibility.
211 								panel._.currentBlock.element.setStyle( 'display', 'none' ).removeStyle( 'display' );
212 							}
213
214 							if ( !CKEDITOR.env.gecko || panel.isLoaded )
215 							{
216 								// IE7 needs some time (setting the delay to 0ms won't work) to refresh
217 								// the scrollHeight. (#3174)
218 								if ( CKEDITOR.env.ie && CKEDITOR.env.version >= 7 )
219 									setTimeout( setHeight, 50 );
220 								else
221 									setHeight();
222 							}
223 							else
224 								panel.onLoad = setHeight;
225 						}
226 						else
227 							element.getFirst().removeStyle( 'height' );
228
229 						// Set the IFrame focus, so the blur event gets fired.
230 						setTimeout( function()
231 							{
232 								if ( definition.voiceLabel )
233 								{
234 									if ( CKEDITOR.env.gecko )
235 									{
236 										var container = iframe.getParent();
237 										container.setAttribute( 'role', 'region' );
238 										container.setAttribute( 'title', definition.voiceLabel );
239 										iframe.setAttribute( 'role', 'region' );
240 										iframe.setAttribute( 'title', ' ' );
241 									}
242 								}
243 								if ( CKEDITOR.env.ie && CKEDITOR.env.quirks )
244 									iframe.focus();
245 								else
246 									iframe.$.contentWindow.focus();
247 							}, 0);
248 					}, 0);
249
250 				this.visible = 1;
251
252 				if ( this.onShow )
253 					this.onShow.call( this );
254
255 				isShowing = false;
256 			},
257
258 			hide : function()
259 			{
260 				if ( this.visible && ( !this.onHide || this.onHide.call( this ) !== true ) )
261 				{
262 					this.hideChild();
263 					this.element.setStyle( 'display', 'none' );
264 					this.visible = 0;
265 				}
266 			},
267
268 			allowBlur : function( allow )	// Prevent editor from hiding the panel. #3222.
269 			{
270 				var panel = this._.panel;
271 				if ( allow != undefined )
272 					panel.allowBlur = allow;
273
274 				return panel.allowBlur;
275 			},
276
277 			showAsChild : function( panel, blockName, offsetParent, corner, offsetX, offsetY )
278 			{
279 				this.hideChild();
280
281 				panel.onHide = CKEDITOR.tools.bind( function()
282 					{
283 						// Use a timeout, so we give time for this menu to get
284 						// potentially focused.
285 						CKEDITOR.tools.setTimeout( function()
286 							{
287 								if ( !this._.focused )
288 									this.hide();
289 							},
290 							0, this );
291 					},
292 					this );
293
294 				this._.activeChild = panel;
295 				this._.focused = false;
296
297 				panel.showBlock( blockName, offsetParent, corner, offsetX, offsetY );
298
299 				/* #3767 IE: Second level menu may not have borders */
300 				if ( CKEDITOR.env.ie7Compat || ( CKEDITOR.env.ie8 && CKEDITOR.env.ie6Compat ) )
301 				{
302 					setTimeout(function()
303 						{
304 							panel.element.getChild( 0 ).$.style.cssText += '';
305 						}, 100);
306 				}
307 			},
308
309 			hideChild : function()
310 			{
311 				var activeChild = this._.activeChild;
312
313 				if ( activeChild )
314 				{
315 					delete activeChild.onHide;
316 					delete this._.activeChild;
317 					activeChild.hide();
318 				}
319 			}
320 		}
321 	});
322 })();
323