8
0

dropdownview.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module ui/dropdown/dropdownview
  7. */
  8. import View from '../view';
  9. import IconView from '../icon/iconview';
  10. import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  11. import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
  12. import dropdownArrowIcon from '../../theme/icons/dropdown-arrow.svg';
  13. import '../../theme/components/dropdown/dropdown.css';
  14. /**
  15. * The dropdown view class.
  16. *
  17. * const button = new ButtonView( locale );
  18. * const panel = new DropdownPanelView( locale );
  19. * const dropdown = new DropdownView( locale, button, panel );
  20. *
  21. * panel.element.textContent = 'Content of the panel';
  22. * button.set( {
  23. * label: 'A dropdown',
  24. * withText: true
  25. * } );
  26. *
  27. * dropdown.render();
  28. *
  29. * // Will render a dropdown with a panel containing a "Content of the panel" text.
  30. * document.body.appendChild( dropdown.element );
  31. *
  32. * Also see {@link module:ui/dropdown/utils~createDropdown} and {@link module:ui/dropdown/utils~createSplitButtonDropdown}
  33. * to learn about different dropdown creation helpers.
  34. *
  35. * @extends module:ui/view~View
  36. */
  37. export default class DropdownView extends View {
  38. /**
  39. * @inheritDoc
  40. */
  41. constructor( locale, buttonView, panelView ) {
  42. super( locale );
  43. const bind = this.bindTemplate;
  44. /**
  45. * Button of the dropdown view. Clicking the button opens the {@link #panelView}.
  46. *
  47. * @readonly
  48. * @member {module:ui/button/buttonview~ButtonView} #buttonView
  49. */
  50. this.buttonView = buttonView;
  51. /**
  52. * Panel of the dropdown. It opens when the {@link #buttonView} is
  53. * {@link module:ui/button/buttonview~ButtonView#event:execute executed} (i.e. clicked).
  54. *
  55. * Child views can be added to the panel's `children` collection:
  56. *
  57. * dropdown.panelView.children.add( childView );
  58. *
  59. * See {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView#children} and
  60. * {@link module:ui/viewcollection~ViewCollection#add}.
  61. *
  62. * @readonly
  63. * @member {module:ui/dropdown/dropdownpanelview~DropdownPanelView} #panelView
  64. */
  65. this.panelView = panelView;
  66. /**
  67. * Controls whether the dropdown view is open, i.e. shows or hides the {@link #panelView panel}.
  68. *
  69. * @observable
  70. * @member {Boolean} #isOpen
  71. */
  72. this.set( 'isOpen', false );
  73. /**
  74. * Controls whether the dropdown is enabled, i.e. it can be clicked and execute an action.
  75. *
  76. * See {@link module:ui/button/buttonview~ButtonView#isEnabled}.
  77. *
  78. * @observable
  79. * @member {Boolean} #isEnabled
  80. */
  81. this.set( 'isEnabled', true );
  82. /**
  83. * Tracks information about DOM focus in the dropdown.
  84. *
  85. * @readonly
  86. * @member {module:utils/focustracker~FocusTracker}
  87. */
  88. this.focusTracker = new FocusTracker();
  89. /**
  90. * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. It manages
  91. * keystrokes of the dropdown:
  92. *
  93. * * <kbd>▼</kbd> opens the dropdown,
  94. * * <kbd>◀</kbd> and <kbd>Esc</kbd> closes the dropdown.
  95. *
  96. * @readonly
  97. * @member {module:utils/keystrokehandler~KeystrokeHandler}
  98. */
  99. this.keystrokes = new KeystrokeHandler();
  100. /**
  101. * The arrow icon of the dropdown.
  102. *
  103. * @readonly
  104. * @member {module:ui/icon/iconview~IconView} #arrowView
  105. */
  106. const arrowView = this.arrowView = new IconView();
  107. this.setTemplate( {
  108. tag: 'div',
  109. attributes: {
  110. class: [
  111. 'ck-dropdown',
  112. bind.to( 'isEnabled', isEnabled => isEnabled ? '' : 'ck-disabled' )
  113. ]
  114. },
  115. children: [
  116. buttonView,
  117. arrowView,
  118. panelView
  119. ]
  120. } );
  121. arrowView.content = dropdownArrowIcon;
  122. arrowView.extendTemplate( {
  123. attributes: {
  124. class: 'ck-dropdown__arrow'
  125. }
  126. } );
  127. buttonView.extendTemplate( {
  128. attributes: {
  129. class: [
  130. 'ck-dropdown__button',
  131. ]
  132. }
  133. } );
  134. /**
  135. * The label of the dropdown.
  136. *
  137. * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or
  138. * {@link module:ui/dropdown/utils~createSplitButtonDropdown}.
  139. *
  140. * Also see {@link module:ui/button/buttonview~ButtonView#label}.
  141. *
  142. * @observable
  143. * @member {String} #label
  144. */
  145. /**
  146. * Controls whether the dropdown is enabled, i.e. it opens the panel when clicked.
  147. *
  148. * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or
  149. * {@link module:ui/dropdown/utils~createSplitButtonDropdown}.
  150. *
  151. * Also see {@link module:ui/button/buttonview~ButtonView#isEnabled}.
  152. *
  153. * @observable
  154. * @member {Boolean} #isEnabled
  155. */
  156. /**
  157. * Controls whether the dropdown is "on". It makes sense when a feature it represents
  158. * is currently active.
  159. *
  160. * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or
  161. * {@link module:ui/dropdown/utils~createSplitButtonDropdown}.
  162. *
  163. * Also see {@link module:ui/button/buttonview~ButtonView#isOn}.
  164. *
  165. * @observable
  166. * @member {Boolean} #isOn
  167. */
  168. /**
  169. * (Optional) Controls whether the label of the dropdown is visible.
  170. *
  171. * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or
  172. * {@link module:ui/dropdown/utils~createSplitButtonDropdown}.
  173. *
  174. * Also see {@link module:ui/button/buttonview~ButtonView#withText}.
  175. *
  176. * @observable
  177. * @member {Boolean} #withText
  178. */
  179. /**
  180. * (Optional) Controls the icon of the dropdown.
  181. *
  182. * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or
  183. * {@link module:ui/dropdown/utils~createSplitButtonDropdown}.
  184. *
  185. * Also see {@link module:ui/button/buttonview~ButtonView#withText}.
  186. *
  187. * @observable
  188. * @member {Boolean} #icon
  189. */
  190. /**
  191. * Controls dropdown's toolbar direction.
  192. * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addToolbarToDropdown}.
  193. *
  194. * @observable
  195. * @member {Boolean} #isVertical=false
  196. */
  197. /**
  198. * A child {@link module:ui/list/listview~ListView list view} of the dropdown located
  199. * in its {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}.
  200. *
  201. * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addListToDropdown}.
  202. *
  203. * @readonly
  204. * @member {module:ui/list/listview~ListView} #listView
  205. */
  206. /**
  207. * A child toolbar of the dropdown located in the
  208. * {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}.
  209. *
  210. * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addToolbarToDropdown}.
  211. *
  212. * @readonly
  213. * @member {module:ui/toolbar/toolbarview~ToolbarView} #toolbarView
  214. */
  215. /**
  216. * Fired when the toolbar button or list item is executed.
  217. *
  218. * For {@link #listView} It fires when one of the list items has been
  219. * {@link module:ui/list/listitemview~ListItemView#event:execute executed}.
  220. *
  221. * For {@link #toolbarView} It fires when one of the buttons has been
  222. * {@link module:ui/button/buttonview~ButtonView#event:execute executed}.
  223. *
  224. * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addListToDropdown}
  225. * or {@link module:ui/dropdown/utils~addToolbarToDropdown}.
  226. *
  227. * @event #execute
  228. */
  229. }
  230. /**
  231. * @inheritDoc
  232. */
  233. render() {
  234. super.render();
  235. // Toggle the the dropdown when it's button has been clicked.
  236. this.listenTo( this.buttonView, 'select', () => {
  237. this.isOpen = !this.isOpen;
  238. } );
  239. // Toggle the visibility of the panel when the dropdown becomes open.
  240. this.panelView.bind( 'isVisible' ).to( this, 'isOpen' );
  241. // Listen for keystrokes coming from within #element.
  242. this.keystrokes.listenTo( this.element );
  243. // Register #element in the focus tracker.
  244. this.focusTracker.add( this.element );
  245. const closeDropdown = ( data, cancel ) => {
  246. if ( this.isOpen ) {
  247. this.buttonView.focus();
  248. this.isOpen = false;
  249. cancel();
  250. }
  251. };
  252. // Open the dropdown panel using the arrow down key, just like with return or space.
  253. this.keystrokes.set( 'arrowdown', ( data, cancel ) => {
  254. // Don't open if the dropdown is disabled or already open.
  255. if ( this.buttonView.isEnabled && !this.isOpen ) {
  256. this.isOpen = true;
  257. cancel();
  258. }
  259. } );
  260. // Block the right arrow key (until nested dropdowns are implemented).
  261. this.keystrokes.set( 'arrowright', ( data, cancel ) => {
  262. if ( this.isOpen ) {
  263. cancel();
  264. }
  265. } );
  266. // Close the dropdown using the arrow left/escape key.
  267. this.keystrokes.set( 'arrowleft', closeDropdown );
  268. this.keystrokes.set( 'esc', closeDropdown );
  269. }
  270. /**
  271. * Focuses the {@link #buttonView}.
  272. */
  273. focus() {
  274. this.buttonView.focus();
  275. }
  276. }