plugin.js 52 KB

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