plugin.js 48 KB

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