8
0

linkformview.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  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 link/ui/linkformview
  7. */
  8. import View from '@ckeditor/ckeditor5-ui/src/view';
  9. import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection';
  10. import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
  11. import SwitchButtonView from '@ckeditor/ckeditor5-ui/src/button/switchbuttonview';
  12. import LabeledFieldView from '@ckeditor/ckeditor5-ui/src/labeledfield/labeledfieldview';
  13. import { createLabeledInputText } from '@ckeditor/ckeditor5-ui/src/labeledfield/utils';
  14. import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
  15. import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  16. import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler';
  17. import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
  18. import checkIcon from '@ckeditor/ckeditor5-core/theme/icons/check.svg';
  19. import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
  20. import '../../theme/linkform.css';
  21. import '@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css';
  22. /**
  23. * The link form view controller class.
  24. *
  25. * See {@link module:link/ui/linkformview~LinkFormView}.
  26. *
  27. * @extends module:ui/view~View
  28. */
  29. export default class LinkFormView extends View {
  30. /**
  31. * Creates an instance of the {@link module:link/ui/linkformview~LinkFormView} class.
  32. *
  33. * Also see {@link #render}.
  34. *
  35. * @param {module:utils/locale~Locale} [locale] The localization services instance.
  36. * @param {module:link/linkcommand~LinkCommand} linkCommand Reference to {@link module:link/linkcommand~LinkCommand}.
  37. * @param {String} [protocol] A value of a protocol to be displayed in the input's placeholder.
  38. */
  39. constructor( locale, linkCommand, protocol ) {
  40. super( locale );
  41. const t = locale.t;
  42. /**
  43. * Tracks information about DOM focus in the form.
  44. *
  45. * @readonly
  46. * @member {module:utils/focustracker~FocusTracker}
  47. */
  48. this.focusTracker = new FocusTracker();
  49. /**
  50. * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
  51. *
  52. * @readonly
  53. * @member {module:utils/keystrokehandler~KeystrokeHandler}
  54. */
  55. this.keystrokes = new KeystrokeHandler();
  56. /**
  57. * The URL input view.
  58. *
  59. * @member {module:ui/labeledfield/labeledfieldview~LabeledFieldView}
  60. */
  61. this.urlInputView = this._createUrlInput( protocol );
  62. /**
  63. * The Save button view.
  64. *
  65. * @member {module:ui/button/buttonview~ButtonView}
  66. */
  67. this.saveButtonView = this._createButton( t( 'Save' ), checkIcon, 'ck-button-save' );
  68. this.saveButtonView.type = 'submit';
  69. /**
  70. * The Cancel button view.
  71. *
  72. * @member {module:ui/button/buttonview~ButtonView}
  73. */
  74. this.cancelButtonView = this._createButton( t( 'Cancel' ), cancelIcon, 'ck-button-cancel', 'cancel' );
  75. /**
  76. * A collection of {@link module:ui/button/switchbuttonview~SwitchButtonView},
  77. * which corresponds to {@link module:link/linkcommand~LinkCommand#manualDecorators manual decorators}
  78. * configured in the editor.
  79. *
  80. * @private
  81. * @readonly
  82. * @type {module:ui/viewcollection~ViewCollection}
  83. */
  84. this._manualDecoratorSwitches = this._createManualDecoratorSwitches( linkCommand );
  85. /**
  86. * A collection of child views in the form.
  87. *
  88. * @readonly
  89. * @type {module:ui/viewcollection~ViewCollection}
  90. */
  91. this.children = this._createFormChildren( linkCommand.manualDecorators );
  92. /**
  93. * A collection of views that can be focused in the form.
  94. *
  95. * @readonly
  96. * @protected
  97. * @member {module:ui/viewcollection~ViewCollection}
  98. */
  99. this._focusables = new ViewCollection();
  100. /**
  101. * Helps cycling over {@link #_focusables} in the form.
  102. *
  103. * @readonly
  104. * @protected
  105. * @member {module:ui/focuscycler~FocusCycler}
  106. */
  107. this._focusCycler = new FocusCycler( {
  108. focusables: this._focusables,
  109. focusTracker: this.focusTracker,
  110. keystrokeHandler: this.keystrokes,
  111. actions: {
  112. // Navigate form fields backwards using the Shift + Tab keystroke.
  113. focusPrevious: 'shift + tab',
  114. // Navigate form fields forwards using the Tab key.
  115. focusNext: 'tab'
  116. }
  117. } );
  118. const classList = [ 'ck', 'ck-link-form', 'ck-responsive-form' ];
  119. if ( linkCommand.manualDecorators.length ) {
  120. classList.push( 'ck-link-form_layout-vertical', 'ck-vertical-form' );
  121. }
  122. this.setTemplate( {
  123. tag: 'form',
  124. attributes: {
  125. class: classList,
  126. // https://github.com/ckeditor/ckeditor5-link/issues/90
  127. tabindex: '-1'
  128. },
  129. children: this.children
  130. } );
  131. }
  132. /**
  133. * Obtains the state of the {@link module:ui/button/switchbuttonview~SwitchButtonView switch buttons} representing
  134. * {@link module:link/linkcommand~LinkCommand#manualDecorators manual link decorators}
  135. * in the {@link module:link/ui/linkformview~LinkFormView}.
  136. *
  137. * @returns {Object.<String,Boolean>} Key-value pairs, where the key is the name of the decorator and the value is
  138. * its state.
  139. */
  140. getDecoratorSwitchesState() {
  141. return Array.from( this._manualDecoratorSwitches ).reduce( ( accumulator, switchButton ) => {
  142. accumulator[ switchButton.name ] = switchButton.isOn;
  143. return accumulator;
  144. }, {} );
  145. }
  146. /**
  147. * @inheritDoc
  148. */
  149. render() {
  150. super.render();
  151. submitHandler( {
  152. view: this
  153. } );
  154. const childViews = [
  155. this.urlInputView,
  156. ...this._manualDecoratorSwitches,
  157. this.saveButtonView,
  158. this.cancelButtonView
  159. ];
  160. childViews.forEach( v => {
  161. // Register the view as focusable.
  162. this._focusables.add( v );
  163. // Register the view in the focus tracker.
  164. this.focusTracker.add( v.element );
  165. } );
  166. // Start listening for the keystrokes coming from #element.
  167. this.keystrokes.listenTo( this.element );
  168. }
  169. /**
  170. * Focuses the fist {@link #_focusables} in the form.
  171. */
  172. focus() {
  173. this._focusCycler.focusFirst();
  174. }
  175. /**
  176. * Creates a labeled input view.
  177. *
  178. * @private
  179. * @param {String} [protocol=http://] A value of a protocol to be displayed in the input's placeholder.
  180. * @returns {module:ui/labeledfield/labeledfieldview~LabeledFieldView} Labeled field view instance.
  181. */
  182. _createUrlInput( protocol = 'https://' ) {
  183. const t = this.locale.t;
  184. const labeledInput = new LabeledFieldView( this.locale, createLabeledInputText );
  185. labeledInput.label = t( 'Link URL' );
  186. labeledInput.fieldView.placeholder = protocol + 'example.com';
  187. return labeledInput;
  188. }
  189. /**
  190. * Creates a button view.
  191. *
  192. * @private
  193. * @param {String} label The button label.
  194. * @param {String} icon The button icon.
  195. * @param {String} className The additional button CSS class name.
  196. * @param {String} [eventName] An event name that the `ButtonView#execute` event will be delegated to.
  197. * @returns {module:ui/button/buttonview~ButtonView} The button view instance.
  198. */
  199. _createButton( label, icon, className, eventName ) {
  200. const button = new ButtonView( this.locale );
  201. button.set( {
  202. label,
  203. icon,
  204. tooltip: true
  205. } );
  206. button.extendTemplate( {
  207. attributes: {
  208. class: className
  209. }
  210. } );
  211. if ( eventName ) {
  212. button.delegate( 'execute' ).to( this, eventName );
  213. }
  214. return button;
  215. }
  216. /**
  217. * Populates {@link module:ui/viewcollection~ViewCollection} of {@link module:ui/button/switchbuttonview~SwitchButtonView}
  218. * made based on {@link module:link/linkcommand~LinkCommand#manualDecorators}.
  219. *
  220. * @private
  221. * @param {module:link/linkcommand~LinkCommand} linkCommand A reference to the link command.
  222. * @returns {module:ui/viewcollection~ViewCollection} of switch buttons.
  223. */
  224. _createManualDecoratorSwitches( linkCommand ) {
  225. const switches = this.createCollection();
  226. for ( const manualDecorator of linkCommand.manualDecorators ) {
  227. const switchButton = new SwitchButtonView( this.locale );
  228. switchButton.set( {
  229. name: manualDecorator.id,
  230. label: manualDecorator.label,
  231. withText: true
  232. } );
  233. switchButton.bind( 'isOn' ).toMany( [ manualDecorator, linkCommand ], 'value', ( decoratorValue, commandValue ) => {
  234. return commandValue === undefined && decoratorValue === undefined ? manualDecorator.defaultValue : decoratorValue;
  235. } );
  236. switchButton.on( 'execute', () => {
  237. manualDecorator.set( 'value', !switchButton.isOn );
  238. } );
  239. switches.add( switchButton );
  240. }
  241. return switches;
  242. }
  243. /**
  244. * Populates the {@link #children} collection of the form.
  245. *
  246. * If {@link module:link/linkcommand~LinkCommand#manualDecorators manual decorators} are configured in the editor, it creates an
  247. * additional `View` wrapping all {@link #_manualDecoratorSwitches} switch buttons corresponding
  248. * to these decorators.
  249. *
  250. * @private
  251. * @param {module:utils/collection~Collection} manualDecorators A reference to
  252. * the collection of manual decorators stored in the link command.
  253. * @returns {module:ui/viewcollection~ViewCollection} The children of link form view.
  254. */
  255. _createFormChildren( manualDecorators ) {
  256. const children = this.createCollection();
  257. children.add( this.urlInputView );
  258. if ( manualDecorators.length ) {
  259. const additionalButtonsView = new View();
  260. additionalButtonsView.setTemplate( {
  261. tag: 'ul',
  262. children: this._manualDecoratorSwitches.map( switchButton => ( {
  263. tag: 'li',
  264. children: [ switchButton ],
  265. attributes: {
  266. class: [
  267. 'ck',
  268. 'ck-list__item'
  269. ]
  270. }
  271. } ) ),
  272. attributes: {
  273. class: [
  274. 'ck',
  275. 'ck-reset',
  276. 'ck-list'
  277. ]
  278. }
  279. } );
  280. children.add( additionalButtonsView );
  281. }
  282. children.add( this.saveButtonView );
  283. children.add( this.cancelButtonView );
  284. return children;
  285. }
  286. }
  287. /**
  288. * Fired when the form view is submitted (when one of the children triggered the submit event),
  289. * for example with a click on {@link #saveButtonView}.
  290. *
  291. * @event submit
  292. */
  293. /**
  294. * Fired when the form view is canceled, for example with a click on {@link #cancelButtonView}.
  295. *
  296. * @event cancel
  297. */