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