tablepropertiesview.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module table/tableproperties/ui/tablepropertiesview
  7. */
  8. import View from '@ckeditor/ckeditor5-ui/src/view';
  9. import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection';
  10. import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
  11. import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
  12. import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  13. import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler';
  14. import LabeledView from '@ckeditor/ckeditor5-ui/src/labeledview/labeledview';
  15. import { createLabeledDropdown, createLabeledInputText } from '@ckeditor/ckeditor5-ui/src/labeledview/utils';
  16. import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview';
  17. import { addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
  18. import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
  19. import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
  20. import { fillToolbar, getBorderStyleDefinitions, getBorderStyleLabels } from '../../ui/utils';
  21. import FormRowView from '../../ui/formrowview';
  22. import FormHeaderView from '../../ui/formheaderview';
  23. import checkIcon from '@ckeditor/ckeditor5-core/theme/icons/check.svg';
  24. import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
  25. import objectLeftIcon from '@ckeditor/ckeditor5-core/theme/icons/object-left.svg';
  26. import objectRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg';
  27. import objectCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg';
  28. import '../../../theme/form.css';
  29. import '../../../theme/tableform.css';
  30. import '../../../theme/tableproperties.css';
  31. const ALIGNMENT_ICONS = {
  32. left: objectLeftIcon,
  33. center: objectCenterIcon,
  34. right: objectRightIcon
  35. };
  36. /**
  37. * The class representing a table properties form, allowing users to customize
  38. * certain style aspects of a table, for instance, border, background color, alignment, etc..
  39. *
  40. * @extends module:ui/view~View
  41. */
  42. export default class TablePropertiesView extends View {
  43. /**
  44. * @inheritDoc
  45. */
  46. constructor( locale ) {
  47. super( locale );
  48. const { borderStyleDropdown, borderWidthInput, borderColorInput, borderRowLabel } = this._createBorderFields();
  49. const { widthInput, operatorLabel, heightInput, dimensionsLabel } = this._createDimensionFields();
  50. const { alignmentToolbar, alignmentLabel } = this._createAlignmentFields();
  51. /**
  52. * Tracks information about the DOM focus in the form.
  53. *
  54. * @readonly
  55. * @member {module:utils/focustracker~FocusTracker}
  56. */
  57. this.focusTracker = new FocusTracker();
  58. /**
  59. * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
  60. *
  61. * @readonly
  62. * @member {module:utils/keystrokehandler~KeystrokeHandler}
  63. */
  64. this.keystrokes = new KeystrokeHandler();
  65. /**
  66. * A collection of child views in the form.
  67. *
  68. * @readonly
  69. * @type {module:ui/viewcollection~ViewCollection}
  70. */
  71. this.children = this.createCollection();
  72. this.set( {
  73. /**
  74. * The value of the border style.
  75. *
  76. * @observable
  77. * @default 'none'
  78. * @member #borderStyle
  79. */
  80. borderStyle: 'none',
  81. /**
  82. * The value of the border width style.
  83. *
  84. * @observable
  85. * @default ''
  86. * @member #borderWidth
  87. */
  88. borderWidth: '',
  89. /**
  90. * The value of the border color style.
  91. *
  92. * @observable
  93. * @default ''
  94. * @member #borderColor
  95. */
  96. borderColor: '',
  97. /**
  98. * The value of the background color style.
  99. *
  100. * @observable
  101. * @default ''
  102. * @member #backgroundColor
  103. */
  104. backgroundColor: '',
  105. /**
  106. * The value of the table width style.
  107. *
  108. * @observable
  109. * @default ''
  110. * @member #width
  111. */
  112. width: '',
  113. /**
  114. * The value of the table height style.
  115. *
  116. * @observable
  117. * @default ''
  118. * @member #height
  119. */
  120. height: '',
  121. /**
  122. * The value of the table alignment style.
  123. *
  124. * @observable
  125. * @default ''
  126. * @member #alignment
  127. */
  128. alignment: ''
  129. } );
  130. /**
  131. * A dropdown that allows selecting the style of the table border.
  132. *
  133. * @readonly
  134. * @member {module:ui/dropdown/dropdownview~DropdownView}
  135. */
  136. this.borderStyleDropdown = borderStyleDropdown;
  137. /**
  138. * An input that allows specifying the width of the table border.
  139. *
  140. * @readonly
  141. * @member {module:ui/inputtext/inputtextview~InputTextView}
  142. */
  143. this.borderWidthInput = borderWidthInput;
  144. /**
  145. * An input that allows specifying the color of the table border.
  146. *
  147. * @readonly
  148. * @member {module:ui/inputtext/inputtextview~InputTextView}
  149. */
  150. this.borderColorInput = borderColorInput;
  151. /**
  152. * An input that allows specifying the table background color.
  153. *
  154. * @readonly
  155. * @member {module:ui/inputtext/inputtextview~InputTextView}
  156. */
  157. this.backgroundInput = this._createBackgroundField();
  158. /**
  159. * An input that allows specifying the table width.
  160. *
  161. * @readonly
  162. * @member {module:ui/inputtext/inputtextview~InputTextView}
  163. */
  164. this.widthInput = widthInput;
  165. /**
  166. * An input that allows specifying the table height.
  167. *
  168. * @readonly
  169. * @member {module:ui/inputtext/inputtextview~InputTextView}
  170. */
  171. this.heightInput = heightInput;
  172. /**
  173. * A toolbar with buttons that allow changing the alignment of an entire table.
  174. *
  175. * @readonly
  176. * @member {module:ui/toolbar/toolbar~ToolbarView}
  177. */
  178. this.alignmentToolbar = alignmentToolbar;
  179. // Defer creating to make sure other fields are present and the Save button can
  180. // bind its #isEnabled to their error messages so there's no way to save unless all
  181. // fields are valid.
  182. const { saveButtonView, cancelButtonView } = this._createActionButtons();
  183. /**
  184. * The "Save" button view.
  185. *
  186. * @member {module:ui/button/buttonview~ButtonView}
  187. */
  188. this.saveButtonView = saveButtonView;
  189. /**
  190. * The "Cancel" button view.
  191. *
  192. * @member {module:ui/button/buttonview~ButtonView}
  193. */
  194. this.cancelButtonView = cancelButtonView;
  195. /**
  196. * A collection of views that can be focused in the form.
  197. *
  198. * @readonly
  199. * @protected
  200. * @member {module:ui/viewcollection~ViewCollection}
  201. */
  202. this._focusables = new ViewCollection();
  203. /**
  204. * Helps cycling over {@link #_focusables} in the form.
  205. *
  206. * @readonly
  207. * @protected
  208. * @member {module:ui/focuscycler~FocusCycler}
  209. */
  210. this._focusCycler = new FocusCycler( {
  211. focusables: this._focusables,
  212. focusTracker: this.focusTracker,
  213. keystrokeHandler: this.keystrokes,
  214. actions: {
  215. // Navigate form fields backwards using the Shift + Tab keystroke.
  216. focusPrevious: 'shift + tab',
  217. // Navigate form fields forwards using the Tab key.
  218. focusNext: 'tab'
  219. }
  220. } );
  221. // Form header.
  222. this.children.add( new FormHeaderView( locale, {
  223. label: this.t( 'Table properties' )
  224. } ) );
  225. // Border row.
  226. this.children.add( new FormRowView( locale, {
  227. labelView: borderRowLabel,
  228. children: [
  229. borderRowLabel,
  230. borderStyleDropdown,
  231. borderColorInput,
  232. borderWidthInput
  233. ],
  234. class: 'ck-table-form__border-row'
  235. } ) );
  236. // Background row.
  237. this.children.add( new FormRowView( locale, {
  238. children: [
  239. this.backgroundInput
  240. ]
  241. } ) );
  242. this.children.add( new FormRowView( locale, {
  243. children: [
  244. // Dimensions row.
  245. new FormRowView( locale, {
  246. labelView: dimensionsLabel,
  247. children: [
  248. dimensionsLabel,
  249. widthInput,
  250. operatorLabel,
  251. heightInput
  252. ],
  253. class: 'ck-table-properties-form__dimensions-row'
  254. } ),
  255. // Alignment row.
  256. new FormRowView( locale, {
  257. labelView: alignmentLabel,
  258. children: [
  259. alignmentLabel,
  260. alignmentToolbar
  261. ],
  262. class: 'ck-table-properties-form__alignment-row'
  263. } )
  264. ]
  265. } ) );
  266. // Action row.
  267. this.children.add( new FormRowView( locale, {
  268. children: [
  269. this.saveButtonView,
  270. this.cancelButtonView,
  271. ],
  272. class: 'ck-table-form__action-row'
  273. } ) );
  274. this.setTemplate( {
  275. tag: 'form',
  276. attributes: {
  277. class: [
  278. 'ck',
  279. 'ck-form',
  280. 'ck-table-form',
  281. 'ck-table-properties-form'
  282. ],
  283. // https://github.com/ckeditor/ckeditor5-link/issues/90
  284. tabindex: '-1'
  285. },
  286. children: this.children
  287. } );
  288. }
  289. /**
  290. * @inheritDoc
  291. */
  292. render() {
  293. super.render();
  294. // Enable the "submit" event for this view. It can be triggered by the #saveButtonView
  295. // which is of the "submit" DOM "type".
  296. submitHandler( {
  297. view: this
  298. } );
  299. [
  300. this.borderStyleDropdown,
  301. this.borderColorInput,
  302. this.borderWidthInput,
  303. this.backgroundInput,
  304. this.widthInput,
  305. this.heightInput,
  306. this.alignmentToolbar,
  307. this.saveButtonView,
  308. this.cancelButtonView,
  309. ].forEach( view => {
  310. // Register the view as focusable.
  311. this._focusables.add( view );
  312. // Register the view in the focus tracker.
  313. this.focusTracker.add( view.element );
  314. } );
  315. // Mainly for closing using "Esc" and navigation using "Tab".
  316. this.keystrokes.listenTo( this.element );
  317. }
  318. /**
  319. * Focuses the fist focusable field in the form.
  320. */
  321. focus() {
  322. this._focusCycler.focusFirst();
  323. }
  324. /**
  325. * Creates the following form fields:
  326. *
  327. * * {@link #borderStyleDropdown},
  328. * * {@link #borderWidthInput},
  329. * * {@link #borderColorInput}.
  330. *
  331. * @private
  332. * @returns {Object.<String,module:ui/view~View>}
  333. */
  334. _createBorderFields() {
  335. const locale = this.locale;
  336. const t = this.t;
  337. // -- Group label ---------------------------------------------
  338. const borderRowLabel = new LabelView( locale );
  339. borderRowLabel.text = t( 'Border' );
  340. // -- Style ---------------------------------------------------
  341. const styleLabels = getBorderStyleLabels( this.t );
  342. const borderStyleDropdown = new LabeledView( locale, createLabeledDropdown );
  343. borderStyleDropdown.set( {
  344. label: t( 'Style' ),
  345. class: 'ck-table-form__border-style'
  346. } );
  347. borderStyleDropdown.view.buttonView.set( {
  348. isOn: false,
  349. withText: true,
  350. tooltip: t( 'Style' )
  351. } );
  352. borderStyleDropdown.view.buttonView.bind( 'label' ).to( this, 'borderStyle', value => {
  353. return styleLabels[ value ];
  354. } );
  355. borderStyleDropdown.view.on( 'execute', evt => {
  356. this.borderStyle = evt.source._borderStyleValue;
  357. } );
  358. addListToDropdown( borderStyleDropdown.view, getBorderStyleDefinitions( this ) );
  359. // -- Width ---------------------------------------------------
  360. const borderWidthInput = new LabeledView( locale, createLabeledInputText );
  361. borderWidthInput.set( {
  362. label: t( 'Width' ),
  363. class: 'ck-table-form__border-width',
  364. } );
  365. borderWidthInput.view.bind( 'value' ).to( this, 'borderWidth' );
  366. borderWidthInput.bind( 'isEnabled' ).to( this, 'borderStyle', value => {
  367. return value !== 'none';
  368. } );
  369. borderWidthInput.view.on( 'input', () => {
  370. this.borderWidth = borderWidthInput.view.element.value;
  371. } );
  372. // -- Color ---------------------------------------------------
  373. const borderColorInput = new LabeledView( locale, createLabeledInputText );
  374. borderColorInput.label = t( 'Color' );
  375. borderColorInput.view.bind( 'value' ).to( this, 'borderColor' );
  376. borderColorInput.bind( 'isEnabled' ).to( this, 'borderStyle', value => {
  377. return value !== 'none';
  378. } );
  379. borderColorInput.view.on( 'input', () => {
  380. this.borderColor = borderColorInput.view.element.value;
  381. } );
  382. return {
  383. borderRowLabel,
  384. borderStyleDropdown,
  385. borderColorInput,
  386. borderWidthInput,
  387. };
  388. }
  389. /**
  390. * Creates the following form fields:
  391. *
  392. * * {@link #backgroundInput}.
  393. *
  394. * @private
  395. * @returns {module:ui/labeledview/labeledview~LabeledView}
  396. */
  397. _createBackgroundField() {
  398. const locale = this.locale;
  399. const t = this.t;
  400. const backgroundInput = new LabeledView( locale, createLabeledInputText );
  401. backgroundInput.set( {
  402. label: t( 'Background' ),
  403. class: 'ck-table-properties-form__background',
  404. } );
  405. backgroundInput.view.bind( 'value' ).to( this, 'backgroundColor' );
  406. backgroundInput.view.on( 'input', () => {
  407. this.backgroundColor = backgroundInput.view.element.value;
  408. } );
  409. return backgroundInput;
  410. }
  411. /**
  412. * Creates the following form fields:
  413. *
  414. * * {@link #widthInput}.
  415. * * {@link #heightInput}.
  416. *
  417. * @private
  418. * @returns {module:ui/labeledview/labeledview~LabeledView}
  419. */
  420. _createDimensionFields() {
  421. const locale = this.locale;
  422. const t = this.t;
  423. // -- Label ---------------------------------------------------
  424. const dimensionsLabel = new LabelView( locale );
  425. dimensionsLabel.text = t( 'Dimensions' );
  426. // -- Width ---------------------------------------------------
  427. const widthInput = new LabeledView( locale, createLabeledInputText );
  428. widthInput.set( {
  429. label: t( 'Width' ),
  430. class: 'ck-table-properties-form__width',
  431. } );
  432. widthInput.view.bind( 'value' ).to( this, 'width' );
  433. widthInput.view.on( 'input', () => {
  434. this.width = widthInput.view.element.value;
  435. } );
  436. // -- Operator ---------------------------------------------------
  437. const operatorLabel = new View( locale );
  438. operatorLabel.setTemplate( {
  439. tag: 'span',
  440. attributes: {
  441. class: [
  442. 'ck-table-form__dimension-operator'
  443. ]
  444. },
  445. children: [
  446. { text: '×' }
  447. ]
  448. } );
  449. // -- Height ---------------------------------------------------
  450. const heightInput = new LabeledView( locale, createLabeledInputText );
  451. heightInput.set( {
  452. label: t( 'Height' ),
  453. class: 'ck-table-properties-form__height',
  454. } );
  455. heightInput.view.bind( 'value' ).to( this, 'height' );
  456. heightInput.view.on( 'input', () => {
  457. this.height = heightInput.view.element.value;
  458. } );
  459. return {
  460. dimensionsLabel,
  461. widthInput,
  462. operatorLabel,
  463. heightInput
  464. };
  465. }
  466. /**
  467. * Creates the following form fields:
  468. *
  469. * * {@link #alignmentToolbar},
  470. *
  471. * @private
  472. * @returns {Object.<String,module:ui/view~View>}
  473. */
  474. _createAlignmentFields() {
  475. const locale = this.locale;
  476. const t = this.t;
  477. // -- Label ---------------------------------------------------
  478. const alignmentLabel = new LabelView( locale );
  479. alignmentLabel.text = t( 'Alignment' );
  480. // -- Toolbar ---------------------------------------------------
  481. const alignmentToolbar = new ToolbarView( locale );
  482. alignmentToolbar.set( {
  483. isCompact: true,
  484. ariaLabel: t( 'Table alignment toolbar' )
  485. } );
  486. fillToolbar( {
  487. view: this,
  488. icons: ALIGNMENT_ICONS,
  489. toolbar: alignmentToolbar,
  490. labels: this._alignmentLabels,
  491. propertyName: 'alignment',
  492. nameToValue: name => {
  493. return name === 'center' ? '' : name;
  494. }
  495. } );
  496. return {
  497. alignmentLabel,
  498. alignmentToolbar
  499. };
  500. }
  501. /**
  502. * Creates the following form controls:
  503. *
  504. * * {@link #saveButtonView},
  505. * * {@link #cancelButtonView}.
  506. *
  507. * @private
  508. * @returns {Object.<String,module:ui/view~View>}
  509. */
  510. _createActionButtons() {
  511. const locale = this.locale;
  512. const t = this.t;
  513. const saveButtonView = new ButtonView( locale );
  514. const cancelButtonView = new ButtonView( locale );
  515. const fieldsThatShouldValidateToSave = [
  516. this.borderWidthInput,
  517. this.borderColorInput,
  518. this.backgroundInput,
  519. this.widthInput,
  520. this.heightInput
  521. ];
  522. saveButtonView.set( {
  523. label: t( 'Save' ),
  524. icon: checkIcon,
  525. class: 'ck-button-save',
  526. type: 'submit',
  527. withText: true,
  528. } );
  529. saveButtonView.bind( 'isEnabled' ).toMany( fieldsThatShouldValidateToSave, 'errorText', ( ...errorTexts ) => {
  530. return errorTexts.every( errorText => !errorText );
  531. } );
  532. cancelButtonView.set( {
  533. label: t( 'Cancel' ),
  534. icon: cancelIcon,
  535. class: 'ck-button-cancel',
  536. type: 'cancel',
  537. withText: true,
  538. } );
  539. cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
  540. return {
  541. saveButtonView, cancelButtonView
  542. };
  543. }
  544. /**
  545. * Provides localized labels for {@link #alignmentToolbar} buttons.
  546. *
  547. * @private
  548. * @type {Object.<String,String>}
  549. */
  550. get _alignmentLabels() {
  551. const t = this.t;
  552. return {
  553. left: t( 'Align table to the left' ),
  554. center: t( 'Center table' ),
  555. right: t( 'Align table to the right' )
  556. };
  557. }
  558. }