AuralView.es6.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /**
  2. * @file
  3. * A Backbone View that provides the aural view of CKEditor toolbar
  4. * configuration.
  5. */
  6. (function(Drupal, Backbone, $) {
  7. Drupal.ckeditor.AuralView = Backbone.View.extend(
  8. /** @lends Drupal.ckeditor.AuralView# */ {
  9. /**
  10. * @type {object}
  11. */
  12. events: {
  13. 'click .ckeditor-buttons a': 'announceButtonHelp',
  14. 'click .ckeditor-multiple-buttons a': 'announceSeparatorHelp',
  15. 'focus .ckeditor-button a': 'onFocus',
  16. 'focus .ckeditor-button-separator a': 'onFocus',
  17. 'focus .ckeditor-toolbar-group': 'onFocus',
  18. },
  19. /**
  20. * Backbone View for CKEditor toolbar configuration; aural UX (output only).
  21. *
  22. * @constructs
  23. *
  24. * @augments Backbone.View
  25. */
  26. initialize() {
  27. // Announce the button and group positions when the model is no longer
  28. // dirty.
  29. this.listenTo(this.model, 'change:isDirty', this.announceMove);
  30. },
  31. /**
  32. * Calls announce on buttons and groups when their position is changed.
  33. *
  34. * @param {Drupal.ckeditor.ConfigurationModel} model
  35. * The ckeditor configuration model.
  36. * @param {bool} isDirty
  37. * A model attribute that indicates if the changed toolbar configuration
  38. * has been stored or not.
  39. */
  40. announceMove(model, isDirty) {
  41. // Announce the position of a button or group after the model has been
  42. // updated.
  43. if (!isDirty) {
  44. const item = document.activeElement || null;
  45. if (item) {
  46. const $item = $(item);
  47. if ($item.hasClass('ckeditor-toolbar-group')) {
  48. this.announceButtonGroupPosition($item);
  49. } else if ($item.parent().hasClass('ckeditor-button')) {
  50. this.announceButtonPosition($item.parent());
  51. }
  52. }
  53. }
  54. },
  55. /**
  56. * Handles the focus event of elements in the active and available toolbars.
  57. *
  58. * @param {jQuery.Event} event
  59. * The focus event that was triggered.
  60. */
  61. onFocus(event) {
  62. event.stopPropagation();
  63. const $originalTarget = $(event.target);
  64. const $currentTarget = $(event.currentTarget);
  65. const $parent = $currentTarget.parent();
  66. if (
  67. $parent.hasClass('ckeditor-button') ||
  68. $parent.hasClass('ckeditor-button-separator')
  69. ) {
  70. this.announceButtonPosition($currentTarget.parent());
  71. } else if (
  72. $originalTarget.attr('role') !== 'button' &&
  73. $currentTarget.hasClass('ckeditor-toolbar-group')
  74. ) {
  75. this.announceButtonGroupPosition($currentTarget);
  76. }
  77. },
  78. /**
  79. * Announces the current position of a button group.
  80. *
  81. * @param {jQuery} $group
  82. * A jQuery set that contains an li element that wraps a group of buttons.
  83. */
  84. announceButtonGroupPosition($group) {
  85. const $groups = $group.parent().children();
  86. const $row = $group.closest('.ckeditor-row');
  87. const $rows = $row.parent().children();
  88. const position = $groups.index($group) + 1;
  89. const positionCount = $groups.not('.placeholder').length;
  90. const row = $rows.index($row) + 1;
  91. const rowCount = $rows.not('.placeholder').length;
  92. let text = Drupal.t(
  93. '@groupName button group in position @position of @positionCount in row @row of @rowCount.',
  94. {
  95. '@groupName': $group.attr(
  96. 'data-drupal-ckeditor-toolbar-group-name',
  97. ),
  98. '@position': position,
  99. '@positionCount': positionCount,
  100. '@row': row,
  101. '@rowCount': rowCount,
  102. },
  103. );
  104. // If this position is the first in the last row then tell the user that
  105. // pressing the down arrow key will create a new row.
  106. if (position === 1 && row === rowCount) {
  107. text += '\n';
  108. text += Drupal.t('Press the down arrow key to create a new row.');
  109. }
  110. Drupal.announce(text, 'assertive');
  111. },
  112. /**
  113. * Announces current button position.
  114. *
  115. * @param {jQuery} $button
  116. * A jQuery set that contains an li element that wraps a button.
  117. */
  118. announceButtonPosition($button) {
  119. const $row = $button.closest('.ckeditor-row');
  120. const $rows = $row.parent().children();
  121. const $buttons = $button.closest('.ckeditor-buttons').children();
  122. const $group = $button.closest('.ckeditor-toolbar-group');
  123. const $groups = $group.parent().children();
  124. const groupPosition = $groups.index($group) + 1;
  125. const groupPositionCount = $groups.not('.placeholder').length;
  126. const position = $buttons.index($button) + 1;
  127. const positionCount = $buttons.length;
  128. const row = $rows.index($row) + 1;
  129. const rowCount = $rows.not('.placeholder').length;
  130. // The name of the button separator is 'button separator' and its type
  131. // is 'separator', so we do not want to print the type of this item,
  132. // otherwise the UA will speak 'button separator separator'.
  133. const type =
  134. $button.attr('data-drupal-ckeditor-type') === 'separator'
  135. ? ''
  136. : Drupal.t('button');
  137. let text;
  138. // The button is located in the available button set.
  139. if ($button.closest('.ckeditor-toolbar-disabled').length > 0) {
  140. text = Drupal.t('@name @type.', {
  141. '@name': $button.children().attr('aria-label'),
  142. '@type': type,
  143. });
  144. text += `\n${Drupal.t('Press the down arrow key to activate.')}`;
  145. Drupal.announce(text, 'assertive');
  146. }
  147. // The button is in the active toolbar.
  148. else if ($group.not('.placeholder').length === 1) {
  149. text = Drupal.t(
  150. '@name @type in position @position of @positionCount in @groupName button group in row @row of @rowCount.',
  151. {
  152. '@name': $button.children().attr('aria-label'),
  153. '@type': type,
  154. '@position': position,
  155. '@positionCount': positionCount,
  156. '@groupName': $group.attr(
  157. 'data-drupal-ckeditor-toolbar-group-name',
  158. ),
  159. '@row': row,
  160. '@rowCount': rowCount,
  161. },
  162. );
  163. // If this position is the first in the last row then tell the user that
  164. // pressing the down arrow key will create a new row.
  165. if (groupPosition === 1 && position === 1 && row === rowCount) {
  166. text += '\n';
  167. text += Drupal.t(
  168. 'Press the down arrow key to create a new button group in a new row.',
  169. );
  170. }
  171. // If this position is the last one in this row then tell the user that
  172. // moving the button to the next group will create a new group.
  173. if (
  174. groupPosition === groupPositionCount &&
  175. position === positionCount
  176. ) {
  177. text += '\n';
  178. text += Drupal.t(
  179. 'This is the last group. Move the button forward to create a new group.',
  180. );
  181. }
  182. Drupal.announce(text, 'assertive');
  183. }
  184. },
  185. /**
  186. * Provides help information when a button is clicked.
  187. *
  188. * @param {jQuery.Event} event
  189. * The click event for the button click.
  190. */
  191. announceButtonHelp(event) {
  192. const $link = $(event.currentTarget);
  193. const $button = $link.parent();
  194. const enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
  195. let message;
  196. if (enabled) {
  197. message = Drupal.t('The "@name" button is currently enabled.', {
  198. '@name': $link.attr('aria-label'),
  199. });
  200. message += `\n${Drupal.t(
  201. 'Use the keyboard arrow keys to change the position of this button.',
  202. )}`;
  203. message += `\n${Drupal.t(
  204. 'Press the up arrow key on the top row to disable the button.',
  205. )}`;
  206. } else {
  207. message = Drupal.t('The "@name" button is currently disabled.', {
  208. '@name': $link.attr('aria-label'),
  209. });
  210. message += `\n${Drupal.t(
  211. 'Use the down arrow key to move this button into the active toolbar.',
  212. )}`;
  213. }
  214. Drupal.announce(message);
  215. event.preventDefault();
  216. },
  217. /**
  218. * Provides help information when a separator is clicked.
  219. *
  220. * @param {jQuery.Event} event
  221. * The click event for the separator click.
  222. */
  223. announceSeparatorHelp(event) {
  224. const $link = $(event.currentTarget);
  225. const $button = $link.parent();
  226. const enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
  227. let message;
  228. if (enabled) {
  229. message = Drupal.t('This @name is currently enabled.', {
  230. '@name': $link.attr('aria-label'),
  231. });
  232. message += `\n${Drupal.t(
  233. 'Use the keyboard arrow keys to change the position of this separator.',
  234. )}`;
  235. } else {
  236. message = Drupal.t(
  237. 'Separators are used to visually split individual buttons.',
  238. );
  239. message += `\n${Drupal.t('This @name is currently disabled.', {
  240. '@name': $link.attr('aria-label'),
  241. })}`;
  242. message += `\n${Drupal.t(
  243. 'Use the down arrow key to move this separator into the active toolbar.',
  244. )}`;
  245. message += `\n${Drupal.t(
  246. 'You may add multiple separators to each button group.',
  247. )}`;
  248. }
  249. Drupal.announce(message);
  250. event.preventDefault();
  251. },
  252. },
  253. );
  254. })(Drupal, Backbone, jQuery);