3 * A Backbone View that provides the aural view of CKEditor toolbar
7 (function (Drupal, Backbone, $) {
8 Drupal.ckeditor.AuralView = Backbone.View.extend(/** @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);
53 else if ($item.parent().hasClass('ckeditor-button')) {
54 this.announceButtonPosition($item.parent());
61 * Handles the focus event of elements in the active and available toolbars.
63 * @param {jQuery.Event} event
64 * The focus event that was triggered.
67 event.stopPropagation();
69 const $originalTarget = $(event.target);
70 const $currentTarget = $(event.currentTarget);
71 const $parent = $currentTarget.parent();
72 if ($parent.hasClass('ckeditor-button') || $parent.hasClass('ckeditor-button-separator')) {
73 this.announceButtonPosition($currentTarget.parent());
75 else if ($originalTarget.attr('role') !== 'button' && $currentTarget.hasClass('ckeditor-toolbar-group')) {
76 this.announceButtonGroupPosition($currentTarget);
81 * Announces the current position of a button group.
83 * @param {jQuery} $group
84 * A jQuery set that contains an li element that wraps a group of buttons.
86 announceButtonGroupPosition($group) {
87 const $groups = $group.parent().children();
88 const $row = $group.closest('.ckeditor-row');
89 const $rows = $row.parent().children();
90 const position = $groups.index($group) + 1;
91 const positionCount = $groups.not('.placeholder').length;
92 const row = $rows.index($row) + 1;
93 const rowCount = $rows.not('.placeholder').length;
94 let text = Drupal.t('@groupName button group in position @position of @positionCount in row @row of @rowCount.', {
95 '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
96 '@position': position,
97 '@positionCount': positionCount,
99 '@rowCount': rowCount,
101 // If this position is the first in the last row then tell the user that
102 // pressing the down arrow key will create a new row.
103 if (position === 1 && row === rowCount) {
105 text += Drupal.t('Press the down arrow key to create a new row.');
107 Drupal.announce(text, 'assertive');
111 * Announces current button position.
113 * @param {jQuery} $button
114 * A jQuery set that contains an li element that wraps a button.
116 announceButtonPosition($button) {
117 const $row = $button.closest('.ckeditor-row');
118 const $rows = $row.parent().children();
119 const $buttons = $button.closest('.ckeditor-buttons').children();
120 const $group = $button.closest('.ckeditor-toolbar-group');
121 const $groups = $group.parent().children();
122 const groupPosition = $groups.index($group) + 1;
123 const groupPositionCount = $groups.not('.placeholder').length;
124 const position = $buttons.index($button) + 1;
125 const positionCount = $buttons.length;
126 const row = $rows.index($row) + 1;
127 const rowCount = $rows.not('.placeholder').length;
128 // The name of the button separator is 'button separator' and its type
129 // is 'separator', so we do not want to print the type of this item,
130 // otherwise the UA will speak 'button separator separator'.
131 const type = ($button.attr('data-drupal-ckeditor-type') === 'separator') ? '' : Drupal.t('button');
133 // The button is located in the available button set.
134 if ($button.closest('.ckeditor-toolbar-disabled').length > 0) {
135 text = Drupal.t('@name @type.', {
136 '@name': $button.children().attr('aria-label'),
139 text += `\n${Drupal.t('Press the down arrow key to activate.')}`;
141 Drupal.announce(text, 'assertive');
143 // The button is in the active toolbar.
144 else if ($group.not('.placeholder').length === 1) {
145 text = Drupal.t('@name @type in position @position of @positionCount in @groupName button group in row @row of @rowCount.', {
146 '@name': $button.children().attr('aria-label'),
148 '@position': position,
149 '@positionCount': positionCount,
150 '@groupName': $group.attr('data-drupal-ckeditor-toolbar-group-name'),
152 '@rowCount': rowCount,
154 // If this position is the first in the last row then tell the user that
155 // pressing the down arrow key will create a new row.
156 if (groupPosition === 1 && position === 1 && row === rowCount) {
158 text += Drupal.t('Press the down arrow key to create a new button group in a new row.');
160 // If this position is the last one in this row then tell the user that
161 // moving the button to the next group will create a new group.
162 if (groupPosition === groupPositionCount && position === positionCount) {
164 text += Drupal.t('This is the last group. Move the button forward to create a new group.');
166 Drupal.announce(text, 'assertive');
171 * Provides help information when a button is clicked.
173 * @param {jQuery.Event} event
174 * The click event for the button click.
176 announceButtonHelp(event) {
177 const $link = $(event.currentTarget);
178 const $button = $link.parent();
179 const enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
183 message = Drupal.t('The "@name" button is currently enabled.', {
184 '@name': $link.attr('aria-label'),
186 message += `\n${Drupal.t('Use the keyboard arrow keys to change the position of this button.')}`;
187 message += `\n${Drupal.t('Press the up arrow key on the top row to disable the button.')}`;
190 message = Drupal.t('The "@name" button is currently disabled.', {
191 '@name': $link.attr('aria-label'),
193 message += `\n${Drupal.t('Use the down arrow key to move this button into the active toolbar.')}`;
195 Drupal.announce(message);
196 event.preventDefault();
200 * Provides help information when a separator is clicked.
202 * @param {jQuery.Event} event
203 * The click event for the separator click.
205 announceSeparatorHelp(event) {
206 const $link = $(event.currentTarget);
207 const $button = $link.parent();
208 const enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
212 message = Drupal.t('This @name is currently enabled.', {
213 '@name': $link.attr('aria-label'),
215 message += `\n${Drupal.t('Use the keyboard arrow keys to change the position of this separator.')}`;
218 message = Drupal.t('Separators are used to visually split individual buttons.');
219 message += `\n${Drupal.t('This @name is currently disabled.', {
220 '@name': $link.attr('aria-label'),
222 message += `\n${Drupal.t('Use the down arrow key to move this separator into the active toolbar.')}`;
223 message += `\n${Drupal.t('You may add multiple separators to each button group.')}`;
225 Drupal.announce(message);
226 event.preventDefault();
229 }(Drupal, Backbone, jQuery));