From c63493c899de714b05b0521bb38aab60d19030ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Fri, 20 Jan 2017 00:55:51 +0100 Subject: Validation initiale --- sources/plugins/dialogui/plugin.js | 1530 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1530 insertions(+) create mode 100644 sources/plugins/dialogui/plugin.js (limited to 'sources/plugins/dialogui/plugin.js') diff --git a/sources/plugins/dialogui/plugin.js b/sources/plugins/dialogui/plugin.js new file mode 100644 index 0000000..cf91407 --- /dev/null +++ b/sources/plugins/dialogui/plugin.js @@ -0,0 +1,1530 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * @fileOverview The Dialog User Interface plugin. + */ + +CKEDITOR.plugins.add( 'dialogui', { + onLoad: function() { + + var initPrivateObject = function( elementDefinition ) { + this._ || ( this._ = {} ); + this._[ 'default' ] = this._.initValue = elementDefinition[ 'default' ] || ''; + this._.required = elementDefinition.required || false; + var args = [ this._ ]; + for ( var i = 1; i < arguments.length; i++ ) + args.push( arguments[ i ] ); + args.push( true ); + CKEDITOR.tools.extend.apply( CKEDITOR.tools, args ); + return this._; + }, + textBuilder = { + build: function( dialog, elementDefinition, output ) { + return new CKEDITOR.ui.dialog.textInput( dialog, elementDefinition, output ); + } + }, + commonBuilder = { + build: function( dialog, elementDefinition, output ) { + return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, elementDefinition, output ); + } + }, + containerBuilder = { + build: function( dialog, elementDefinition, output ) { + var children = elementDefinition.children, + child, + childHtmlList = [], + childObjList = []; + for ( var i = 0; + ( i < children.length && ( child = children[ i ] ) ); i++ ) { + var childHtml = []; + childHtmlList.push( childHtml ); + childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); + } + return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, childObjList, childHtmlList, output, elementDefinition ); + } + }, + commonPrototype = { + isChanged: function() { + return this.getValue() != this.getInitValue(); + }, + + reset: function( noChangeEvent ) { + this.setValue( this.getInitValue(), noChangeEvent ); + }, + + setInitValue: function() { + this._.initValue = this.getValue(); + }, + + resetInitValue: function() { + this._.initValue = this._[ 'default' ]; + }, + + getInitValue: function() { + return this._.initValue; + } + }, + commonEventProcessors = CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, { + onChange: function( dialog, func ) { + if ( !this._.domOnChangeRegistered ) { + dialog.on( 'load', function() { + this.getInputElement().on( 'change', function() { + // Make sure 'onchange' doesn't get fired after dialog closed. (#5719) + if ( !dialog.parts.dialog.isVisible() ) + return; + + this.fire( 'change', { value: this.getValue() } ); + }, this ); + }, this ); + this._.domOnChangeRegistered = true; + } + + this.on( 'change', func ); + } + }, true ), + eventRegex = /^on([A-Z]\w+)/, + cleanInnerDefinition = function( def ) { + // An inner UI element should not have the parent's type, title or events. + for ( var i in def ) { + if ( eventRegex.test( i ) || i == 'title' || i == 'type' ) + delete def[ i ]; + } + return def; + }, + // @context {CKEDITOR.dialog.uiElement} UI element (textarea or textInput) + // @param {CKEDITOR.dom.event} evt + toggleBidiKeyUpHandler = function( evt ) { + var keystroke = evt.data.getKeystroke(); + + // ALT + SHIFT + Home for LTR direction. + if ( keystroke == CKEDITOR.SHIFT + CKEDITOR.ALT + 36 ) + this.setDirectionMarker( 'ltr' ); + + // ALT + SHIFT + End for RTL direction. + else if ( keystroke == CKEDITOR.SHIFT + CKEDITOR.ALT + 35 ) + this.setDirectionMarker( 'rtl' ); + }; + + CKEDITOR.tools.extend( CKEDITOR.ui.dialog, { + /** + * Base class for all dialog window elements with a textual label on the left. + * + * @class CKEDITOR.ui.dialog.labeledElement + * @extends CKEDITOR.ui.dialog.uiElement + * @constructor Creates a labeledElement class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `label` (Required) The label string. + * * `labelLayout` (Optional) Put 'horizontal' here if the + * label element is to be laid out horizontally. Otherwise a vertical + * layout will be used. + * * `widths` (Optional) This applies only to horizontal + * layouts — a two-element array of lengths to specify the widths of the + * label and the content element. + * * `role` (Optional) Value for the `role` attribute. + * * `includeLabel` (Optional) If set to `true`, the `aria-labelledby` attribute + * will be included. + * + * @param {Array} htmlList The list of HTML code to output to. + * @param {Function} contentHtml + * A function returning the HTML code string to be added inside the content + * cell. + */ + labeledElement: function( dialog, elementDefinition, htmlList, contentHtml ) { + if ( arguments.length < 4 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition ); + _.labelId = CKEDITOR.tools.getNextId() + '_label'; + this._.children = []; + + var innerHTML = function() { + var html = [], + requiredClass = elementDefinition.required ? ' cke_required' : ''; + if ( elementDefinition.labelLayout != 'horizontal' ) { + html.push( + '', + '' ); + } else { + var hboxDefinition = { + type: 'hbox', + widths: elementDefinition.widths, + padding: 0, + children: [ { + type: 'html', + html: '' + }, + { + type: 'html', + html: '' + + contentHtml.call( this, dialog, elementDefinition ) + + '' + } ] + }; + CKEDITOR.dialog._.uiElementBuilders.hbox.build( dialog, hboxDefinition, html ); + } + return html.join( '' ); + }; + var attributes = { role: elementDefinition.role || 'presentation' }; + + if ( elementDefinition.includeLabel ) + attributes[ 'aria-labelledby' ] = _.labelId; + + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'div', null, attributes, innerHTML ); + }, + + /** + * A text input with a label. This UI element class represents both the + * single-line text inputs and password inputs in dialog boxes. + * + * @class CKEDITOR.ui.dialog.textInput + * @extends CKEDITOR.ui.dialog.labeledElement + * @constructor Creates a textInput class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `default` (Optional) The default value. + * * `validate` (Optional) The validation function. + * * `maxLength` (Optional) The maximum length of text box contents. + * * `size` (Optional) The size of the text box. This is + * usually overridden by the size defined by the skin, though. + * + * @param {Array} htmlList List of HTML code to output to. + */ + textInput: function( dialog, elementDefinition, htmlList ) { + if ( arguments.length < 3 ) + return; + + initPrivateObject.call( this, elementDefinition ); + var domId = this._.inputId = CKEDITOR.tools.getNextId() + '_textInput', + attributes = { 'class': 'cke_dialog_ui_input_' + elementDefinition.type, id: domId, type: elementDefinition.type }; + + // Set the validator, if any. + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + // Set the max length and size. + if ( elementDefinition.maxLength ) + attributes.maxlength = elementDefinition.maxLength; + if ( elementDefinition.size ) + attributes.size = elementDefinition.size; + + if ( elementDefinition.inputStyle ) + attributes.style = elementDefinition.inputStyle; + + // If user presses Enter in a text box, it implies clicking OK for the dialog. + var me = this, + keyPressedOnMe = false; + dialog.on( 'load', function() { + me.getInputElement().on( 'keydown', function( evt ) { + if ( evt.data.getKeystroke() == 13 ) + keyPressedOnMe = true; + } ); + + // Lower the priority this 'keyup' since 'ok' will close the dialog.(#3749) + me.getInputElement().on( 'keyup', function( evt ) { + if ( evt.data.getKeystroke() == 13 && keyPressedOnMe ) { + dialog.getButton( 'ok' ) && setTimeout( function() { + dialog.getButton( 'ok' ).click(); + }, 0 ); + keyPressedOnMe = false; + } + + if ( me.bidi ) + toggleBidiKeyUpHandler.call( me, evt ); + }, null, null, 1000 ); + } ); + + var innerHTML = function() { + // IE BUG: Text input fields in IE at 100% would exceed a or inline + // container's width, so need to wrap it inside a
. + var html = [ '' ); + return html.join( '' ); + }; + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + }, + + /** + * A text area with a label at the top or on the left. + * + * @class CKEDITOR.ui.dialog.textarea + * @extends CKEDITOR.ui.dialog.labeledElement + * @constructor Creates a textarea class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * + * The element definition. Accepted fields: + * + * * `rows` (Optional) The number of rows displayed. + * Defaults to 5 if not defined. + * * `cols` (Optional) The number of cols displayed. + * Defaults to 20 if not defined. Usually overridden by skins. + * * `default` (Optional) The default value. + * * `validate` (Optional) The validation function. + * + * @param {Array} htmlList List of HTML code to output to. + */ + textarea: function( dialog, elementDefinition, htmlList ) { + if ( arguments.length < 3 ) + return; + + initPrivateObject.call( this, elementDefinition ); + var me = this, + domId = this._.inputId = CKEDITOR.tools.getNextId() + '_textarea', + attributes = {}; + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + // Generates the essential attributes for the textarea tag. + attributes.rows = elementDefinition.rows || 5; + attributes.cols = elementDefinition.cols || 20; + + attributes[ 'class' ] = 'cke_dialog_ui_input_textarea ' + ( elementDefinition[ 'class' ] || '' ); + + if ( typeof elementDefinition.inputStyle != 'undefined' ) + attributes.style = elementDefinition.inputStyle; + + if ( elementDefinition.dir ) + attributes.dir = elementDefinition.dir; + + if ( me.bidi ) { + dialog.on( 'load', function() { + me.getInputElement().on( 'keyup', toggleBidiKeyUpHandler ); + }, me ); + } + + var innerHTML = function() { + attributes[ 'aria-labelledby' ] = this._.labelId; + this._.required && ( attributes[ 'aria-required' ] = this._.required ); + var html = [ '' ); + return html.join( '' ); + }; + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + }, + + /** + * A single checkbox with a label on the right. + * + * @class CKEDITOR.ui.dialog.checkbox + * @extends CKEDITOR.ui.dialog.uiElement + * @constructor Creates a checkbox class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `checked` (Optional) Whether the checkbox is checked + * on instantiation. Defaults to `false`. + * * `validate` (Optional) The validation function. + * * `label` (Optional) The checkbox label. + * + * @param {Array} htmlList List of HTML code to output to. + */ + checkbox: function( dialog, elementDefinition, htmlList ) { + if ( arguments.length < 3 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition, { 'default': !!elementDefinition[ 'default' ] } ); + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + var innerHTML = function() { + var myDefinition = CKEDITOR.tools.extend( + {}, + elementDefinition, + { + id: elementDefinition.id ? elementDefinition.id + '_checkbox' : CKEDITOR.tools.getNextId() + '_checkbox' + }, + true + ), + html = []; + + var labelId = CKEDITOR.tools.getNextId() + '_label'; + var attributes = { 'class': 'cke_dialog_ui_checkbox_input', type: 'checkbox', 'aria-labelledby': labelId }; + cleanInnerDefinition( myDefinition ); + if ( elementDefinition[ 'default' ] ) + attributes.checked = 'checked'; + + if ( typeof myDefinition.inputStyle != 'undefined' ) + myDefinition.style = myDefinition.inputStyle; + + _.checkbox = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'input', null, attributes ); + html.push( + ' ' + ); + return html.join( '' ); + }; + + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'span', null, null, innerHTML ); + }, + + /** + * A group of radio buttons. + * + * @class CKEDITOR.ui.dialog.radio + * @extends CKEDITOR.ui.dialog.labeledElement + * @constructor Creates a radio class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `default` (Required) The default value. + * * `validate` (Optional) The validation function. + * * `items` (Required) An array of options. Each option + * is a one- or two-item array of format `[ 'Description', 'Value' ]`. If `'Value'` + * is missing, then the value would be assumed to be the same as the description. + * + * @param {Array} htmlList List of HTML code to output to. + */ + radio: function( dialog, elementDefinition, htmlList ) { + if ( arguments.length < 3 ) + return; + + initPrivateObject.call( this, elementDefinition ); + + if ( !this._[ 'default' ] ) + this._[ 'default' ] = this._.initValue = elementDefinition.items[ 0 ][ 1 ]; + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + var children = [], + me = this; + + var innerHTML = function() { + var inputHtmlList = [], + html = [], + commonName = ( elementDefinition.id ? elementDefinition.id : CKEDITOR.tools.getNextId() ) + '_radio'; + + for ( var i = 0; i < elementDefinition.items.length; i++ ) { + var item = elementDefinition.items[ i ], + title = item[ 2 ] !== undefined ? item[ 2 ] : item[ 0 ], + value = item[ 1 ] !== undefined ? item[ 1 ] : item[ 0 ], + inputId = CKEDITOR.tools.getNextId() + '_radio_input', + labelId = inputId + '_label', + + inputDefinition = CKEDITOR.tools.extend( {}, elementDefinition, { + id: inputId, + title: null, + type: null + }, true ), + + labelDefinition = CKEDITOR.tools.extend( {}, inputDefinition, { + title: title + }, true ), + + inputAttributes = { + type: 'radio', + 'class': 'cke_dialog_ui_radio_input', + name: commonName, + value: value, + 'aria-labelledby': labelId + }, + + inputHtml = []; + + if ( me._[ 'default' ] == value ) + inputAttributes.checked = 'checked'; + + cleanInnerDefinition( inputDefinition ); + cleanInnerDefinition( labelDefinition ); + + if ( typeof inputDefinition.inputStyle != 'undefined' ) + inputDefinition.style = inputDefinition.inputStyle; + + // Make inputs of radio type focusable (#10866). + inputDefinition.keyboardFocusable = true; + + children.push( new CKEDITOR.ui.dialog.uiElement( dialog, inputDefinition, inputHtml, 'input', null, inputAttributes ) ); + + inputHtml.push( ' ' ); + + new CKEDITOR.ui.dialog.uiElement( dialog, labelDefinition, inputHtml, 'label', null, { + id: labelId, + 'for': inputAttributes.id + }, item[ 0 ] ); + + inputHtmlList.push( inputHtml.join( '' ) ); + } + + new CKEDITOR.ui.dialog.hbox( dialog, children, inputHtmlList, html ); + + return html.join( '' ); + }; + + // Adding a role="radiogroup" to definition used for wrapper. + elementDefinition.role = 'radiogroup'; + elementDefinition.includeLabel = true; + + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + this._.children = children; + }, + + /** + * A button with a label inside. + * + * @class CKEDITOR.ui.dialog.button + * @extends CKEDITOR.ui.dialog.uiElement + * @constructor Creates a button class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `label` (Required) The button label. + * * `disabled` (Optional) Set to `true` if you want the + * button to appear in the disabled state. + * + * @param {Array} htmlList List of HTML code to output to. + */ + button: function( dialog, elementDefinition, htmlList ) { + if ( !arguments.length ) + return; + + if ( typeof elementDefinition == 'function' ) + elementDefinition = elementDefinition( dialog.getParentEditor() ); + + initPrivateObject.call( this, elementDefinition, { disabled: elementDefinition.disabled || false } ); + + // Add OnClick event to this input. + CKEDITOR.event.implementOn( this ); + + var me = this; + + // Register an event handler for processing button clicks. + dialog.on( 'load', function() { + var element = this.getElement(); + + ( function() { + element.on( 'click', function( evt ) { + me.click(); + // #9958 + evt.data.preventDefault(); + } ); + + element.on( 'keydown', function( evt ) { + if ( evt.data.getKeystroke() in { 32: 1 } ) { + me.click(); + evt.data.preventDefault(); + } + } ); + } )(); + + element.unselectable(); + }, this ); + + var outerDefinition = CKEDITOR.tools.extend( {}, elementDefinition ); + delete outerDefinition.style; + + var labelId = CKEDITOR.tools.getNextId() + '_label'; + CKEDITOR.ui.dialog.uiElement.call( this, dialog, outerDefinition, htmlList, 'a', null, { + style: elementDefinition.style, + href: 'javascript:void(0)', // jshint ignore:line + title: elementDefinition.label, + hidefocus: 'true', + 'class': elementDefinition[ 'class' ], + role: 'button', + 'aria-labelledby': labelId + }, '' + + CKEDITOR.tools.htmlEncode( elementDefinition.label ) + + '' ); + }, + + /** + * A select box. + * + * @class CKEDITOR.ui.dialog.select + * @extends CKEDITOR.ui.dialog.uiElement + * @constructor Creates a button class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `default` (Required) The default value. + * * `validate` (Optional) The validation function. + * * `items` (Required) An array of options. Each option + * is a one- or two-item array of format `[ 'Description', 'Value' ]`. If `'Value'` + * is missing, then the value would be assumed to be the same as the + * description. + * * `multiple` (Optional) Set this to `true` if you would like + * to have a multiple-choice select box. + * * `size` (Optional) The number of items to display in + * the select box. + * + * @param {Array} htmlList List of HTML code to output to. + */ + select: function( dialog, elementDefinition, htmlList ) { + if ( arguments.length < 3 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition ); + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + _.inputId = CKEDITOR.tools.getNextId() + '_select'; + + var innerHTML = function() { + var myDefinition = CKEDITOR.tools.extend( + {}, + elementDefinition, + { + id: ( elementDefinition.id ? elementDefinition.id + '_select' : CKEDITOR.tools.getNextId() + '_select' ) + }, + true + ), + html = [], + innerHTML = [], + attributes = { 'id': _.inputId, 'class': 'cke_dialog_ui_input_select', 'aria-labelledby': this._.labelId }; + + html.push( '' ); + + return html.join( '' ); + }; + + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + }, + + /** + * A file upload input. + * + * @class CKEDITOR.ui.dialog.file + * @extends CKEDITOR.ui.dialog.labeledElement + * @constructor Creates a file class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `validate` (Optional) The validation function. + * + * @param {Array} htmlList List of HTML code to output to. + */ + file: function( dialog, elementDefinition, htmlList ) { + if ( arguments.length < 3 ) + return; + + if ( elementDefinition[ 'default' ] === undefined ) + elementDefinition[ 'default' ] = ''; + + var _ = CKEDITOR.tools.extend( initPrivateObject.call( this, elementDefinition ), { definition: elementDefinition, buttons: [] } ); + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + /** @ignore */ + var innerHTML = function() { + _.frameId = CKEDITOR.tools.getNextId() + '_fileInput'; + + var html = [ + '' ); + + return html.join( '' ); + }; + + // IE BUG: Parent container does not resize to contain the iframe automatically. + dialog.on( 'load', function() { + var iframe = CKEDITOR.document.getById( _.frameId ), + contentDiv = iframe.getParent(); + contentDiv.addClass( 'cke_dialog_ui_input_file' ); + } ); + + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + }, + + /** + * A button for submitting the file in a file upload input. + * + * @class CKEDITOR.ui.dialog.fileButton + * @extends CKEDITOR.ui.dialog.button + * @constructor Creates a fileButton class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `for` (Required) The file input's page and element ID + * to associate with, in a two-item array format: `[ 'page_id', 'element_id' ]`. + * * `validate` (Optional) The validation function. + * + * @param {Array} htmlList List of HTML code to output to. + */ + fileButton: function( dialog, elementDefinition, htmlList ) { + var me = this; + if ( arguments.length < 3 ) + return; + + initPrivateObject.call( this, elementDefinition ); + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition ); + var onClick = myDefinition.onClick; + myDefinition.className = ( myDefinition.className ? myDefinition.className + ' ' : '' ) + 'cke_dialog_ui_button'; + myDefinition.onClick = function( evt ) { + var target = elementDefinition[ 'for' ]; // [ pageId, elementId ] + if ( !onClick || onClick.call( this, evt ) !== false ) { + dialog.getContentElement( target[ 0 ], target[ 1 ] ).submit(); + this.disable(); + } + }; + + dialog.on( 'load', function() { + dialog.getContentElement( elementDefinition[ 'for' ][ 0 ], elementDefinition[ 'for' ][ 1 ] )._.buttons.push( me ); + } ); + + CKEDITOR.ui.dialog.button.call( this, dialog, myDefinition, htmlList ); + }, + + html: ( function() { + var myHtmlRe = /^\s*<[\w:]+\s+([^>]*)?>/, + theirHtmlRe = /^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/, + emptyTagRe = /\/$/; + /** + * A dialog window element made from raw HTML code. + * + * @class CKEDITOR.ui.dialog.html + * @extends CKEDITOR.ui.dialog.uiElement + * @constructor Creates a html class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element definition. + * Accepted fields: + * + * * `html` (Required) HTML code of this element. + * + * @param {Array} htmlList List of HTML code to be added to the dialog's content area. + */ + return function( dialog, elementDefinition, htmlList ) { + if ( arguments.length < 3 ) + return; + + var myHtmlList = [], + myHtml, + theirHtml = elementDefinition.html, + myMatch, theirMatch; + + // If the HTML input doesn't contain any tags at the beginning, add a tag around it. + if ( theirHtml.charAt( 0 ) != '<' ) + theirHtml = '' + theirHtml + ''; + + // Look for focus function in definition. + var focus = elementDefinition.focus; + if ( focus ) { + var oldFocus = this.focus; + this.focus = function() { + ( typeof focus == 'function' ? focus : oldFocus ).call( this ); + this.fire( 'focus' ); + }; + if ( elementDefinition.isFocusable ) { + var oldIsFocusable = this.isFocusable; + this.isFocusable = oldIsFocusable; + } + this.keyboardFocusable = true; + } + + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, myHtmlList, 'span', null, null, '' ); + + // Append the attributes created by the uiElement call to the real HTML. + myHtml = myHtmlList.join( '' ); + myMatch = myHtml.match( myHtmlRe ); + theirMatch = theirHtml.match( theirHtmlRe ) || [ '', '', '' ]; + + if ( emptyTagRe.test( theirMatch[ 1 ] ) ) { + theirMatch[ 1 ] = theirMatch[ 1 ].slice( 0, -1 ); + theirMatch[ 2 ] = '/' + theirMatch[ 2 ]; + } + + htmlList.push( [ theirMatch[ 1 ], ' ', myMatch[ 1 ] || '', theirMatch[ 2 ] ].join( '' ) ); + }; + } )(), + + /** + * Form fieldset for grouping dialog UI elements. + * + * @class CKEDITOR.ui.dialog.fieldset + * @extends CKEDITOR.ui.dialog.uiElement + * @constructor Creates a fieldset class instance. + * @param {CKEDITOR.dialog} dialog Parent dialog window object. + * @param {Array} childObjList + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container. + * @param {Array} childHtmlList Array of HTML code that corresponds to the HTML output of all the + * objects in childObjList. + * @param {Array} htmlList Array of HTML code that this element will output to. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition + * The element definition. Accepted fields: + * + * * `label` (Optional) The legend of the this fieldset. + * * `children` (Required) An array of dialog window field definitions which will be grouped inside this fieldset. + * + */ + fieldset: function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) { + var legendLabel = elementDefinition.label; + /** @ignore */ + var innerHTML = function() { + var html = []; + legendLabel && html.push( '' + legendLabel + '' ); + for ( var i = 0; i < childHtmlList.length; i++ ) + html.push( childHtmlList[ i ] ); + return html.join( '' ); + }; + + this._ = { children: childObjList }; + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'fieldset', null, null, innerHTML ); + } + + }, true ); + + CKEDITOR.ui.dialog.html.prototype = new CKEDITOR.ui.dialog.uiElement(); + + /** @class CKEDITOR.ui.dialog.labeledElement */ + CKEDITOR.ui.dialog.labeledElement.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement(), { + /** + * Sets the label text of the element. + * + * @param {String} label The new label text. + * @returns {CKEDITOR.ui.dialog.labeledElement} The current labeled element. + */ + setLabel: function( label ) { + var node = CKEDITOR.document.getById( this._.labelId ); + if ( node.getChildCount() < 1 ) + ( new CKEDITOR.dom.text( label, CKEDITOR.document ) ).appendTo( node ); + else + node.getChild( 0 ).$.nodeValue = label; + return this; + }, + + /** + * Retrieves the current label text of the elment. + * + * @returns {String} The current label text. + */ + getLabel: function() { + var node = CKEDITOR.document.getById( this._.labelId ); + if ( !node || node.getChildCount() < 1 ) + return ''; + else + return node.getChild( 0 ).getText(); + }, + + /** + * Defines the `onChange` event for UI element definitions. + * @property {Object} + */ + eventProcessors: commonEventProcessors + }, true ); + + /** @class CKEDITOR.ui.dialog.button */ + CKEDITOR.ui.dialog.button.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement(), { + /** + * Simulates a click to the button. + * + * @returns {Object} Return value of the `click` event. + */ + click: function() { + if ( !this._.disabled ) + return this.fire( 'click', { dialog: this._.dialog } ); + return false; + }, + + /** + * Enables the button. + */ + enable: function() { + this._.disabled = false; + var element = this.getElement(); + element && element.removeClass( 'cke_disabled' ); + }, + + /** + * Disables the button. + */ + disable: function() { + this._.disabled = true; + this.getElement().addClass( 'cke_disabled' ); + }, + + /** + * Checks whether a field is visible. + * + * @returns {Boolean} + */ + isVisible: function() { + return this.getElement().getFirst().isVisible(); + }, + + /** + * Checks whether a field is enabled. Fields can be disabled by using the + * {@link #disable} method and enabled by using the {@link #enable} method. + * + * @returns {Boolean} + */ + isEnabled: function() { + return !this._.disabled; + }, + + /** + * Defines the `onChange` event and `onClick` for button element definitions. + * + * @property {Object} + */ + eventProcessors: CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, { + onClick: function( dialog, func ) { + this.on( 'click', function() { + func.apply( this, arguments ); + } ); + } + }, true ), + + /** + * Handler for the element's access key up event. Simulates a click to + * the button. + */ + accessKeyUp: function() { + this.click(); + }, + + /** + * Handler for the element's access key down event. Simulates a mouse + * down to the button. + */ + accessKeyDown: function() { + this.focus(); + }, + + keyboardFocusable: true + }, true ); + + /** @class CKEDITOR.ui.dialog.textInput */ + CKEDITOR.ui.dialog.textInput.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement(), { + /** + * Gets the text input DOM element under this UI object. + * + * @returns {CKEDITOR.dom.element} The DOM element of the text input. + */ + getInputElement: function() { + return CKEDITOR.document.getById( this._.inputId ); + }, + + /** + * Puts focus into the text input. + */ + focus: function() { + var me = this.selectParentTab(); + + // GECKO BUG: setTimeout() is needed to workaround invisible selections. + setTimeout( function() { + var element = me.getInputElement(); + element && element.$.focus(); + }, 0 ); + }, + + /** + * Selects all the text in the text input. + */ + select: function() { + var me = this.selectParentTab(); + + // GECKO BUG: setTimeout() is needed to workaround invisible selections. + setTimeout( function() { + var e = me.getInputElement(); + if ( e ) { + e.$.focus(); + e.$.select(); + } + }, 0 ); + }, + + /** + * Handler for the text input's access key up event. Makes a `select()` + * call to the text input. + */ + accessKeyUp: function() { + this.select(); + }, + + /** + * Sets the value of this text input object. + * + * uiElement.setValue( 'Blamo' ); + * + * @param {Object} value The new value. + * @returns {CKEDITOR.ui.dialog.textInput} The current UI element. + */ + setValue: function( value ) { + if ( this.bidi ) { + var marker = value && value.charAt( 0 ), + dir = ( marker == '\u202A' ? 'ltr' : marker == '\u202B' ? 'rtl' : null ); + + if ( dir ) { + value = value.slice( 1 ); + } + + // Set the marker or reset it (if dir==null). + this.setDirectionMarker( dir ); + } + + if ( !value ) { + value = ''; + } + + return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply( this, arguments ); + }, + + /** + * Gets the value of this text input object. + * + * @returns {String} The value. + */ + getValue: function() { + var value = CKEDITOR.ui.dialog.uiElement.prototype.getValue.call( this ); + + if ( this.bidi && value ) { + var dir = this.getDirectionMarker(); + if ( dir ) { + value = ( dir == 'ltr' ? '\u202A' : '\u202B' ) + value; + } + } + + return value; + }, + + /** + * Sets the text direction marker and the `dir` attribute of the input element. + * + * @since 4.5 + * @param {String} dir The text direction. Pass `null` to reset. + */ + setDirectionMarker: function( dir ) { + var inputElement = this.getInputElement(); + + if ( dir ) { + inputElement.setAttributes( { + dir: dir, + 'data-cke-dir-marker': dir + } ); + // Don't remove the dir attribute if this field hasn't got the marker, + // because the dir attribute could be set independently. + } else if ( this.getDirectionMarker() ) { + inputElement.removeAttributes( [ 'dir', 'data-cke-dir-marker' ] ); + } + }, + + /** + * Gets the value of the text direction marker. + * + * @since 4.5 + * @returns {String} `'ltr'`, `'rtl'` or `null` if the marker is not set. + */ + getDirectionMarker: function() { + return this.getInputElement().data( 'cke-dir-marker' ); + }, + + keyboardFocusable: true + }, commonPrototype, true ); + + CKEDITOR.ui.dialog.textarea.prototype = new CKEDITOR.ui.dialog.textInput(); + + /** @class CKEDITOR.ui.dialog.select */ + CKEDITOR.ui.dialog.select.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement(), { + /** + * Gets the DOM element of the select box. + * + * @returns {CKEDITOR.dom.element} The `` element of this file input. + * + * @returns {CKEDITOR.dom.element} The file input element. + */ + getInputElement: function() { + var frameDocument = CKEDITOR.document.getById( this._.frameId ).getFrameDocument(); + return frameDocument.$.forms.length > 0 ? new CKEDITOR.dom.element( frameDocument.$.forms[ 0 ].elements[ 0 ] ) : this.getElement(); + }, + + /** + * Uploads the file in the file input. + * + * @returns {CKEDITOR.ui.dialog.file} This object. + */ + submit: function() { + this.getInputElement().getParent().$.submit(); + return this; + }, + + /** + * Gets the action assigned to the form. + * + * @returns {String} The value of the action. + */ + getAction: function() { + return this.getInputElement().getParent().$.action; + }, + + /** + * The events must be applied to the inner input element, and + * this must be done when the iframe and form have been loaded. + */ + registerEvents: function( definition ) { + var regex = /^on([A-Z]\w+)/, + match; + + var registerDomEvent = function( uiElement, dialog, eventName, func ) { + uiElement.on( 'formLoaded', function() { + uiElement.getInputElement().on( eventName, func, uiElement ); + } ); + }; + + for ( var i in definition ) { + if ( !( match = i.match( regex ) ) ) + continue; + + if ( this.eventProcessors[ i ] ) + this.eventProcessors[ i ].call( this, this._.dialog, definition[ i ] ); + else + registerDomEvent( this, this._.dialog, match[ 1 ].toLowerCase(), definition[ i ] ); + } + + return this; + }, + + /** + * Redraws the file input and resets the file path in the file input. + * The redrawing logic is necessary because non-IE browsers tend to clear + * the `