1616
1717define ( 'A11Y_ADDON_VERSION ' , '1.1.0 ' );
1818
19+ /**
20+ * Load custom facet types
21+ */
22+ function load_facets ( $ facet_types ) {
23+ // Load custom facets
24+ include ( dirname ( __FILE__ ) . '/facets/Submit.php ' );
25+ $ facet_types ['submit ' ] = new FacetWP_Facet_Submit ();
26+
27+ // Remove facets that aren't yet accessible
28+ $ disabled_facets = [
29+ 'autocomplete ' , // Autocomplete dropdown is not focusable.
30+ 'slider ' , // Using min + max setting is not accessible. Needs further review.
31+ 'date_range ' , // Uses a date picker library that isn't accessible.
32+ 'hierarchy ' , // Not keyboard or screen reader accessible.
33+ 'rating ' , // Not keyboard or screen reader accessible.
34+ 'fselect ' , // Not keyboard or screen reader accessible.
35+ 'number_range ' , // Multiple inputs are not accessible.
36+ 'proximity ' , // Missing labels, among other things.
37+ ];
38+
39+ foreach ( $ disabled_facets as $ facet ) {
40+ if ( isset ( $ facet_types [ $ facet ] ) ) {
41+ unset( $ facet_types [ $ facet ] );
42+ }
43+ }
44+
45+ return $ facet_types ;
46+ }
47+ add_filter ( 'facetwp_facet_types ' , 'load_facets ' );
48+
1949/**
2050 * Run plugin update process on activation.
2151 */
@@ -39,45 +69,33 @@ function a11y_addon_update_check() {
3969}
4070add_action ( 'plugins_loaded ' , 'a11y_addon_update_check ' );
4171
42-
43- /**
44- * Enqueue jQuery because FacetWP just assumes it's enqueued.
45- */
46- function a11y_addon_facet_assets () {
47- wp_enqueue_script ('jquery ' );
48- }
49- add_action ( 'wp_enqueue_scripts ' , 'a11y_addon_facet_assets ' );
50-
51-
5272/**
5373 * Adjusts markup for specific facets so they use real input elements
5474 * Adds matching label for and ids to all facets
5575 *
5676 * @param string $output HTML
5777 * @param array $params FacetWP field parameters
58- *
78+ *
5979 * @return string Updated HTML output for a facet
60- *
80+ *
6181 * @todo consider whether a combination of totally custom output and str_replace make sense or whether doing something with the WP HTML API might make more sense in the long term
62- *
82+ *
6383 * most of this courtesy of Mark Root-Wiley
6484 * @link https://github.com/mrwweb/accessibility-addon-for-facetwp
6585 */
6686function a11y_addon_transform_facet_markup ( $ output , $ params ) {
67-
6887 $ facet_type = $ params ['facet ' ]['type ' ];
6988
70- switch ($ facet_type ) {
71-
89+ switch ( $ facet_type ) {
7290 case 'checkboxes ' :
73- // Note: The trick to this working was moving the facetwp-checkbox class and data-value attribute to the `input`. Clicking the label works because the input element still emits a click event when the label is clicked. Leaving that class and attribute on the wrapping list item resulted in two events being fired when the label was clicked.
74- $ output = sprintf ('<fieldset><legend>%1$s</legend> ' ,
75- $ params ['facet ' ]['label ' ] );
91+ // Note: The trick to this working was moving the facetwp-checkbox class and data-value attribute to the `input`.
92+ // Clicking the label works because the input element still emits a click event when the label is clicked.
93+ // Leaving that class and attribute on the wrapping list item resulted in two events being fired when the label was clicked.
94+ $ output = '' ;
7695 foreach ( $ params ['values ' ] as $ value ) {
77- if ( $ value ['counter ' ] > 0 || ! $ params ['facet ' ]['preserve_ghosts ' ] === 'no ' ) {
78- $ output .= sprintf (
79- '<div>
80- <input type="checkbox" id="%3$s"%1$s value="%2$s" class="facetwp-checkbox%1$s" data-value="%2$s">
96+ $ checkbox = sprintf (
97+ '<div class="facetwp-checkbox-wrapper %6$s">
98+ <input type="checkbox" id="%3$s"%1$s value="%2$s" class="facetwp-checkbox%1$s" %6$s data-value="%2$s">
8199 <label for="%3$s">
82100 <span class="facetwp-display-value">%4$s</span>
83101 <span class="facetwp-counter">(%5$d)</span>
@@ -87,75 +105,59 @@ function a11y_addon_transform_facet_markup( $output, $params ) {
87105 esc_attr ( $ value ['facet_value ' ] ),
88106 'checkbox- ' . esc_attr ( $ value ['term_id ' ] ),
89107 esc_html ( $ value ['facet_display_value ' ] ),
90- $ value ['counter ' ]
108+ $ value ['counter ' ],
109+ $ value ['counter ' ] == 0 ? 'disabled ' : ''
91110 );
92- }
111+
112+ $ output .= $ checkbox ;
93113 }
94- $ output .= '</fieldset> ' ;
95114 break ;
96115
97116 case 'search ' :
98- // use search landmark and insert a real button
99- $ output = '<search> ' . $ output . '</search> ' ;
100117 // remove the fake button
101118 $ output = str_replace ( '<i class="facetwp-icon"></i> ' , '' , $ output );
119+
102120 // add label to search input
103121 $ id = $ params ['facet ' ]['name ' ];
104- $ output = str_replace (
105- '<input ' , '<div class="trec-facetwp-search-wrapper"><input id=" ' . esc_attr ( $ id ) . '" ' , $ output );
106-
122+ $ output = str_replace ( '<input ' , '<div class="trec-facetwp-search-wrapper"><input id=" ' . esc_attr ( $ id ) . '" ' , $ output );
123+
107124 // placeholders are bad for UX
108125 $ output = str_replace ( 'placeholder="Enter keywords" ' , '' , $ output );
109126 break ;
110127
111128 case 'dropdown ' :
112-
113129 $ output = str_replace ( 'facetwp-dropdown ' , 'facetwp-dropdown a11y-addon-filter ' , $ output );
114130
115- $ id_string = 'id=" ' . $ params ['facet ' ]['name ' ]. '" class= ' ;
131+ $ id_string = 'id=" ' . $ params ['facet ' ]['name ' ] . '" class= ' ;
116132
117133 $ output = str_replace ('class= ' , $ id_string , $ output );
118-
119- break ;
120-
121- case 'radio ' :
122- $ output = sprintf ('<fieldset><legend>%1$s</legend> ' ,
123- $ params ['facet ' ]['label ' ] );
124- foreach ( $ params ['values ' ] as $ value ) {
125- $ output .= sprintf (
126- '<div><input type="radio" id="%1$s" name="%3$s" value="%2$s">
127- <label for="%1$s">%2$s</label></div> ' ,
128- esc_attr ( 'radio- ' .$ value ['facet_value ' ] ),
129- esc_html ( $ value ['facet_display_value ' ] ),
130- esc_attr ( $ params ['facet ' ]['name ' ] )
131- );
132- }
133-
134- $ output .= '</fieldset> ' ;
135134 break ;
136135
137136 case 'reset ' :
138-
139- $ output = str_replace ('button ' ,'button type="reset" ' ,$ output );
140-
141- break ;
142-
143- case 'pager ' :
144-
145- $ output = sprintf ('
146- <nav class="navigation pagination" aria-label="Pagination"><h2 class="screen-reader-text">Pagination</h2>%1$s</nav> ' ,
147- $ output );
148-
137+ $ output = str_replace ('<button ' , '<button type="reset" ' , $ output );
149138 break ;
150139
151140 default :
152141
153142 $ id_string = 'id=" ' .$ params ['facet ' ]['name ' ].'" class= ' ;
154143
155144 $ output = str_replace ('class= ' , $ id_string , $ output );
145+ break ;
146+
147+ case 'pager ' :
148+ // Use nav element for pager
149+ $ output = str_replace ( '<div ' , '<nav aria-label=" ' . esc_html__ ( 'Pagination ' , 'aawp ' ) . '" ' , $ output );
150+ $ output = str_replace ( '</div> ' , '</nav> ' , $ output );
156151
152+ // Add role="presentation" to dots & active item so screen readers don't read them as links
153+ $ output = str_replace ( 'facetwp-page dots" ' , 'facetwp-page dots" role="presentation" ' , $ output );
154+ $ output = str_replace ( 'facetwp-page active" ' , 'facetwp-page active" role="presentation" ' , $ output );
155+
156+ // Change page links to buttons
157+ $ output = str_replace ( 'a ' , 'button ' , $ output );
158+ break ;
157159 }
158-
160+
159161 return $ output ;
160162}
161163
@@ -166,84 +168,94 @@ function a11y_addon_transform_facet_markup( $output, $params ) {
166168 * Programatically add labels above filters
167169 * @link https://facetwp.com/add-labels-above-each-facet/
168170*/
169-
170171function a11y_addon_add_facet_labels () {
171172 ?>
172173 <script>
173- (function($) {
174-
175- function remove_underscores( name ) {
176- return name.replace(/_/g, ' ');
177- }
178-
179- // Make enter & space work for links
180- $(document).on('keydown', '.facetwp-page, .facetwp-toggle, .facetwp-selection-value', function(e) {
181- var keyCode = e.originalEvent.keyCode;
182- if ( 32 == keyCode || 13 == keyCode ) {
183- e.preventDefault();
184- $(this).click();
185- }
186- });
187-
188- $(document).on('keydown', '.facetwp-checkbox, .facetwp-radio', function(e) {
189- var keyCode = e.originalEvent.keyCode;
190- if ( 32 == keyCode || 13 == keyCode ) {
191- var is_checked = $(this).hasClass('checked');
192-
193- if (!is_checked) {
194- $(this).attr('aria-checked', 'true');
195- } else {
196- $(this).attr('aria-checked', 'false');
197- }
198-
199- e.preventDefault();
200- $(this).click();
201- }
202- });
203-
204- $(document).on('facetwp-loaded', function() {
205-
206- // pager
207- $('.facetwp-pager').attr('role', 'navigation');
208-
174+ (function() {
175+ document.addEventListener('facetwp-loaded', function() {
209176 // Add labels
210- $('.facetwp-facet').each(function() {
211- var $facet = $(this);
212- var facet_name = $facet.attr('data-name');
213- var facet_type = $facet.attr('data-type');
214-
215- if ( facet_name && facet_type ) {
216- // Don't label some facets
217- if ( facet_type.match(/checkboxes|radio|pager|reset|results_count/g) ) {
177+ var facets = document.querySelectorAll('.facetwp-facet');
178+
179+ facets.forEach(function(facet) {
180+ var facet_name = facet.getAttribute('data-name');
181+ var facet_type = facet.getAttribute('data-type');
182+
183+ if (facet_name && facet_type) {
184+ // Exclude some facets from getting labels
185+ if (facet_name.match(/pagination/g) ||
186+ facet_name.match(/reset/g) ||
187+ facet_name.match(/submit/g) ||
188+ facet_name.match(/results_count/g)) {
218189 return;
219190 }
220191
221192 var facet_label = FWP.settings.labels[facet_name];
222193
223- if ($facet.closest('.facet-wrap').length < 1 && $facet.closest('.facetwp-flyout').length < 1) {
224-
225- //wrapper div
226- $facet.wrap(`<div class="facet-wrap facet-wrap-${facet_name}"></div>`);
227-
228- //label
229- $facet.before('<label class="facet-label" for="'+facet_name.replace(/\s/g, '')+'">' + facet_label + '</label>');
230-
194+ if (!facet.closest('.facet-wrap') && !facet.closest('.facetwp-flyout')) {
195+ if (facet_type.match(/checkboxes/g) || facet_type.match(/radio/g)) {
196+ // Checkboxes & radio buttons need a fieldset/legend created here using role group & arialabelledby
197+ var wrapDiv = document.createElement('div');
198+ wrapDiv.className = 'facet-wrap facet-wrap-' + facet_name;
199+ wrapDiv.setAttribute('role', 'group');
200+ wrapDiv.setAttribute('aria-labelledby', facet_name + '_label');
201+
202+ var labelDiv = document.createElement('div');
203+ labelDiv.id = facet_name + '_label';
204+ labelDiv.className = 'facet-label';
205+ labelDiv.textContent = facet_label;
206+
207+ facet.parentNode.insertBefore(wrapDiv, facet);
208+ wrapDiv.appendChild(labelDiv);
209+ wrapDiv.appendChild(facet);
210+ } else {
211+ var wrapDiv = document.createElement('div');
212+ wrapDiv.className = 'facet-wrap facet-wrap-' + facet_name;
213+
214+ var label = document.createElement('label');
215+ label.className = 'facet-label';
216+ label.setAttribute('for', facet_name.replace(/\s/g, '')); // remove spaces for id
217+ label.textContent = facet_label;
218+
219+ facet.parentNode.insertBefore(wrapDiv, facet);
220+ wrapDiv.appendChild(label);
221+ wrapDiv.appendChild(facet);
222+ }
231223 }
232224 }
233225 });
234226 });
235- })(jQuery );
227+ })();
236228 </script>
237229 <?php
238230}
239231
240- add_action ( 'wp_head ' , 'a11y_addon_add_facet_labels ' , 100 );
232+ add_action ( 'facetwp_scripts ' , 'a11y_addon_add_facet_labels ' , 100 );
233+
234+ /**
235+ * Submit form when enter key is pressed
236+ * @link https://facetwp.com/help-center/add-on-features-and-extras/submit-button#submit-on-enter
237+ */
238+ function a11y_addon_add_facet_submit_form () {
239+ ?>
240+ <script>
241+ (function() {
242+ document.addEventListener('facetwp-loaded', function() {
243+ document.addEventListener('keyup', function(event) {
244+ if (event.keyCode === 13) {
245+ FWP.refresh()
246+ }
247+ });
248+ });
249+ })();
250+ </script>
251+ <?php
252+ }
253+ add_action ( 'facetwp_scripts ' , 'a11y_addon_add_facet_submit_form ' , 100 );
241254
242255/**
243256 * Hide counts in all dropdowns
244257 * @link https://facetwp.com/help-center/facets/facet-types/dropdown/
245258*/
246-
247259function a11y_addon_hide_dropdown_counts ( $ return , $ params ) {
248260 return false ;
249261}
@@ -267,48 +279,3 @@ function a11y_addon_disable_auto_refresh() {
267279<?php
268280}
269281add_action ( 'facetwp_scripts ' , 'a11y_addon_disable_auto_refresh ' , 100 );
270-
271- // Customize icon for prev/next pagination links.
272- function a11y_addon_facetwp_facet_pager_link ($ html , $ params ) {
273- if ( 'next ' == $ params ['extra_class ' ] ) {
274- $ icon = 'Next <svg class="icon" aria-hidden="true"><use xlink:href="#caret-right"/></svg></svg> ' ;
275- $ html = str_replace ( 'Next ' , $ icon , $ html );
276- }
277-
278- if ( 'prev ' == $ params ['extra_class ' ] ) {
279- $ icon = '<svg class="icon" aria-hidden="true"><use xlink:href="#caret-left"/></svg></svg> Prev ' ;
280- $ html = str_replace ( 'Prev ' , $ icon , $ html );
281- }
282-
283- return $ html ;
284- }
285- add_action ( 'facetwp_facet_pager_link ' , 'a11y_addon_facetwp_facet_pager_link ' , 100 , 2 );
286-
287- /**
288- * Scroll back to top of results when pager is clicked
289- * @link https://facetwp.com/how-to-scroll-the-page-on-facet-interaction/#scroll-when-a-pager-facet-is-used
290- **/
291-
292- function a11y_addon_scroll_on_pager_interaction () {
293- ?>
294- <script>
295- (function($) {
296- $(document).on('facetwp-refresh', function() {
297- if ( FWP.soft_refresh == true ) {
298- FWP.enable_scroll = true;
299- } else {
300- FWP.enable_scroll = false;
301- }
302- });
303- $(document).on('facetwp-loaded', function() {
304- if (FWP.enable_scroll == true) {
305- $('html, body').animate({
306- scrollTop: $('.facetwp-template').offset().top
307- }, 500);
308- }
309- });
310- })(jQuery);
311- </script>
312- <?php
313- }
314- add_action ( 'facetwp_scripts ' , 'a11y_addon_scroll_on_pager_interaction ' );
0 commit comments