]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blob - sources/plugins/dialogui/plugin.js
Update to 4.7.3
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / plugins / dialogui / plugin.js
1 /**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 /**
7 * @fileOverview The Dialog User Interface plugin.
8 */
9
10 CKEDITOR.plugins.add( 'dialogui', {
11 onLoad: function() {
12
13 var initPrivateObject = function( elementDefinition ) {
14 this._ || ( this._ = {} );
15 this._[ 'default' ] = this._.initValue = elementDefinition[ 'default' ] || '';
16 this._.required = elementDefinition.required || false;
17 var args = [ this._ ];
18 for ( var i = 1; i < arguments.length; i++ )
19 args.push( arguments[ i ] );
20 args.push( true );
21 CKEDITOR.tools.extend.apply( CKEDITOR.tools, args );
22 return this._;
23 },
24 textBuilder = {
25 build: function( dialog, elementDefinition, output ) {
26 return new CKEDITOR.ui.dialog.textInput( dialog, elementDefinition, output );
27 }
28 },
29 commonBuilder = {
30 build: function( dialog, elementDefinition, output ) {
31 return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, elementDefinition, output );
32 }
33 },
34 containerBuilder = {
35 build: function( dialog, elementDefinition, output ) {
36 var children = elementDefinition.children,
37 child,
38 childHtmlList = [],
39 childObjList = [];
40 for ( var i = 0;
41 ( i < children.length && ( child = children[ i ] ) ); i++ ) {
42 var childHtml = [];
43 childHtmlList.push( childHtml );
44 childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) );
45 }
46 return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, childObjList, childHtmlList, output, elementDefinition );
47 }
48 },
49 commonPrototype = {
50 isChanged: function() {
51 return this.getValue() != this.getInitValue();
52 },
53
54 reset: function( noChangeEvent ) {
55 this.setValue( this.getInitValue(), noChangeEvent );
56 },
57
58 setInitValue: function() {
59 this._.initValue = this.getValue();
60 },
61
62 resetInitValue: function() {
63 this._.initValue = this._[ 'default' ];
64 },
65
66 getInitValue: function() {
67 return this._.initValue;
68 }
69 },
70 commonEventProcessors = CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, {
71 onChange: function( dialog, func ) {
72 if ( !this._.domOnChangeRegistered ) {
73 dialog.on( 'load', function() {
74 this.getInputElement().on( 'change', function() {
75 // Make sure 'onchange' doesn't get fired after dialog closed. (http://dev.ckeditor.com/ticket/5719)
76 if ( !dialog.parts.dialog.isVisible() )
77 return;
78
79 this.fire( 'change', { value: this.getValue() } );
80 }, this );
81 }, this );
82 this._.domOnChangeRegistered = true;
83 }
84
85 this.on( 'change', func );
86 }
87 }, true ),
88 eventRegex = /^on([A-Z]\w+)/,
89 cleanInnerDefinition = function( def ) {
90 // An inner UI element should not have the parent's type, title or events.
91 for ( var i in def ) {
92 if ( eventRegex.test( i ) || i == 'title' || i == 'type' )
93 delete def[ i ];
94 }
95 return def;
96 },
97 // @context {CKEDITOR.dialog.uiElement} UI element (textarea or textInput)
98 // @param {CKEDITOR.dom.event} evt
99 toggleBidiKeyUpHandler = function( evt ) {
100 var keystroke = evt.data.getKeystroke();
101
102 // ALT + SHIFT + Home for LTR direction.
103 if ( keystroke == CKEDITOR.SHIFT + CKEDITOR.ALT + 36 )
104 this.setDirectionMarker( 'ltr' );
105
106 // ALT + SHIFT + End for RTL direction.
107 else if ( keystroke == CKEDITOR.SHIFT + CKEDITOR.ALT + 35 )
108 this.setDirectionMarker( 'rtl' );
109 };
110
111 CKEDITOR.tools.extend( CKEDITOR.ui.dialog, {
112 /**
113 * Base class for all dialog window elements with a textual label on the left.
114 *
115 * @class CKEDITOR.ui.dialog.labeledElement
116 * @extends CKEDITOR.ui.dialog.uiElement
117 * @constructor Creates a labeledElement class instance.
118 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
119 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
120 * The element definition. Accepted fields:
121 *
122 * * `label` (Required) The label string.
123 * * `labelLayout` (Optional) Put 'horizontal' here if the
124 * label element is to be laid out horizontally. Otherwise a vertical
125 * layout will be used.
126 * * `widths` (Optional) This applies only to horizontal
127 * layouts &mdash; a two-element array of lengths to specify the widths of the
128 * label and the content element.
129 * * `role` (Optional) Value for the `role` attribute.
130 * * `includeLabel` (Optional) If set to `true`, the `aria-labelledby` attribute
131 * will be included.
132 *
133 * @param {Array} htmlList The list of HTML code to output to.
134 * @param {Function} contentHtml
135 * A function returning the HTML code string to be added inside the content
136 * cell.
137 */
138 labeledElement: function( dialog, elementDefinition, htmlList, contentHtml ) {
139 if ( arguments.length < 4 )
140 return;
141
142 var _ = initPrivateObject.call( this, elementDefinition );
143 _.labelId = CKEDITOR.tools.getNextId() + '_label';
144 this._.children = [];
145
146 var innerHTML = function() {
147 var html = [],
148 requiredClass = elementDefinition.required ? ' cke_required' : '';
149 if ( elementDefinition.labelLayout != 'horizontal' ) {
150 html.push(
151 '<label class="cke_dialog_ui_labeled_label' + requiredClass + '" ', ' id="' + _.labelId + '"',
152 ( _.inputId ? ' for="' + _.inputId + '"' : '' ),
153 ( elementDefinition.labelStyle ? ' style="' + elementDefinition.labelStyle + '"' : '' ) + '>',
154 elementDefinition.label,
155 '</label>',
156 '<div class="cke_dialog_ui_labeled_content"',
157 ( elementDefinition.controlStyle ? ' style="' + elementDefinition.controlStyle + '"' : '' ),
158 ' role="presentation">',
159 contentHtml.call( this, dialog, elementDefinition ),
160 '</div>' );
161 } else {
162 var hboxDefinition = {
163 type: 'hbox',
164 widths: elementDefinition.widths,
165 padding: 0,
166 children: [ {
167 type: 'html',
168 html: '<label class="cke_dialog_ui_labeled_label' + requiredClass + '"' +
169 ' id="' + _.labelId + '"' +
170 ' for="' + _.inputId + '"' +
171 ( elementDefinition.labelStyle ? ' style="' + elementDefinition.labelStyle + '"' : '' ) + '>' +
172 CKEDITOR.tools.htmlEncode( elementDefinition.label ) +
173 '</label>'
174 },
175 {
176 type: 'html',
177 html: '<span class="cke_dialog_ui_labeled_content"' + ( elementDefinition.controlStyle ? ' style="' + elementDefinition.controlStyle + '"' : '' ) + '>' +
178 contentHtml.call( this, dialog, elementDefinition ) +
179 '</span>'
180 } ]
181 };
182 CKEDITOR.dialog._.uiElementBuilders.hbox.build( dialog, hboxDefinition, html );
183 }
184 return html.join( '' );
185 };
186 var attributes = { role: elementDefinition.role || 'presentation' };
187
188 if ( elementDefinition.includeLabel )
189 attributes[ 'aria-labelledby' ] = _.labelId;
190
191 CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'div', null, attributes, innerHTML );
192 },
193
194 /**
195 * A text input with a label. This UI element class represents both the
196 * single-line text inputs and password inputs in dialog boxes.
197 *
198 * @class CKEDITOR.ui.dialog.textInput
199 * @extends CKEDITOR.ui.dialog.labeledElement
200 * @constructor Creates a textInput class instance.
201 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
202 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
203 * The element definition. Accepted fields:
204 *
205 * * `default` (Optional) The default value.
206 * * `validate` (Optional) The validation function.
207 * * `maxLength` (Optional) The maximum length of text box contents.
208 * * `size` (Optional) The size of the text box. This is
209 * usually overridden by the size defined by the skin, though.
210 *
211 * @param {Array} htmlList List of HTML code to output to.
212 */
213 textInput: function( dialog, elementDefinition, htmlList ) {
214 if ( arguments.length < 3 )
215 return;
216
217 initPrivateObject.call( this, elementDefinition );
218 var domId = this._.inputId = CKEDITOR.tools.getNextId() + '_textInput',
219 attributes = { 'class': 'cke_dialog_ui_input_' + elementDefinition.type, id: domId, type: elementDefinition.type };
220
221 // Set the validator, if any.
222 if ( elementDefinition.validate )
223 this.validate = elementDefinition.validate;
224
225 // Set the max length and size.
226 if ( elementDefinition.maxLength )
227 attributes.maxlength = elementDefinition.maxLength;
228 if ( elementDefinition.size )
229 attributes.size = elementDefinition.size;
230
231 if ( elementDefinition.inputStyle )
232 attributes.style = elementDefinition.inputStyle;
233
234 // If user presses Enter in a text box, it implies clicking OK for the dialog.
235 var me = this,
236 keyPressedOnMe = false;
237 dialog.on( 'load', function() {
238 me.getInputElement().on( 'keydown', function( evt ) {
239 if ( evt.data.getKeystroke() == 13 )
240 keyPressedOnMe = true;
241 } );
242
243 // Lower the priority this 'keyup' since 'ok' will close the dialog.(http://dev.ckeditor.com/ticket/3749)
244 me.getInputElement().on( 'keyup', function( evt ) {
245 if ( evt.data.getKeystroke() == 13 && keyPressedOnMe ) {
246 dialog.getButton( 'ok' ) && setTimeout( function() {
247 dialog.getButton( 'ok' ).click();
248 }, 0 );
249 keyPressedOnMe = false;
250 }
251
252 if ( me.bidi )
253 toggleBidiKeyUpHandler.call( me, evt );
254 }, null, null, 1000 );
255 } );
256
257 var innerHTML = function() {
258 // IE BUG: Text input fields in IE at 100% would exceed a <td> or inline
259 // container's width, so need to wrap it inside a <div>.
260 var html = [ '<div class="cke_dialog_ui_input_', elementDefinition.type, '" role="presentation"' ];
261
262 if ( elementDefinition.width )
263 html.push( 'style="width:' + elementDefinition.width + '" ' );
264
265 html.push( '><input ' );
266
267 attributes[ 'aria-labelledby' ] = this._.labelId;
268 this._.required && ( attributes[ 'aria-required' ] = this._.required );
269 for ( var i in attributes )
270 html.push( i + '="' + attributes[ i ] + '" ' );
271 html.push( ' /></div>' );
272 return html.join( '' );
273 };
274 CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
275 },
276
277 /**
278 * A text area with a label at the top or on the left.
279 *
280 * @class CKEDITOR.ui.dialog.textarea
281 * @extends CKEDITOR.ui.dialog.labeledElement
282 * @constructor Creates a textarea class instance.
283 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
284 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
285 *
286 * The element definition. Accepted fields:
287 *
288 * * `rows` (Optional) The number of rows displayed.
289 * Defaults to 5 if not defined.
290 * * `cols` (Optional) The number of cols displayed.
291 * Defaults to 20 if not defined. Usually overridden by skins.
292 * * `default` (Optional) The default value.
293 * * `validate` (Optional) The validation function.
294 *
295 * @param {Array} htmlList List of HTML code to output to.
296 */
297 textarea: function( dialog, elementDefinition, htmlList ) {
298 if ( arguments.length < 3 )
299 return;
300
301 initPrivateObject.call( this, elementDefinition );
302 var me = this,
303 domId = this._.inputId = CKEDITOR.tools.getNextId() + '_textarea',
304 attributes = {};
305
306 if ( elementDefinition.validate )
307 this.validate = elementDefinition.validate;
308
309 // Generates the essential attributes for the textarea tag.
310 attributes.rows = elementDefinition.rows || 5;
311 attributes.cols = elementDefinition.cols || 20;
312
313 attributes[ 'class' ] = 'cke_dialog_ui_input_textarea ' + ( elementDefinition[ 'class' ] || '' );
314
315 if ( typeof elementDefinition.inputStyle != 'undefined' )
316 attributes.style = elementDefinition.inputStyle;
317
318 if ( elementDefinition.dir )
319 attributes.dir = elementDefinition.dir;
320
321 if ( me.bidi ) {
322 dialog.on( 'load', function() {
323 me.getInputElement().on( 'keyup', toggleBidiKeyUpHandler );
324 }, me );
325 }
326
327 var innerHTML = function() {
328 attributes[ 'aria-labelledby' ] = this._.labelId;
329 this._.required && ( attributes[ 'aria-required' ] = this._.required );
330 var html = [ '<div class="cke_dialog_ui_input_textarea" role="presentation"><textarea id="', domId, '" ' ];
331 for ( var i in attributes )
332 html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[ i ] ) + '" ' );
333 html.push( '>', CKEDITOR.tools.htmlEncode( me._[ 'default' ] ), '</textarea></div>' );
334 return html.join( '' );
335 };
336 CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
337 },
338
339 /**
340 * A single checkbox with a label on the right.
341 *
342 * @class CKEDITOR.ui.dialog.checkbox
343 * @extends CKEDITOR.ui.dialog.uiElement
344 * @constructor Creates a checkbox class instance.
345 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
346 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
347 * The element definition. Accepted fields:
348 *
349 * * `checked` (Optional) Whether the checkbox is checked
350 * on instantiation. Defaults to `false`.
351 * * `validate` (Optional) The validation function.
352 * * `label` (Optional) The checkbox label.
353 *
354 * @param {Array} htmlList List of HTML code to output to.
355 */
356 checkbox: function( dialog, elementDefinition, htmlList ) {
357 if ( arguments.length < 3 )
358 return;
359
360 var _ = initPrivateObject.call( this, elementDefinition, { 'default': !!elementDefinition[ 'default' ] } );
361
362 if ( elementDefinition.validate )
363 this.validate = elementDefinition.validate;
364
365 var innerHTML = function() {
366 var myDefinition = CKEDITOR.tools.extend(
367 {},
368 elementDefinition,
369 {
370 id: elementDefinition.id ? elementDefinition.id + '_checkbox' : CKEDITOR.tools.getNextId() + '_checkbox'
371 },
372 true
373 ),
374 html = [];
375
376 var labelId = CKEDITOR.tools.getNextId() + '_label';
377 var attributes = { 'class': 'cke_dialog_ui_checkbox_input', type: 'checkbox', 'aria-labelledby': labelId };
378 cleanInnerDefinition( myDefinition );
379 if ( elementDefinition[ 'default' ] )
380 attributes.checked = 'checked';
381
382 if ( typeof myDefinition.inputStyle != 'undefined' )
383 myDefinition.style = myDefinition.inputStyle;
384
385 _.checkbox = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'input', null, attributes );
386 html.push(
387 ' <label id="',
388 labelId,
389 '" for="',
390 attributes.id,
391 '"' + ( elementDefinition.labelStyle ? ' style="' + elementDefinition.labelStyle + '"' : '' ) + '>',
392 CKEDITOR.tools.htmlEncode( elementDefinition.label ),
393 '</label>'
394 );
395 return html.join( '' );
396 };
397
398 CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'span', null, null, innerHTML );
399 },
400
401 /**
402 * A group of radio buttons.
403 *
404 * @class CKEDITOR.ui.dialog.radio
405 * @extends CKEDITOR.ui.dialog.labeledElement
406 * @constructor Creates a radio class instance.
407 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
408 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
409 * The element definition. Accepted fields:
410 *
411 * * `default` (Required) The default value.
412 * * `validate` (Optional) The validation function.
413 * * `items` (Required) An array of options. Each option
414 * is a one- or two-item array of format `[ 'Description', 'Value' ]`. If `'Value'`
415 * is missing, then the value would be assumed to be the same as the description.
416 *
417 * @param {Array} htmlList List of HTML code to output to.
418 */
419 radio: function( dialog, elementDefinition, htmlList ) {
420 if ( arguments.length < 3 )
421 return;
422
423 initPrivateObject.call( this, elementDefinition );
424
425 if ( !this._[ 'default' ] )
426 this._[ 'default' ] = this._.initValue = elementDefinition.items[ 0 ][ 1 ];
427
428 if ( elementDefinition.validate )
429 this.validate = elementDefinition.validate;
430
431 var children = [],
432 me = this;
433
434 var innerHTML = function() {
435 var inputHtmlList = [],
436 html = [],
437 commonName = ( elementDefinition.id ? elementDefinition.id : CKEDITOR.tools.getNextId() ) + '_radio';
438
439 for ( var i = 0; i < elementDefinition.items.length; i++ ) {
440 var item = elementDefinition.items[ i ],
441 title = item[ 2 ] !== undefined ? item[ 2 ] : item[ 0 ],
442 value = item[ 1 ] !== undefined ? item[ 1 ] : item[ 0 ],
443 inputId = CKEDITOR.tools.getNextId() + '_radio_input',
444 labelId = inputId + '_label',
445
446 inputDefinition = CKEDITOR.tools.extend( {}, elementDefinition, {
447 id: inputId,
448 title: null,
449 type: null
450 }, true ),
451
452 labelDefinition = CKEDITOR.tools.extend( {}, inputDefinition, {
453 title: title
454 }, true ),
455
456 inputAttributes = {
457 type: 'radio',
458 'class': 'cke_dialog_ui_radio_input',
459 name: commonName,
460 value: value,
461 'aria-labelledby': labelId
462 },
463
464 inputHtml = [];
465
466 if ( me._[ 'default' ] == value )
467 inputAttributes.checked = 'checked';
468
469 cleanInnerDefinition( inputDefinition );
470 cleanInnerDefinition( labelDefinition );
471
472 if ( typeof inputDefinition.inputStyle != 'undefined' )
473 inputDefinition.style = inputDefinition.inputStyle;
474
475 // Make inputs of radio type focusable (http://dev.ckeditor.com/ticket/10866).
476 inputDefinition.keyboardFocusable = true;
477
478 children.push( new CKEDITOR.ui.dialog.uiElement( dialog, inputDefinition, inputHtml, 'input', null, inputAttributes ) );
479
480 inputHtml.push( ' ' );
481
482 new CKEDITOR.ui.dialog.uiElement( dialog, labelDefinition, inputHtml, 'label', null, {
483 id: labelId,
484 'for': inputAttributes.id
485 }, item[ 0 ] );
486
487 inputHtmlList.push( inputHtml.join( '' ) );
488 }
489
490 new CKEDITOR.ui.dialog.hbox( dialog, children, inputHtmlList, html );
491
492 return html.join( '' );
493 };
494
495 // Adding a role="radiogroup" to definition used for wrapper.
496 elementDefinition.role = 'radiogroup';
497 elementDefinition.includeLabel = true;
498
499 CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
500 this._.children = children;
501 },
502
503 /**
504 * A button with a label inside.
505 *
506 * @class CKEDITOR.ui.dialog.button
507 * @extends CKEDITOR.ui.dialog.uiElement
508 * @constructor Creates a button class instance.
509 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
510 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
511 * The element definition. Accepted fields:
512 *
513 * * `label` (Required) The button label.
514 * * `disabled` (Optional) Set to `true` if you want the
515 * button to appear in the disabled state.
516 *
517 * @param {Array} htmlList List of HTML code to output to.
518 */
519 button: function( dialog, elementDefinition, htmlList ) {
520 if ( !arguments.length )
521 return;
522
523 if ( typeof elementDefinition == 'function' )
524 elementDefinition = elementDefinition( dialog.getParentEditor() );
525
526 initPrivateObject.call( this, elementDefinition, { disabled: elementDefinition.disabled || false } );
527
528 // Add OnClick event to this input.
529 CKEDITOR.event.implementOn( this );
530
531 var me = this;
532
533 // Register an event handler for processing button clicks.
534 dialog.on( 'load', function() {
535 var element = this.getElement();
536
537 ( function() {
538 element.on( 'click', function( evt ) {
539 me.click();
540 // http://dev.ckeditor.com/ticket/9958
541 evt.data.preventDefault();
542 } );
543
544 element.on( 'keydown', function( evt ) {
545 if ( evt.data.getKeystroke() in { 32: 1 } ) {
546 me.click();
547 evt.data.preventDefault();
548 }
549 } );
550 } )();
551
552 element.unselectable();
553 }, this );
554
555 var outerDefinition = CKEDITOR.tools.extend( {}, elementDefinition );
556 delete outerDefinition.style;
557
558 var labelId = CKEDITOR.tools.getNextId() + '_label';
559 CKEDITOR.ui.dialog.uiElement.call( this, dialog, outerDefinition, htmlList, 'a', null, {
560 style: elementDefinition.style,
561 href: 'javascript:void(0)', // jshint ignore:line
562 title: elementDefinition.label,
563 hidefocus: 'true',
564 'class': elementDefinition[ 'class' ],
565 role: 'button',
566 'aria-labelledby': labelId
567 }, '<span id="' + labelId + '" class="cke_dialog_ui_button">' +
568 CKEDITOR.tools.htmlEncode( elementDefinition.label ) +
569 '</span>' );
570 },
571
572 /**
573 * A select box.
574 *
575 * @class CKEDITOR.ui.dialog.select
576 * @extends CKEDITOR.ui.dialog.uiElement
577 * @constructor Creates a button class instance.
578 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
579 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
580 * The element definition. Accepted fields:
581 *
582 * * `default` (Required) The default value.
583 * * `validate` (Optional) The validation function.
584 * * `items` (Required) An array of options. Each option
585 * is a one- or two-item array of format `[ 'Description', 'Value' ]`. If `'Value'`
586 * is missing, then the value would be assumed to be the same as the
587 * description.
588 * * `multiple` (Optional) Set this to `true` if you would like
589 * to have a multiple-choice select box.
590 * * `size` (Optional) The number of items to display in
591 * the select box.
592 *
593 * @param {Array} htmlList List of HTML code to output to.
594 */
595 select: function( dialog, elementDefinition, htmlList ) {
596 if ( arguments.length < 3 )
597 return;
598
599 var _ = initPrivateObject.call( this, elementDefinition );
600
601 if ( elementDefinition.validate )
602 this.validate = elementDefinition.validate;
603
604 _.inputId = CKEDITOR.tools.getNextId() + '_select';
605
606 var innerHTML = function() {
607 var myDefinition = CKEDITOR.tools.extend(
608 {},
609 elementDefinition,
610 {
611 id: ( elementDefinition.id ? elementDefinition.id + '_select' : CKEDITOR.tools.getNextId() + '_select' )
612 },
613 true
614 ),
615 html = [],
616 innerHTML = [],
617 attributes = { 'id': _.inputId, 'class': 'cke_dialog_ui_input_select', 'aria-labelledby': this._.labelId };
618
619 html.push( '<div class="cke_dialog_ui_input_', elementDefinition.type, '" role="presentation"' );
620 if ( elementDefinition.width )
621 html.push( 'style="width:' + elementDefinition.width + '" ' );
622 html.push( '>' );
623
624 // Add multiple and size attributes from element definition.
625 if ( elementDefinition.size !== undefined )
626 attributes.size = elementDefinition.size;
627 if ( elementDefinition.multiple !== undefined )
628 attributes.multiple = elementDefinition.multiple;
629
630 cleanInnerDefinition( myDefinition );
631 for ( var i = 0, item; i < elementDefinition.items.length && ( item = elementDefinition.items[ i ] ); i++ ) {
632 innerHTML.push( '<option value="', CKEDITOR.tools.htmlEncode( item[ 1 ] !== undefined ? item[ 1 ] : item[ 0 ] ).replace( /"/g, '&quot;' ), '" /> ', CKEDITOR.tools.htmlEncode( item[ 0 ] ) );
633 }
634
635 if ( typeof myDefinition.inputStyle != 'undefined' )
636 myDefinition.style = myDefinition.inputStyle;
637
638 _.select = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'select', null, attributes, innerHTML.join( '' ) );
639
640 html.push( '</div>' );
641
642 return html.join( '' );
643 };
644
645 CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
646 },
647
648 /**
649 * A file upload input.
650 *
651 * @class CKEDITOR.ui.dialog.file
652 * @extends CKEDITOR.ui.dialog.labeledElement
653 * @constructor Creates a file class instance.
654 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
655 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
656 * The element definition. Accepted fields:
657 *
658 * * `validate` (Optional) The validation function.
659 *
660 * @param {Array} htmlList List of HTML code to output to.
661 */
662 file: function( dialog, elementDefinition, htmlList ) {
663 if ( arguments.length < 3 )
664 return;
665
666 if ( elementDefinition[ 'default' ] === undefined )
667 elementDefinition[ 'default' ] = '';
668
669 var _ = CKEDITOR.tools.extend( initPrivateObject.call( this, elementDefinition ), { definition: elementDefinition, buttons: [] } );
670
671 if ( elementDefinition.validate )
672 this.validate = elementDefinition.validate;
673
674 /** @ignore */
675 var innerHTML = function() {
676 _.frameId = CKEDITOR.tools.getNextId() + '_fileInput';
677
678 var html = [
679 '<iframe' +
680 ' frameborder="0"' +
681 ' allowtransparency="0"' +
682 ' class="cke_dialog_ui_input_file"' +
683 ' role="presentation"' +
684 ' id="', _.frameId, '"' +
685 ' title="', elementDefinition.label, '"' +
686 ' src="javascript:void('
687 ];
688
689 // Support for custom document.domain on IE. (http://dev.ckeditor.com/ticket/10165)
690 html.push( CKEDITOR.env.ie ?
691 '(function(){' + encodeURIComponent(
692 'document.open();' +
693 '(' + CKEDITOR.tools.fixDomain + ')();' +
694 'document.close();'
695 ) + '})()'
696 :
697 '0'
698 );
699
700 html.push( ')"></iframe>' );
701
702 return html.join( '' );
703 };
704
705 // IE BUG: Parent container does not resize to contain the iframe automatically.
706 dialog.on( 'load', function() {
707 var iframe = CKEDITOR.document.getById( _.frameId ),
708 contentDiv = iframe.getParent();
709 contentDiv.addClass( 'cke_dialog_ui_input_file' );
710 } );
711
712 CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
713 },
714
715 /**
716 * A button for submitting the file in a file upload input.
717 *
718 * @class CKEDITOR.ui.dialog.fileButton
719 * @extends CKEDITOR.ui.dialog.button
720 * @constructor Creates a fileButton class instance.
721 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
722 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
723 * The element definition. Accepted fields:
724 *
725 * * `for` (Required) The file input's page and element ID
726 * to associate with, in a two-item array format: `[ 'page_id', 'element_id' ]`.
727 * * `validate` (Optional) The validation function.
728 *
729 * @param {Array} htmlList List of HTML code to output to.
730 */
731 fileButton: function( dialog, elementDefinition, htmlList ) {
732 var me = this;
733 if ( arguments.length < 3 )
734 return;
735
736 initPrivateObject.call( this, elementDefinition );
737
738 if ( elementDefinition.validate )
739 this.validate = elementDefinition.validate;
740
741 var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition );
742 var onClick = myDefinition.onClick;
743 myDefinition.className = ( myDefinition.className ? myDefinition.className + ' ' : '' ) + 'cke_dialog_ui_button';
744 myDefinition.onClick = function( evt ) {
745 var target = elementDefinition[ 'for' ]; // [ pageId, elementId ]
746 if ( !onClick || onClick.call( this, evt ) !== false ) {
747 dialog.getContentElement( target[ 0 ], target[ 1 ] ).submit();
748 this.disable();
749 }
750 };
751
752 dialog.on( 'load', function() {
753 dialog.getContentElement( elementDefinition[ 'for' ][ 0 ], elementDefinition[ 'for' ][ 1 ] )._.buttons.push( me );
754 } );
755
756 CKEDITOR.ui.dialog.button.call( this, dialog, myDefinition, htmlList );
757 },
758
759 html: ( function() {
760 var myHtmlRe = /^\s*<[\w:]+\s+([^>]*)?>/,
761 theirHtmlRe = /^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,
762 emptyTagRe = /\/$/;
763 /**
764 * A dialog window element made from raw HTML code.
765 *
766 * @class CKEDITOR.ui.dialog.html
767 * @extends CKEDITOR.ui.dialog.uiElement
768 * @constructor Creates a html class instance.
769 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
770 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element definition.
771 * Accepted fields:
772 *
773 * * `html` (Required) HTML code of this element.
774 *
775 * @param {Array} htmlList List of HTML code to be added to the dialog's content area.
776 */
777 return function( dialog, elementDefinition, htmlList ) {
778 if ( arguments.length < 3 )
779 return;
780
781 var myHtmlList = [],
782 myHtml,
783 theirHtml = elementDefinition.html,
784 myMatch, theirMatch;
785
786 // If the HTML input doesn't contain any tags at the beginning, add a <span> tag around it.
787 if ( theirHtml.charAt( 0 ) != '<' )
788 theirHtml = '<span>' + theirHtml + '</span>';
789
790 // Look for focus function in definition.
791 var focus = elementDefinition.focus;
792 if ( focus ) {
793 var oldFocus = this.focus;
794 this.focus = function() {
795 ( typeof focus == 'function' ? focus : oldFocus ).call( this );
796 this.fire( 'focus' );
797 };
798 if ( elementDefinition.isFocusable ) {
799 var oldIsFocusable = this.isFocusable;
800 this.isFocusable = oldIsFocusable;
801 }
802 this.keyboardFocusable = true;
803 }
804
805 CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, myHtmlList, 'span', null, null, '' );
806
807 // Append the attributes created by the uiElement call to the real HTML.
808 myHtml = myHtmlList.join( '' );
809 myMatch = myHtml.match( myHtmlRe );
810 theirMatch = theirHtml.match( theirHtmlRe ) || [ '', '', '' ];
811
812 if ( emptyTagRe.test( theirMatch[ 1 ] ) ) {
813 theirMatch[ 1 ] = theirMatch[ 1 ].slice( 0, -1 );
814 theirMatch[ 2 ] = '/' + theirMatch[ 2 ];
815 }
816
817 htmlList.push( [ theirMatch[ 1 ], ' ', myMatch[ 1 ] || '', theirMatch[ 2 ] ].join( '' ) );
818 };
819 } )(),
820
821 /**
822 * Form fieldset for grouping dialog UI elements.
823 *
824 * @class CKEDITOR.ui.dialog.fieldset
825 * @extends CKEDITOR.ui.dialog.uiElement
826 * @constructor Creates a fieldset class instance.
827 * @param {CKEDITOR.dialog} dialog Parent dialog window object.
828 * @param {Array} childObjList
829 * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container.
830 * @param {Array} childHtmlList Array of HTML code that corresponds to the HTML output of all the
831 * objects in childObjList.
832 * @param {Array} htmlList Array of HTML code that this element will output to.
833 * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition
834 * The element definition. Accepted fields:
835 *
836 * * `label` (Optional) The legend of the this fieldset.
837 * * `children` (Required) An array of dialog window field definitions which will be grouped inside this fieldset.
838 *
839 */
840 fieldset: function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) {
841 var legendLabel = elementDefinition.label;
842 /** @ignore */
843 var innerHTML = function() {
844 var html = [];
845 legendLabel && html.push( '<legend' +
846 ( elementDefinition.labelStyle ? ' style="' + elementDefinition.labelStyle + '"' : '' ) +
847 '>' + legendLabel + '</legend>' );
848 for ( var i = 0; i < childHtmlList.length; i++ )
849 html.push( childHtmlList[ i ] );
850 return html.join( '' );
851 };
852
853 this._ = { children: childObjList };
854 CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'fieldset', null, null, innerHTML );
855 }
856
857 }, true );
858
859 CKEDITOR.ui.dialog.html.prototype = new CKEDITOR.ui.dialog.uiElement();
860
861 /** @class CKEDITOR.ui.dialog.labeledElement */
862 CKEDITOR.ui.dialog.labeledElement.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement(), {
863 /**
864 * Sets the label text of the element.
865 *
866 * @param {String} label The new label text.
867 * @returns {CKEDITOR.ui.dialog.labeledElement} The current labeled element.
868 */
869 setLabel: function( label ) {
870 var node = CKEDITOR.document.getById( this._.labelId );
871 if ( node.getChildCount() < 1 )
872 ( new CKEDITOR.dom.text( label, CKEDITOR.document ) ).appendTo( node );
873 else
874 node.getChild( 0 ).$.nodeValue = label;
875 return this;
876 },
877
878 /**
879 * Retrieves the current label text of the elment.
880 *
881 * @returns {String} The current label text.
882 */
883 getLabel: function() {
884 var node = CKEDITOR.document.getById( this._.labelId );
885 if ( !node || node.getChildCount() < 1 )
886 return '';
887 else
888 return node.getChild( 0 ).getText();
889 },
890
891 /**
892 * Defines the `onChange` event for UI element definitions.
893 * @property {Object}
894 */
895 eventProcessors: commonEventProcessors
896 }, true );
897
898 /** @class CKEDITOR.ui.dialog.button */
899 CKEDITOR.ui.dialog.button.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement(), {
900 /**
901 * Simulates a click to the button.
902 *
903 * @returns {Object} Return value of the `click` event.
904 */
905 click: function() {
906 if ( !this._.disabled )
907 return this.fire( 'click', { dialog: this._.dialog } );
908 return false;
909 },
910
911 /**
912 * Enables the button.
913 */
914 enable: function() {
915 this._.disabled = false;
916 var element = this.getElement();
917 element && element.removeClass( 'cke_disabled' );
918 },
919
920 /**
921 * Disables the button.
922 */
923 disable: function() {
924 this._.disabled = true;
925 this.getElement().addClass( 'cke_disabled' );
926 },
927
928 /**
929 * Checks whether a field is visible.
930 *
931 * @returns {Boolean}
932 */
933 isVisible: function() {
934 return this.getElement().getFirst().isVisible();
935 },
936
937 /**
938 * Checks whether a field is enabled. Fields can be disabled by using the
939 * {@link #disable} method and enabled by using the {@link #enable} method.
940 *
941 * @returns {Boolean}
942 */
943 isEnabled: function() {
944 return !this._.disabled;
945 },
946
947 /**
948 * Defines the `onChange` event and `onClick` for button element definitions.
949 *
950 * @property {Object}
951 */
952 eventProcessors: CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, {
953 onClick: function( dialog, func ) {
954 this.on( 'click', function() {
955 func.apply( this, arguments );
956 } );
957 }
958 }, true ),
959
960 /**
961 * Handler for the element's access key up event. Simulates a click to
962 * the button.
963 */
964 accessKeyUp: function() {
965 this.click();
966 },
967
968 /**
969 * Handler for the element's access key down event. Simulates a mouse
970 * down to the button.
971 */
972 accessKeyDown: function() {
973 this.focus();
974 },
975
976 keyboardFocusable: true
977 }, true );
978
979 /** @class CKEDITOR.ui.dialog.textInput */
980 CKEDITOR.ui.dialog.textInput.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement(), {
981 /**
982 * Gets the text input DOM element under this UI object.
983 *
984 * @returns {CKEDITOR.dom.element} The DOM element of the text input.
985 */
986 getInputElement: function() {
987 return CKEDITOR.document.getById( this._.inputId );
988 },
989
990 /**
991 * Puts focus into the text input.
992 */
993 focus: function() {
994 var me = this.selectParentTab();
995
996 // GECKO BUG: setTimeout() is needed to workaround invisible selections.
997 setTimeout( function() {
998 var element = me.getInputElement();
999 element && element.$.focus();
1000 }, 0 );
1001 },
1002
1003 /**
1004 * Selects all the text in the text input.
1005 */
1006 select: function() {
1007 var me = this.selectParentTab();
1008
1009 // GECKO BUG: setTimeout() is needed to workaround invisible selections.
1010 setTimeout( function() {
1011 var e = me.getInputElement();
1012 if ( e ) {
1013 e.$.focus();
1014 e.$.select();
1015 }
1016 }, 0 );
1017 },
1018
1019 /**
1020 * Handler for the text input's access key up event. Makes a `select()`
1021 * call to the text input.
1022 */
1023 accessKeyUp: function() {
1024 this.select();
1025 },
1026
1027 /**
1028 * Sets the value of this text input object.
1029 *
1030 * uiElement.setValue( 'Blamo' );
1031 *
1032 * @param {Object} value The new value.
1033 * @returns {CKEDITOR.ui.dialog.textInput} The current UI element.
1034 */
1035 setValue: function( value ) {
1036 if ( this.bidi ) {
1037 var marker = value && value.charAt( 0 ),
1038 dir = ( marker == '\u202A' ? 'ltr' : marker == '\u202B' ? 'rtl' : null );
1039
1040 if ( dir ) {
1041 value = value.slice( 1 );
1042 }
1043
1044 // Set the marker or reset it (if dir==null).
1045 this.setDirectionMarker( dir );
1046 }
1047
1048 if ( !value ) {
1049 value = '';
1050 }
1051
1052 return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply( this, arguments );
1053 },
1054
1055 /**
1056 * Gets the value of this text input object.
1057 *
1058 * @returns {String} The value.
1059 */
1060 getValue: function() {
1061 var value = CKEDITOR.ui.dialog.uiElement.prototype.getValue.call( this );
1062
1063 if ( this.bidi && value ) {
1064 var dir = this.getDirectionMarker();
1065 if ( dir ) {
1066 value = ( dir == 'ltr' ? '\u202A' : '\u202B' ) + value;
1067 }
1068 }
1069
1070 return value;
1071 },
1072
1073 /**
1074 * Sets the text direction marker and the `dir` attribute of the input element.
1075 *
1076 * @since 4.5
1077 * @param {String} dir The text direction. Pass `null` to reset.
1078 */
1079 setDirectionMarker: function( dir ) {
1080 var inputElement = this.getInputElement();
1081
1082 if ( dir ) {
1083 inputElement.setAttributes( {
1084 dir: dir,
1085 'data-cke-dir-marker': dir
1086 } );
1087 // Don't remove the dir attribute if this field hasn't got the marker,
1088 // because the dir attribute could be set independently.
1089 } else if ( this.getDirectionMarker() ) {
1090 inputElement.removeAttributes( [ 'dir', 'data-cke-dir-marker' ] );
1091 }
1092 },
1093
1094 /**
1095 * Gets the value of the text direction marker.
1096 *
1097 * @since 4.5
1098 * @returns {String} `'ltr'`, `'rtl'` or `null` if the marker is not set.
1099 */
1100 getDirectionMarker: function() {
1101 return this.getInputElement().data( 'cke-dir-marker' );
1102 },
1103
1104 keyboardFocusable: true
1105 }, commonPrototype, true );
1106
1107 CKEDITOR.ui.dialog.textarea.prototype = new CKEDITOR.ui.dialog.textInput();
1108
1109 /** @class CKEDITOR.ui.dialog.select */
1110 CKEDITOR.ui.dialog.select.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement(), {
1111 /**
1112 * Gets the DOM element of the select box.
1113 *
1114 * @returns {CKEDITOR.dom.element} The `<select>` element of this UI element.
1115 */
1116 getInputElement: function() {
1117 return this._.select.getElement();
1118 },
1119
1120 /**
1121 * Adds an option to the select box.
1122 *
1123 * @param {String} label Option label.
1124 * @param {String} value (Optional) Option value, if not defined it will be
1125 * assumed to be the same as the label.
1126 * @param {Number} index (Optional) Position of the option to be inserted
1127 * to. If not defined, the new option will be inserted to the end of list.
1128 * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
1129 */
1130 add: function( label, value, index ) {
1131 var option = new CKEDITOR.dom.element( 'option', this.getDialog().getParentEditor().document ),
1132 selectElement = this.getInputElement().$;
1133 option.$.text = label;
1134 option.$.value = ( value === undefined || value === null ) ? label : value;
1135 if ( index === undefined || index === null ) {
1136 if ( CKEDITOR.env.ie ) {
1137 selectElement.add( option.$ );
1138 } else {
1139 selectElement.add( option.$, null );
1140 }
1141 } else {
1142 selectElement.add( option.$, index );
1143 }
1144 return this;
1145 },
1146
1147 /**
1148 * Removes an option from the selection list.
1149 *
1150 * @param {Number} index Index of the option to be removed.
1151 * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
1152 */
1153 remove: function( index ) {
1154 var selectElement = this.getInputElement().$;
1155 selectElement.remove( index );
1156 return this;
1157 },
1158
1159 /**
1160 * Clears all options out of the selection list.
1161 *
1162 * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
1163 */
1164 clear: function() {
1165 var selectElement = this.getInputElement().$;
1166 while ( selectElement.length > 0 )
1167 selectElement.remove( 0 );
1168 return this;
1169 },
1170
1171 keyboardFocusable: true
1172 }, commonPrototype, true );
1173
1174 /** @class CKEDITOR.ui.dialog.checkbox */
1175 CKEDITOR.ui.dialog.checkbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement(), {
1176 /**
1177 * Gets the checkbox DOM element.
1178 *
1179 * @returns {CKEDITOR.dom.element} The DOM element of the checkbox.
1180 */
1181 getInputElement: function() {
1182 return this._.checkbox.getElement();
1183 },
1184
1185 /**
1186 * Sets the state of the checkbox.
1187 *
1188 * @param {Boolean} checked `true` to tick the checkbox, `false` to untick it.
1189 * @param {Boolean} noChangeEvent Internal commit, to supress `change` event on this element.
1190 */
1191 setValue: function( checked, noChangeEvent ) {
1192 this.getInputElement().$.checked = checked;
1193 !noChangeEvent && this.fire( 'change', { value: checked } );
1194 },
1195
1196 /**
1197 * Gets the state of the checkbox.
1198 *
1199 * @returns {Boolean} `true` means that the checkbox is ticked, `false` means it is not ticked.
1200 */
1201 getValue: function() {
1202 return this.getInputElement().$.checked;
1203 },
1204
1205 /**
1206 * Handler for the access key up event. Toggles the checkbox.
1207 */
1208 accessKeyUp: function() {
1209 this.setValue( !this.getValue() );
1210 },
1211
1212 /**
1213 * Defines the `onChange` event for UI element definitions.
1214 *
1215 * @property {Object}
1216 */
1217 eventProcessors: {
1218 onChange: function( dialog, func ) {
1219 if ( !CKEDITOR.env.ie || ( CKEDITOR.env.version > 8 ) )
1220 return commonEventProcessors.onChange.apply( this, arguments );
1221 else {
1222 dialog.on( 'load', function() {
1223 var element = this._.checkbox.getElement();
1224 element.on( 'propertychange', function( evt ) {
1225 evt = evt.data.$;
1226 if ( evt.propertyName == 'checked' )
1227 this.fire( 'change', { value: element.$.checked } );
1228 }, this );
1229 }, this );
1230 this.on( 'change', func );
1231 }
1232 return null;
1233 }
1234 },
1235
1236 keyboardFocusable: true
1237 }, commonPrototype, true );
1238
1239 /** @class CKEDITOR.ui.dialog.radio */
1240 CKEDITOR.ui.dialog.radio.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement(), {
1241 /**
1242 * Selects one of the radio buttons in this button group.
1243 *
1244 * @param {String} value The value of the button to be chcked.
1245 * @param {Boolean} noChangeEvent Internal commit, to supress the `change` event on this element.
1246 */
1247 setValue: function( value, noChangeEvent ) {
1248 var children = this._.children,
1249 item;
1250 for ( var i = 0;
1251 ( i < children.length ) && ( item = children[ i ] ); i++ )
1252 item.getElement().$.checked = ( item.getValue() == value );
1253 !noChangeEvent && this.fire( 'change', { value: value } );
1254 },
1255
1256 /**
1257 * Gets the value of the currently selected radio button.
1258 *
1259 * @returns {String} The currently selected button's value.
1260 */
1261 getValue: function() {
1262 var children = this._.children;
1263 for ( var i = 0; i < children.length; i++ ) {
1264 if ( children[ i ].getElement().$.checked )
1265 return children[ i ].getValue();
1266 }
1267 return null;
1268 },
1269
1270 /**
1271 * Handler for the access key up event. Focuses the currently
1272 * selected radio button, or the first radio button if none is selected.
1273 */
1274 accessKeyUp: function() {
1275 var children = this._.children,
1276 i;
1277 for ( i = 0; i < children.length; i++ ) {
1278 if ( children[ i ].getElement().$.checked ) {
1279 children[ i ].getElement().focus();
1280 return;
1281 }
1282 }
1283 children[ 0 ].getElement().focus();
1284 },
1285
1286 /**
1287 * Defines the `onChange` event for UI element definitions.
1288 *
1289 * @property {Object}
1290 */
1291 eventProcessors: {
1292 onChange: function( dialog, func ) {
1293 if ( !CKEDITOR.env.ie || ( CKEDITOR.env.version > 8 ) )
1294 return commonEventProcessors.onChange.apply( this, arguments );
1295 else {
1296 dialog.on( 'load', function() {
1297 var children = this._.children,
1298 me = this;
1299 for ( var i = 0; i < children.length; i++ ) {
1300 var element = children[ i ].getElement();
1301 element.on( 'propertychange', function( evt ) {
1302 evt = evt.data.$;
1303 if ( evt.propertyName == 'checked' && this.$.checked )
1304 me.fire( 'change', { value: this.getAttribute( 'value' ) } );
1305 } );
1306 }
1307 }, this );
1308 this.on( 'change', func );
1309 }
1310 return null;
1311 }
1312 }
1313 }, commonPrototype, true );
1314
1315 /** @class CKEDITOR.ui.dialog.file */
1316 CKEDITOR.ui.dialog.file.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement(), commonPrototype, {
1317 /**
1318 * Gets the `<input>` element of this file input.
1319 *
1320 * @returns {CKEDITOR.dom.element} The file input element.
1321 */
1322 getInputElement: function() {
1323 var frameDocument = CKEDITOR.document.getById( this._.frameId ).getFrameDocument();
1324 return frameDocument.$.forms.length > 0 ? new CKEDITOR.dom.element( frameDocument.$.forms[ 0 ].elements[ 0 ] ) : this.getElement();
1325 },
1326
1327 /**
1328 * Uploads the file in the file input.
1329 *
1330 * @returns {CKEDITOR.ui.dialog.file} This object.
1331 */
1332 submit: function() {
1333 this.getInputElement().getParent().$.submit();
1334 return this;
1335 },
1336
1337 /**
1338 * Gets the action assigned to the form.
1339 *
1340 * @returns {String} The value of the action.
1341 */
1342 getAction: function() {
1343 return this.getInputElement().getParent().$.action;
1344 },
1345
1346 /**
1347 * The events must be applied to the inner input element, and
1348 * this must be done when the iframe and form have been loaded.
1349 */
1350 registerEvents: function( definition ) {
1351 var regex = /^on([A-Z]\w+)/,
1352 match;
1353
1354 var registerDomEvent = function( uiElement, dialog, eventName, func ) {
1355 uiElement.on( 'formLoaded', function() {
1356 uiElement.getInputElement().on( eventName, func, uiElement );
1357 } );
1358 };
1359
1360 for ( var i in definition ) {
1361 if ( !( match = i.match( regex ) ) )
1362 continue;
1363
1364 if ( this.eventProcessors[ i ] )
1365 this.eventProcessors[ i ].call( this, this._.dialog, definition[ i ] );
1366 else
1367 registerDomEvent( this, this._.dialog, match[ 1 ].toLowerCase(), definition[ i ] );
1368 }
1369
1370 return this;
1371 },
1372
1373 /**
1374 * Redraws the file input and resets the file path in the file input.
1375 * The redrawing logic is necessary because non-IE browsers tend to clear
1376 * the `<iframe>` containing the file input after closing the dialog window.
1377 */
1378 reset: function() {
1379 var _ = this._,
1380 frameElement = CKEDITOR.document.getById( _.frameId ),
1381 frameDocument = frameElement.getFrameDocument(),
1382 elementDefinition = _.definition,
1383 buttons = _.buttons,
1384 callNumber = this.formLoadedNumber,
1385 unloadNumber = this.formUnloadNumber,
1386 langDir = _.dialog._.editor.lang.dir,
1387 langCode = _.dialog._.editor.langCode;
1388
1389 // The callback function for the iframe, but we must call tools.addFunction only once
1390 // so we store the function number in this.formLoadedNumber
1391 if ( !callNumber ) {
1392 callNumber = this.formLoadedNumber = CKEDITOR.tools.addFunction( function() {
1393 // Now we can apply the events to the input type=file
1394 this.fire( 'formLoaded' );
1395 }, this );
1396
1397 // Remove listeners attached to the content of the iframe (the file input)
1398 unloadNumber = this.formUnloadNumber = CKEDITOR.tools.addFunction( function() {
1399 this.getInputElement().clearCustomData();
1400 }, this );
1401
1402 this.getDialog()._.editor.on( 'destroy', function() {
1403 CKEDITOR.tools.removeFunction( callNumber );
1404 CKEDITOR.tools.removeFunction( unloadNumber );
1405 } );
1406 }
1407
1408 function generateFormField() {
1409 frameDocument.$.open();
1410
1411 var size = '';
1412 if ( elementDefinition.size )
1413 size = elementDefinition.size - ( CKEDITOR.env.ie ? 7 : 0 ); // "Browse" button is bigger in IE.
1414
1415 var inputId = _.frameId + '_input';
1416
1417 frameDocument.$.write( [
1418 '<html dir="' + langDir + '" lang="' + langCode + '"><head><title></title></head><body style="margin: 0; overflow: hidden; background: transparent;">',
1419 '<form enctype="multipart/form-data" method="POST" dir="' + langDir + '" lang="' + langCode + '" action="',
1420 CKEDITOR.tools.htmlEncode( elementDefinition.action ),
1421 '">',
1422 // Replicate the field label inside of iframe.
1423 '<label id="', _.labelId, '" for="', inputId, '" style="display:none">',
1424 CKEDITOR.tools.htmlEncode( elementDefinition.label ),
1425 '</label>',
1426 // Set width to make sure that input is not clipped by the iframe (http://dev.ckeditor.com/ticket/11253).
1427 '<input style="width:100%" id="', inputId, '" aria-labelledby="', _.labelId, '" type="file" name="',
1428 CKEDITOR.tools.htmlEncode( elementDefinition.id || 'cke_upload' ),
1429 '" size="',
1430 CKEDITOR.tools.htmlEncode( size > 0 ? size : '' ),
1431 '" />',
1432 '</form>',
1433 '</body></html>',
1434 '<script>',
1435 // Support for custom document.domain in IE.
1436 CKEDITOR.env.ie ? '(' + CKEDITOR.tools.fixDomain + ')();' : '',
1437
1438 'window.parent.CKEDITOR.tools.callFunction(' + callNumber + ');',
1439 'window.onbeforeunload = function() {window.parent.CKEDITOR.tools.callFunction(' + unloadNumber + ')}',
1440 '</script>'
1441 ].join( '' ) );
1442
1443 frameDocument.$.close();
1444
1445 for ( var i = 0; i < buttons.length; i++ )
1446 buttons[ i ].enable();
1447 }
1448
1449 // http://dev.ckeditor.com/ticket/3465: Wait for the browser to finish rendering the dialog first.
1450 if ( CKEDITOR.env.gecko )
1451 setTimeout( generateFormField, 500 );
1452 else
1453 generateFormField();
1454 },
1455
1456 getValue: function() {
1457 return this.getInputElement().$.value || '';
1458 },
1459
1460 /**
1461 * The default value of input `type="file"` is an empty string, but during the initialization
1462 * of this UI element, the iframe still is not ready so it cannot be read from that object.
1463 * Setting it manually prevents later issues with the current value (`''`) being different
1464 * than the initial value (undefined as it asked for `.value` of a div).
1465 */
1466 setInitValue: function() {
1467 this._.initValue = '';
1468 },
1469
1470 /**
1471 * Defines the `onChange` event for UI element definitions.
1472 *
1473 * @property {Object}
1474 */
1475 eventProcessors: {
1476 onChange: function( dialog, func ) {
1477 // If this method is called several times (I'm not sure about how this can happen but the default
1478 // onChange processor includes this protection)
1479 // In order to reapply to the new element, the property is deleted at the beggining of the registerEvents method
1480 if ( !this._.domOnChangeRegistered ) {
1481 // By listening for the formLoaded event, this handler will get reapplied when a new
1482 // form is created
1483 this.on( 'formLoaded', function() {
1484 this.getInputElement().on( 'change', function() {
1485 this.fire( 'change', { value: this.getValue() } );
1486 }, this );
1487 }, this );
1488 this._.domOnChangeRegistered = true;
1489 }
1490
1491 this.on( 'change', func );
1492 }
1493 },
1494
1495 keyboardFocusable: true
1496 }, true );
1497
1498 CKEDITOR.ui.dialog.fileButton.prototype = new CKEDITOR.ui.dialog.button();
1499
1500 CKEDITOR.ui.dialog.fieldset.prototype = CKEDITOR.tools.clone( CKEDITOR.ui.dialog.hbox.prototype );
1501
1502 CKEDITOR.dialog.addUIElement( 'text', textBuilder );
1503 CKEDITOR.dialog.addUIElement( 'password', textBuilder );
1504 CKEDITOR.dialog.addUIElement( 'textarea', commonBuilder );
1505 CKEDITOR.dialog.addUIElement( 'checkbox', commonBuilder );
1506 CKEDITOR.dialog.addUIElement( 'radio', commonBuilder );
1507 CKEDITOR.dialog.addUIElement( 'button', commonBuilder );
1508 CKEDITOR.dialog.addUIElement( 'select', commonBuilder );
1509 CKEDITOR.dialog.addUIElement( 'file', commonBuilder );
1510 CKEDITOR.dialog.addUIElement( 'fileButton', commonBuilder );
1511 CKEDITOR.dialog.addUIElement( 'html', commonBuilder );
1512 CKEDITOR.dialog.addUIElement( 'fieldset', containerBuilder );
1513 }
1514 } );
1515
1516 /**
1517 * Fired when the value of the uiElement is changed.
1518 *
1519 * @event change
1520 * @member CKEDITOR.ui.dialog.uiElement
1521 */
1522
1523 /**
1524 * Fired when the inner frame created by the element is ready.
1525 * Each time the button is used or the dialog window is loaded, a new
1526 * form might be created.
1527 *
1528 * @event formLoaded
1529 * @member CKEDITOR.ui.dialog.fileButton
1530 */