Bugfix : prendre le innerHTML du body du document n'est pas une bonne idée, dans...
[ckeditor.git] / skins / ckeditor / _source / plugins / richcombo / plugin.js
1 /*
2 Copyright (c) 2003-2011, 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 ? '&#9660;' : CKEDITOR.env.air ? '&nbsp;' : '' ) + '</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 };