3 * A Backbone View that provides the aural view of CKEditor toolbar
7 (function(Drupal, Backbone, $) {
8 Drupal.ckeditor.AuralView = Backbone.View.extend(
9 /** @lends Drupal.ckeditor.AuralView# */ {
14 'click .ckeditor-buttons a': 'announceButtonHelp',
15 'click .ckeditor-multiple-buttons a': 'announceSeparatorHelp',
16 'focus .ckeditor-button a': 'onFocus',
17 'focus .ckeditor-button-separator a': 'onFocus',
18 'focus .ckeditor-toolbar-group': 'onFocus',
22 * Backbone View for CKEditor toolbar configuration; aural UX (output only).
26 * @augments Backbone.View
29 // Announce the button and group positions when the model is no longer
31 this.listenTo(this.model, 'change:isDirty', this.announceMove);
35 * Calls announce on buttons and groups when their position is changed.
37 * @param {Drupal.ckeditor.ConfigurationModel} model
38 * The ckeditor configuration model.
39 * @param {bool} isDirty
40 * A model attribute that indicates if the changed toolbar configuration
41 * has been stored or not.
43 announceMove(model, isDirty) {
44 // Announce the position of a button or group after the model has been
47 const item = document.activeElement || null;
49 const $item = $(item);
50 if ($item.hasClass('ckeditor-toolbar-group')) {
51 this.announceButtonGroupPosition($item);
52 } else if ($item.parent().hasClass('ckeditor-button')) {
53 this.announceButtonPosition($item.parent());
60 * Handles the focus event of elements in the active and available toolbars.
62 * @param {jQuery.Event} event
63 * The focus event that was triggered.
66 event.stopPropagation();
68 const $originalTarget = $(event.target);
69 const $currentTarget = $(event.currentTarget);
70 const $parent = $currentTarget.parent();
72 $parent.hasClass('ckeditor-button') ||
73 $parent.hasClass('ckeditor-button-separator')
75 this.announceButtonPosition($currentTarget.parent());
77 $originalTarget.attr('role') !== 'button' &&
78 $currentTarget.hasClass('ckeditor-toolbar-group')
80 this.announceButtonGroupPosition($currentTarget);
85 * Announces the current position of a button group.
87 * @param {jQuery} $group
88 * A jQuery set that contains an li element that wraps a group of buttons.
90 announceButtonGroupPosition($group) {
91 const $groups = $group.parent().children();
92 const $row = $group.closest('.ckeditor-row');
93 const $rows = $row.parent().children();
94 const position = $groups.index($group) + 1;
95 const positionCount = $groups.not('.placeholder').length;
96 const row = $rows.index($row) + 1;
97 const rowCount = $rows.not('.placeholder').length;
99 '@groupName button group in position @position of @positionCount in row @row of @rowCount.',
101 '@groupName': $group.attr(
102 'data-drupal-ckeditor-toolbar-group-name',
104 '@position': position,
105 '@positionCount': positionCount,
107 '@rowCount': rowCount,
110 // If this position is the first in the last row then tell the user that
111 // pressing the down arrow key will create a new row.
112 if (position === 1 && row === rowCount) {
114 text += Drupal.t('Press the down arrow key to create a new row.');
116 Drupal.announce(text, 'assertive');
120 * Announces current button position.
122 * @param {jQuery} $button
123 * A jQuery set that contains an li element that wraps a button.
125 announceButtonPosition($button) {
126 const $row = $button.closest('.ckeditor-row');
127 const $rows = $row.parent().children();
128 const $buttons = $button.closest('.ckeditor-buttons').children();
129 const $group = $button.closest('.ckeditor-toolbar-group');
130 const $groups = $group.parent().children();
131 const groupPosition = $groups.index($group) + 1;
132 const groupPositionCount = $groups.not('.placeholder').length;
133 const position = $buttons.index($button) + 1;
134 const positionCount = $buttons.length;
135 const row = $rows.index($row) + 1;
136 const rowCount = $rows.not('.placeholder').length;
137 // The name of the button separator is 'button separator' and its type
138 // is 'separator', so we do not want to print the type of this item,
139 // otherwise the UA will speak 'button separator separator'.
141 $button.attr('data-drupal-ckeditor-type') === 'separator'
143 : Drupal.t('button');
145 // The button is located in the available button set.
146 if ($button.closest('.ckeditor-toolbar-disabled').length > 0) {
147 text = Drupal.t('@name @type.', {
148 '@name': $button.children().attr('aria-label'),
151 text += `\n${Drupal.t('Press the down arrow key to activate.')}`;
153 Drupal.announce(text, 'assertive');
155 // The button is in the active toolbar.
156 else if ($group.not('.placeholder').length === 1) {
158 '@name @type in position @position of @positionCount in @groupName button group in row @row of @rowCount.',
160 '@name': $button.children().attr('aria-label'),
162 '@position': position,
163 '@positionCount': positionCount,
164 '@groupName': $group.attr(
165 'data-drupal-ckeditor-toolbar-group-name',
168 '@rowCount': rowCount,
171 // If this position is the first in the last row then tell the user that
172 // pressing the down arrow key will create a new row.
173 if (groupPosition === 1 && position === 1 && row === rowCount) {
176 'Press the down arrow key to create a new button group in a new row.',
179 // If this position is the last one in this row then tell the user that
180 // moving the button to the next group will create a new group.
182 groupPosition === groupPositionCount &&
183 position === positionCount
187 'This is the last group. Move the button forward to create a new group.',
190 Drupal.announce(text, 'assertive');
195 * Provides help information when a button is clicked.
197 * @param {jQuery.Event} event
198 * The click event for the button click.
200 announceButtonHelp(event) {
201 const $link = $(event.currentTarget);
202 const $button = $link.parent();
203 const enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
207 message = Drupal.t('The "@name" button is currently enabled.', {
208 '@name': $link.attr('aria-label'),
210 message += `\n${Drupal.t(
211 'Use the keyboard arrow keys to change the position of this button.',
213 message += `\n${Drupal.t(
214 'Press the up arrow key on the top row to disable the button.',
217 message = Drupal.t('The "@name" button is currently disabled.', {
218 '@name': $link.attr('aria-label'),
220 message += `\n${Drupal.t(
221 'Use the down arrow key to move this button into the active toolbar.',
224 Drupal.announce(message);
225 event.preventDefault();
229 * Provides help information when a separator is clicked.
231 * @param {jQuery.Event} event
232 * The click event for the separator click.
234 announceSeparatorHelp(event) {
235 const $link = $(event.currentTarget);
236 const $button = $link.parent();
237 const enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
241 message = Drupal.t('This @name is currently enabled.', {
242 '@name': $link.attr('aria-label'),
244 message += `\n${Drupal.t(
245 'Use the keyboard arrow keys to change the position of this separator.',
249 'Separators are used to visually split individual buttons.',
251 message += `\n${Drupal.t('This @name is currently disabled.', {
252 '@name': $link.attr('aria-label'),
254 message += `\n${Drupal.t(
255 'Use the down arrow key to move this separator into the active toolbar.',
257 message += `\n${Drupal.t(
258 'You may add multiple separators to each button group.',
261 Drupal.announce(message);
262 event.preventDefault();
266 })(Drupal, Backbone, jQuery);