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