1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 CKEDITOR.plugins.add( 'richcombo',
  7 {
  8 	requires : [ 'floatpanel', 'listblock', 'button' ],
  9 
 10 	beforeInit : function( editor )
 11 	{
 12 		editor.ui.addHandler( CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler );
 13 	}
 14 });
 15 
 16 /**
 17  * Button UI element.
 18  * @constant
 19  * @example
 20  */
 21 CKEDITOR.UI_RICHCOMBO = 'richcombo';
 22 
 23 CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass(
 24 {
 25 	$ : function( definition )
 26 	{
 27 		// Copy all definition properties to this object.
 28 		CKEDITOR.tools.extend( this, definition,
 29 			// Set defaults.
 30 			{
 31 				title : definition.label,
 32 				modes : { wysiwyg : 1 }
 33 			});
 34 
 35 		// We don't want the panel definition in this object.
 36 		var panelDefinition = this.panel || {};
 37 		delete this.panel;
 38 
 39 		this.id = CKEDITOR.tools.getNextNumber();
 40 
 41 		this.document = ( panelDefinition
 42 							&& panelDefinition.parent
 43 							&& panelDefinition.parent.getDocument() )
 44 						|| CKEDITOR.document;
 45 
 46 		panelDefinition.className = ( panelDefinition.className || '' ) + ' cke_rcombopanel';
 47 		panelDefinition.block =
 48 		{
 49 			multiSelect : panelDefinition.multiSelect,
 50 			attributes : panelDefinition.attributes
 51 		};
 52 
 53 		this._ =
 54 		{
 55 			panelDefinition : panelDefinition,
 56 			items : {},
 57 			state : CKEDITOR.TRISTATE_OFF
 58 		};
 59 	},
 60 
 61 	statics :
 62 	{
 63 		handler :
 64 		{
 65 			create : function( definition )
 66 			{
 67 				return new CKEDITOR.ui.richCombo( definition );
 68 			}
 69 		}
 70 	},
 71 
 72 	proto :
 73 	{
 74 		renderHtml : function( editor )
 75 		{
 76 			var output = [];
 77 			this.render( editor, output );
 78 			return output.join( '' );
 79 		},
 80 
 81 		/**
 82 		 * Renders the combo.
 83 		 * @param {CKEDITOR.editor} editor The editor instance which this button is
 84 		 *		to be used by.
 85 		 * @param {Array} output The output array to which append the HTML relative
 86 		 *		to this button.
 87 		 * @example
 88 		 */
 89 		render : function( editor, output )
 90 		{
 91 			var env = CKEDITOR.env;
 92 
 93 			var id = 'cke_' + this.id;
 94 			var clickFn = CKEDITOR.tools.addFunction( function( $element )
 95 				{
 96 					var _ = this._;
 97 
 98 					if ( _.state == CKEDITOR.TRISTATE_DISABLED )
 99 						return;
100 
101 					this.createPanel( editor );
102 
103 					if ( _.on )
104 					{
105 						_.panel.hide();
106 						return;
107 					}
108 
109 					this.commit();
110 					var value = this.getValue();
111 					if ( value )
112 						_.list.mark( value );
113 					else
114 						_.list.unmarkAll();
115 
116 					_.panel.showBlock( this.id, new CKEDITOR.dom.element( $element ), 4 );
117 				},
118 				this );
119 
120 			var instance = {
121 				id : id,
122 				combo : this,
123 				focus : function()
124 				{
125 					var element = CKEDITOR.document.getById( id ).getChild( 1 );
126 					element.focus();
127 				},
128 				clickFn : clickFn
129 			};
130 
131 			function updateState()
132 			{
133 				var state = this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
134 				this.setState( editor.readOnly && !this.readOnly ? CKEDITOR.TRISTATE_DISABLED : state );
135 				this.setValue( '' );
136 			}
137 
138 			editor.on( 'mode', updateState, this );
139 			// If this combo is sensitive to readOnly state, update it accordingly.
140 			!this.readOnly && editor.on( 'readOnly', updateState, this);
141 
142 			var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element )
143 				{
144 					ev = new CKEDITOR.dom.event( ev );
145 
146 					var keystroke = ev.getKeystroke();
147 					switch ( keystroke )
148 					{
149 						case 13 :	// ENTER
150 						case 32 :	// SPACE
151 						case 40 :	// ARROW-DOWN
152 							// Show panel
153 							CKEDITOR.tools.callFunction( clickFn, element );
154 							break;
155 						default :
156 							// Delegate the default behavior to toolbar button key handling.
157 							instance.onkey( instance,  keystroke );
158 					}
159 
160 					// Avoid subsequent focus grab on editor document.
161 					ev.preventDefault();
162 				});
163 
164 			var focusFn = CKEDITOR.tools.addFunction( function() { instance.onfocus && instance.onfocus(); } );
165 
166 			// For clean up
167 			instance.keyDownFn = keyDownFn;
168 
169 			output.push(
170 				'<span class="cke_rcombo" role="presentation">',
171 				'<span id=', id );
172 
173 			if ( this.className )
174 				output.push( ' class="', this.className, ' cke_off"');
175 
176 			output.push(
177 				' role="presentation">',
178 					'<span id="' + id+ '_label" class=cke_label>', this.label, '</span>',
179 					'<a hidefocus=true title="', this.title, '" tabindex="-1"',
180 						env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(\'' + this.label + '\')"',
181 						' role="button" aria-labelledby="', id , '_label" aria-describedby="', id, '_text" aria-haspopup="true"' );
182 
183 			// Some browsers don't cancel key events in the keydown but in the
184 			// keypress.
185 			// TODO: Check if really needed for Gecko+Mac.
186 			if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
187 			{
188 				output.push(
189 					' onkeypress="return false;"' );
190 			}
191 
192 			// With Firefox, we need to force it to redraw, otherwise it
193 			// will remain in the focus state.
194 			if ( CKEDITOR.env.gecko )
195 			{
196 				output.push(
197 					' onblur="this.style.cssText = this.style.cssText;"' );
198 			}
199 
200 			output.push(
201 					' onkeydown="CKEDITOR.tools.callFunction( ', keyDownFn, ', event, this );"' +
202 					' onfocus="return CKEDITOR.tools.callFunction(', focusFn, ', event);" ' +
203 					( CKEDITOR.env.ie ? 'onclick="return false;" onmouseup' : 'onclick' ) +		// #188
204 						'="CKEDITOR.tools.callFunction(', clickFn, ', this); return false;">' +
205 						'<span>' +
206 							'<span id="' + id + '_text" class="cke_text cke_inline_label">' + this.label + '</span>' +
207 						'</span>' +
208 						'<span class=cke_openbutton><span class=cke_icon>' + ( CKEDITOR.env.hc ? '▼' : CKEDITOR.env.air ?  ' ' : '' ) + '</span></span>' +	// BLACK DOWN-POINTING TRIANGLE
209 					'</a>' +
210 				'</span>' +
211 				'</span>' );
212 
213 			if ( this.onRender )
214 				this.onRender();
215 
216 			return instance;
217 		},
218 
219 		createPanel : function( editor )
220 		{
221 			if ( this._.panel )
222 				return;
223 
224 			var panelDefinition = this._.panelDefinition,
225 				panelBlockDefinition = this._.panelDefinition.block,
226 				panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(),
227 				panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ),
228 				list = panel.addListBlock( this.id, panelBlockDefinition ),
229 				me = this;
230 
231 			panel.onShow = function()
232 				{
233 					if ( me.className )
234 						this.element.getFirst().addClass( me.className + '_panel' );
235 
236 					me.setState( CKEDITOR.TRISTATE_ON );
237 
238 					list.focus( !me.multiSelect && me.getValue() );
239 
240 					me._.on = 1;
241 
242 					if ( me.onOpen )
243 						me.onOpen();
244 				};
245 
246 			panel.onHide = function( preventOnClose )
247 				{
248 					if ( me.className )
249 						this.element.getFirst().removeClass( me.className + '_panel' );
250 
251 					me.setState( me.modes && me.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
252 
253 					me._.on = 0;
254 
255 					if ( !preventOnClose && me.onClose )
256 						me.onClose();
257 				};
258 
259 			panel.onEscape = function()
260 				{
261 					panel.hide();
262 				};
263 
264 			list.onClick = function( value, marked )
265 				{
266 					// Move the focus to the main windows, otherwise it will stay
267 					// into the floating panel, even if invisible, and Safari and
268 					// Opera will go a bit crazy.
269 					me.document.getWindow().focus();
270 
271 					if ( me.onClick )
272 						me.onClick.call( me, value, marked );
273 
274 					if ( marked )
275 						me.setValue( value, me._.items[ value ] );
276 					else
277 						me.setValue( '' );
278 
279 					panel.hide( false );
280 				};
281 
282 			this._.panel = panel;
283 			this._.list = list;
284 
285 			panel.getBlock( this.id ).onHide = function()
286 				{
287 					me._.on = 0;
288 					me.setState( CKEDITOR.TRISTATE_OFF );
289 				};
290 
291 			if ( this.init )
292 				this.init();
293 		},
294 
295 		setValue : function( value, text )
296 		{
297 			this._.value = value;
298 
299 			var textElement = this.document.getById( 'cke_' + this.id + '_text' );
300 			if ( textElement )
301 			{
302 				if ( !( value || text ) )
303 				{
304 					text = this.label;
305 					textElement.addClass( 'cke_inline_label' );
306 				}
307 				else
308 					textElement.removeClass( 'cke_inline_label' );
309 
310 				textElement.setHtml( typeof text != 'undefined' ? text : value );
311 			}
312 		},
313 
314 		getValue : function()
315 		{
316 			return this._.value || '';
317 		},
318 
319 		unmarkAll : function()
320 		{
321 			this._.list.unmarkAll();
322 		},
323 
324 		mark : function( value )
325 		{
326 			this._.list.mark( value );
327 		},
328 
329 		hideItem : function( value )
330 		{
331 			this._.list.hideItem( value );
332 		},
333 
334 		hideGroup : function( groupTitle )
335 		{
336 			this._.list.hideGroup( groupTitle );
337 		},
338 
339 		showAll : function()
340 		{
341 			this._.list.showAll();
342 		},
343 
344 		add : function( value, html, text )
345 		{
346 			this._.items[ value ] = text || value;
347 			this._.list.add( value, html, text );
348 		},
349 
350 		startGroup : function( title )
351 		{
352 			this._.list.startGroup( title );
353 		},
354 
355 		commit : function()
356 		{
357 			if ( !this._.committed )
358 			{
359 				this._.list.commit();
360 				this._.committed = 1;
361 				CKEDITOR.ui.fire( 'ready', this );
362 			}
363 			this._.committed = 1;
364 		},
365 
366 		setState : function( state )
367 		{
368 			if ( this._.state == state )
369 				return;
370 
371 			this.document.getById( 'cke_' + this.id ).setState( state );
372 
373 			this._.state = state;
374 		}
375 	}
376 });
377 
378 CKEDITOR.ui.prototype.addRichCombo = function( name, definition )
379 {
380 	this.add( name, CKEDITOR.UI_RICHCOMBO, definition );
381 };
382