/**
* Fastening Specialists — Accessibility fix C1
* Sitebulb / axe-core: "Form elements must have labels" (866 pages)
*
* CAUSE
* The product tables on category pages are built by the "WooCommerce Product
* Table" plugin (Barn2) and loaded via AJAX (DataTables). The controls it
* generates have no usable label, so axe-core flags them on every category
* page that has a table:
* - the "Select all" / per-row bulk-select checkboxes (class wpt-bulk-select)
* - the per-row quantity fields in the Buy column
* - the table's search box / "show entries" dropdown
* The Barn2 markup even prints a but never connects it to the checkbox (no for=/id=), so it
* doesn't count as a label.
*
* Because the rows are built by JavaScript after page load, this must be
* fixed in JavaScript — a PHP template/filter override will not catch the
* AJAX-rendered rows. This script adds the missing accessible names after the
* table loads and after every redraw (search, sort, pagination).
*
* DEPLOY — pick ONE:
* A) Elementor Pro -> Custom Code -> Add New. Paste everything below into
* the code box, set Location to "End of page / before body close".
* B) Child theme (most reliable): save this file as
* wp-content/themes/hello-theme-child-master/js/c1-form-input-labels-fix.js
* and enqueue it from functions.php (PHP snippet at the bottom of this
* file). An external .js file avoids the inline-script pitfall entirely.
*
* Safe to leave in place permanently — it only touches controls that are
* still missing an accessible name, and no-ops if Barn2 fixes this upstream.
*/
(function () {
'use strict';
function needsName(el) {
if (el.getAttribute('aria-label')) return false;
if (el.getAttribute('aria-labelledby')) return false;
if (el.getAttribute('title')) return false;
if (el.closest('label')) return false;
if (el.id) {
var sel = (window.CSS && CSS.escape) ? CSS.escape(el.id) : el.id;
if (document.querySelector('label[for="' + sel + '"]')) return false;
}
return true;
}
function fix() {
document.querySelectorAll('table.wc-product-table').forEach(function (table) {
// 1. "Select all" checkbox — connect the visible label Barn2 already
// prints but never associates with the input.
table.querySelectorAll('span.wpt-bulk-select-wrap').forEach(function (wrap) {
var label = wrap.querySelector('label.wpt-bulk-select-label');
var box = wrap.querySelector('input.wpt-bulk-select');
if (label && box && !box.id) {
var id = 'wpt-bulk-all-' + Math.random().toString(36).slice(2, 8);
box.id = id;
label.setAttribute('for', id);
}
});
// 2. Per-row bulk-select checkboxes (no visible label text).
table.querySelectorAll('tbody input.wpt-bulk-select').forEach(function (box) {
if (needsName(box)) box.setAttribute('aria-label', 'Select this product');
});
// 3. Quantity fields in the Buy column — name them after the product.
table.querySelectorAll('tbody input.qty, tbody input[type="number"]').forEach(function (qty) {
if (!needsName(qty)) return;
var firstCell = qty.closest('tr') && qty.closest('tr').querySelector('td');
var product = firstCell ? firstCell.textContent.replace(/\s+/g, ' ').trim().slice(0, 80) : '';
qty.setAttribute('aria-label', product ? 'Quantity for ' + product : 'Quantity');
});
// 4. The table's own search box / "show entries" dropdown.
var wrapper = table.closest('.dataTables_wrapper');
if (wrapper) {
wrapper.querySelectorAll('input[type="search"], .dataTables_filter input').forEach(function (s) {
if (needsName(s)) s.setAttribute('aria-label', s.getAttribute('placeholder') || 'Search products');
});
wrapper.querySelectorAll('.dataTables_length select').forEach(function (sel) {
if (needsName(sel)) sel.setAttribute('aria-label', 'Number of products to show');
});
}
});
}
// Run on initial build and after every AJAX redraw / sort / page change.
if (window.jQuery) {
jQuery(document).on('init.dt draw.dt', 'table.wc-product-table', fix);
}
document.addEventListener('DOMContentLoaded', fix);
window.addEventListener('load', function () { setTimeout(fix, 1200); });
})();
/* ---------------------------------------------------------------------------
* functions.php enqueue (Option B) — add to the child theme's functions.php:
*
* add_action( 'wp_enqueue_scripts', function () {
* wp_enqueue_script(
* 'fs-a11y-form-labels',
* get_stylesheet_directory_uri() . '/js/c1-form-input-labels-fix.js',
* array( 'jquery' ),
* '1.0.0',
* true // load in footer
* );
* } );
* ------------------------------------------------------------------------- */