tableCell.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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. CKEDITOR.dialog.add( 'cellProperties', function( editor ) {
  6. var langTable = editor.lang.table,
  7. langCell = langTable.cell,
  8. langCommon = editor.lang.common,
  9. validate = CKEDITOR.dialog.validate,
  10. widthPattern = /^(\d+(?:\.\d+)?)(px|%)$/,
  11. spacer = { type: 'html', html: ' ' },
  12. rtl = editor.lang.dir == 'rtl',
  13. colorDialog = editor.plugins.colordialog;
  14. // Returns a function, which runs regular "setup" for all selected cells to find out
  15. // whether the initial value of the field would be the same for all cells. If so,
  16. // the value is displayed just as if a regular "setup" was executed. Otherwise,
  17. // i.e. when there are several cells of different value of the property, a field
  18. // gets empty value.
  19. //
  20. // * @param {Function} setup Setup function which returns a value instead of setting it.
  21. // * @returns {Function} A function to be used in dialog definition.
  22. function setupCells( setup ) {
  23. return function( cells ) {
  24. var fieldValue = setup( cells[ 0 ] );
  25. // If one of the cells would have a different value of the
  26. // property, set the empty value for a field.
  27. for ( var i = 1; i < cells.length; i++ ) {
  28. if ( setup( cells[ i ] ) !== fieldValue ) {
  29. fieldValue = null;
  30. break;
  31. }
  32. }
  33. // Setting meaningful or empty value only makes sense
  34. // when setup returns some value. Otherwise, a *default* value
  35. // is used for that field.
  36. if ( typeof fieldValue != 'undefined' ) {
  37. this.setValue( fieldValue );
  38. // The only way to have an empty select value in Firefox is
  39. // to set a negative selectedIndex.
  40. if ( CKEDITOR.env.gecko && this.type == 'select' && !fieldValue )
  41. this.getInputElement().$.selectedIndex = -1;
  42. }
  43. };
  44. }
  45. // Reads the unit of width property of the table cell.
  46. //
  47. // * @param {CKEDITOR.dom.element} cell An element representing table cell.
  48. // * @returns {String} A unit of width: 'px', '%' or undefined if none.
  49. function getCellWidthType( cell ) {
  50. var match = widthPattern.exec(
  51. cell.getStyle( 'width' ) || cell.getAttribute( 'width' ) );
  52. if ( match )
  53. return match[ 2 ];
  54. }
  55. return {
  56. title: langCell.title,
  57. minWidth: CKEDITOR.env.ie && CKEDITOR.env.quirks ? 450 : 410,
  58. minHeight: CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.quirks ) ? 230 : 220,
  59. contents: [ {
  60. id: 'info',
  61. label: langCell.title,
  62. accessKey: 'I',
  63. elements: [ {
  64. type: 'hbox',
  65. widths: [ '40%', '5%', '40%' ],
  66. children: [ {
  67. type: 'vbox',
  68. padding: 0,
  69. children: [ {
  70. type: 'hbox',
  71. widths: [ '70%', '30%' ],
  72. children: [ {
  73. type: 'text',
  74. id: 'width',
  75. width: '100px',
  76. label: langCommon.width,
  77. validate: validate.number( langCell.invalidWidth ),
  78. // Extra labelling of width unit type.
  79. onLoad: function() {
  80. var widthType = this.getDialog().getContentElement( 'info', 'widthType' ),
  81. labelElement = widthType.getElement(),
  82. inputElement = this.getInputElement(),
  83. ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' );
  84. inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) );
  85. },
  86. setup: setupCells( function( element ) {
  87. var widthAttr = parseInt( element.getAttribute( 'width' ), 10 ),
  88. widthStyle = parseInt( element.getStyle( 'width' ), 10 );
  89. return !isNaN( widthStyle ) ? widthStyle :
  90. !isNaN( widthAttr ) ? widthAttr : '';
  91. } ),
  92. commit: function( element ) {
  93. var value = parseInt( this.getValue(), 10 ),
  94. // There might be no widthType value, i.e. when multiple cells are
  95. // selected but some of them have width expressed in pixels and some
  96. // of them in percent. Try to re-read the unit from the cell in such
  97. // case (#11439).
  98. unit = this.getDialog().getValueOf( 'info', 'widthType' ) || getCellWidthType( element );
  99. if ( !isNaN( value ) )
  100. element.setStyle( 'width', value + unit );
  101. else
  102. element.removeStyle( 'width' );
  103. element.removeAttribute( 'width' );
  104. },
  105. 'default': ''
  106. },
  107. {
  108. type: 'select',
  109. id: 'widthType',
  110. label: editor.lang.table.widthUnit,
  111. labelStyle: 'visibility:hidden',
  112. 'default': 'px',
  113. items: [
  114. [ langTable.widthPx, 'px' ],
  115. [ langTable.widthPc, '%' ]
  116. ],
  117. setup: setupCells( getCellWidthType )
  118. } ]
  119. },
  120. {
  121. type: 'hbox',
  122. widths: [ '70%', '30%' ],
  123. children: [ {
  124. type: 'text',
  125. id: 'height',
  126. label: langCommon.height,
  127. width: '100px',
  128. 'default': '',
  129. validate: validate.number( langCell.invalidHeight ),
  130. // Extra labelling of height unit type.
  131. onLoad: function() {
  132. var heightType = this.getDialog().getContentElement( 'info', 'htmlHeightType' ),
  133. labelElement = heightType.getElement(),
  134. inputElement = this.getInputElement(),
  135. ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' );
  136. inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) );
  137. },
  138. setup: setupCells( function( element ) {
  139. var heightAttr = parseInt( element.getAttribute( 'height' ), 10 ),
  140. heightStyle = parseInt( element.getStyle( 'height' ), 10 );
  141. return !isNaN( heightStyle ) ? heightStyle :
  142. !isNaN( heightAttr ) ? heightAttr : '';
  143. } ),
  144. commit: function( element ) {
  145. var value = parseInt( this.getValue(), 10 );
  146. if ( !isNaN( value ) )
  147. element.setStyle( 'height', CKEDITOR.tools.cssLength( value ) );
  148. else
  149. element.removeStyle( 'height' );
  150. element.removeAttribute( 'height' );
  151. }
  152. },
  153. {
  154. id: 'htmlHeightType',
  155. type: 'html',
  156. html: '<br />' + langTable.widthPx
  157. } ]
  158. },
  159. spacer,
  160. {
  161. type: 'select',
  162. id: 'wordWrap',
  163. label: langCell.wordWrap,
  164. 'default': 'yes',
  165. items: [
  166. [ langCell.yes, 'yes' ],
  167. [ langCell.no, 'no' ]
  168. ],
  169. setup: setupCells( function( element ) {
  170. var wordWrapAttr = element.getAttribute( 'noWrap' ),
  171. wordWrapStyle = element.getStyle( 'white-space' );
  172. if ( wordWrapStyle == 'nowrap' || wordWrapAttr )
  173. return 'no';
  174. } ),
  175. commit: function( element ) {
  176. if ( this.getValue() == 'no' )
  177. element.setStyle( 'white-space', 'nowrap' );
  178. else
  179. element.removeStyle( 'white-space' );
  180. element.removeAttribute( 'noWrap' );
  181. }
  182. },
  183. spacer,
  184. {
  185. type: 'select',
  186. id: 'hAlign',
  187. label: langCell.hAlign,
  188. 'default': '',
  189. items: [
  190. [ langCommon.notSet, '' ],
  191. [ langCommon.alignLeft, 'left' ],
  192. [ langCommon.alignCenter, 'center' ],
  193. [ langCommon.alignRight, 'right' ],
  194. [ langCommon.alignJustify, 'justify' ]
  195. ],
  196. setup: setupCells( function( element ) {
  197. var alignAttr = element.getAttribute( 'align' ),
  198. textAlignStyle = element.getStyle( 'text-align' );
  199. return textAlignStyle || alignAttr || '';
  200. } ),
  201. commit: function( selectedCell ) {
  202. var value = this.getValue();
  203. if ( value )
  204. selectedCell.setStyle( 'text-align', value );
  205. else
  206. selectedCell.removeStyle( 'text-align' );
  207. selectedCell.removeAttribute( 'align' );
  208. }
  209. },
  210. {
  211. type: 'select',
  212. id: 'vAlign',
  213. label: langCell.vAlign,
  214. 'default': '',
  215. items: [
  216. [ langCommon.notSet, '' ],
  217. [ langCommon.alignTop, 'top' ],
  218. [ langCommon.alignMiddle, 'middle' ],
  219. [ langCommon.alignBottom, 'bottom' ],
  220. [ langCell.alignBaseline, 'baseline' ]
  221. ],
  222. setup: setupCells( function( element ) {
  223. var vAlignAttr = element.getAttribute( 'vAlign' ),
  224. vAlignStyle = element.getStyle( 'vertical-align' );
  225. switch ( vAlignStyle ) {
  226. // Ignore all other unrelated style values..
  227. case 'top':
  228. case 'middle':
  229. case 'bottom':
  230. case 'baseline':
  231. break;
  232. default:
  233. vAlignStyle = '';
  234. }
  235. return vAlignStyle || vAlignAttr || '';
  236. } ),
  237. commit: function( element ) {
  238. var value = this.getValue();
  239. if ( value )
  240. element.setStyle( 'vertical-align', value );
  241. else
  242. element.removeStyle( 'vertical-align' );
  243. element.removeAttribute( 'vAlign' );
  244. }
  245. } ]
  246. },
  247. spacer,
  248. {
  249. type: 'vbox',
  250. padding: 0,
  251. children: [ {
  252. type: 'select',
  253. id: 'cellType',
  254. label: langCell.cellType,
  255. 'default': 'td',
  256. items: [
  257. [ langCell.data, 'td' ],
  258. [ langCell.header, 'th' ]
  259. ],
  260. setup: setupCells( function( selectedCell ) {
  261. return selectedCell.getName();
  262. } ),
  263. commit: function( selectedCell ) {
  264. selectedCell.renameNode( this.getValue() );
  265. }
  266. },
  267. spacer,
  268. {
  269. type: 'text',
  270. id: 'rowSpan',
  271. label: langCell.rowSpan,
  272. 'default': '',
  273. validate: validate.integer( langCell.invalidRowSpan ),
  274. setup: setupCells( function( selectedCell ) {
  275. var attrVal = parseInt( selectedCell.getAttribute( 'rowSpan' ), 10 );
  276. if ( attrVal && attrVal != 1 )
  277. return attrVal;
  278. } ),
  279. commit: function( selectedCell ) {
  280. var value = parseInt( this.getValue(), 10 );
  281. if ( value && value != 1 )
  282. selectedCell.setAttribute( 'rowSpan', this.getValue() );
  283. else
  284. selectedCell.removeAttribute( 'rowSpan' );
  285. }
  286. },
  287. {
  288. type: 'text',
  289. id: 'colSpan',
  290. label: langCell.colSpan,
  291. 'default': '',
  292. validate: validate.integer( langCell.invalidColSpan ),
  293. setup: setupCells( function( element ) {
  294. var attrVal = parseInt( element.getAttribute( 'colSpan' ), 10 );
  295. if ( attrVal && attrVal != 1 )
  296. return attrVal;
  297. } ),
  298. commit: function( selectedCell ) {
  299. var value = parseInt( this.getValue(), 10 );
  300. if ( value && value != 1 )
  301. selectedCell.setAttribute( 'colSpan', this.getValue() );
  302. else
  303. selectedCell.removeAttribute( 'colSpan' );
  304. }
  305. },
  306. spacer,
  307. {
  308. type: 'hbox',
  309. padding: 0,
  310. widths: [ '60%', '40%' ],
  311. children: [ {
  312. type: 'text',
  313. id: 'bgColor',
  314. label: langCell.bgColor,
  315. 'default': '',
  316. setup: setupCells( function( element ) {
  317. var bgColorAttr = element.getAttribute( 'bgColor' ),
  318. bgColorStyle = element.getStyle( 'background-color' );
  319. return bgColorStyle || bgColorAttr;
  320. } ),
  321. commit: function( selectedCell ) {
  322. var value = this.getValue();
  323. if ( value )
  324. selectedCell.setStyle( 'background-color', this.getValue() );
  325. else
  326. selectedCell.removeStyle( 'background-color' );
  327. selectedCell.removeAttribute( 'bgColor' );
  328. }
  329. },
  330. colorDialog ? {
  331. type: 'button',
  332. id: 'bgColorChoose',
  333. 'class': 'colorChooser', // jshint ignore:line
  334. label: langCell.chooseColor,
  335. onLoad: function() {
  336. // Stick the element to the bottom (#5587)
  337. this.getElement().getParent().setStyle( 'vertical-align', 'bottom' );
  338. },
  339. onClick: function() {
  340. editor.getColorFromDialog( function( color ) {
  341. if ( color )
  342. this.getDialog().getContentElement( 'info', 'bgColor' ).setValue( color );
  343. this.focus();
  344. }, this );
  345. }
  346. } : spacer ]
  347. },
  348. spacer,
  349. {
  350. type: 'hbox',
  351. padding: 0,
  352. widths: [ '60%', '40%' ],
  353. children: [ {
  354. type: 'text',
  355. id: 'borderColor',
  356. label: langCell.borderColor,
  357. 'default': '',
  358. setup: setupCells( function( element ) {
  359. var borderColorAttr = element.getAttribute( 'borderColor' ),
  360. borderColorStyle = element.getStyle( 'border-color' );
  361. return borderColorStyle || borderColorAttr;
  362. } ),
  363. commit: function( selectedCell ) {
  364. var value = this.getValue();
  365. if ( value )
  366. selectedCell.setStyle( 'border-color', this.getValue() );
  367. else
  368. selectedCell.removeStyle( 'border-color' );
  369. selectedCell.removeAttribute( 'borderColor' );
  370. }
  371. },
  372. colorDialog ? {
  373. type: 'button',
  374. id: 'borderColorChoose',
  375. 'class': 'colorChooser', // jshint ignore:line
  376. label: langCell.chooseColor,
  377. style: ( rtl ? 'margin-right' : 'margin-left' ) + ': 10px',
  378. onLoad: function() {
  379. // Stick the element to the bottom (#5587)
  380. this.getElement().getParent().setStyle( 'vertical-align', 'bottom' );
  381. },
  382. onClick: function() {
  383. editor.getColorFromDialog( function( color ) {
  384. if ( color )
  385. this.getDialog().getContentElement( 'info', 'borderColor' ).setValue( color );
  386. this.focus();
  387. }, this );
  388. }
  389. } : spacer ]
  390. } ]
  391. } ]
  392. } ]
  393. } ],
  394. onShow: function() {
  395. this.cells = CKEDITOR.plugins.tabletools.getSelectedCells( this._.editor.getSelection() );
  396. this.setupContent( this.cells );
  397. },
  398. onOk: function() {
  399. var selection = this._.editor.getSelection(),
  400. bookmarks = selection.createBookmarks();
  401. var cells = this.cells;
  402. for ( var i = 0; i < cells.length; i++ )
  403. this.commitContent( cells[ i ] );
  404. this._.editor.forceNextSelectionCheck();
  405. selection.selectBookmarks( bookmarks );
  406. this._.editor.selectionChange();
  407. },
  408. onLoad: function() {
  409. var saved = {};
  410. // Prevent from changing cell properties when the field's value
  411. // remains unaltered, i.e. when selected multiple cells and dialog loaded
  412. // only the properties of the first cell (#11439).
  413. this.foreach( function( field ) {
  414. if ( !field.setup || !field.commit )
  415. return;
  416. // Save field's value every time after "setup" is called.
  417. field.setup = CKEDITOR.tools.override( field.setup, function( orgSetup ) {
  418. return function() {
  419. orgSetup.apply( this, arguments );
  420. saved[ field.id ] = field.getValue();
  421. };
  422. } );
  423. // Compare saved value with actual value. Update cell only if value has changed.
  424. field.commit = CKEDITOR.tools.override( field.commit, function( orgCommit ) {
  425. return function() {
  426. if ( saved[ field.id ] !== field.getValue() )
  427. orgCommit.apply( this, arguments );
  428. };
  429. } );
  430. } );
  431. }
  432. };
  433. } );