diff --git a/build/assets/js/modules/quick-key-manager.js b/build/assets/js/modules/quick-key-manager.js new file mode 100644 index 00000000..858b2ba5 --- /dev/null +++ b/build/assets/js/modules/quick-key-manager.js @@ -0,0 +1,62 @@ +/** + * @class + * Class to manage quick keys in the current application. + */ +"use strict"; + +import { QuickKey } from "./quick-key.js"; + +export class QuickKeyManager { + + quickKeys = new Map(); + + /** + * @constructor + * @param {Object} quickKeyData - Set of key/value pairs mapping keyboard characters to CSS selectors. + * @param {Node} rootNode - Root node to use for finding quick key matches. + * @returns {QuickKeyManager} - A new instance of the QuickKeyManager class. + */ + constructor(quickKeyData, rootNode) { + // Create a new QuickKey object for each key/selector pair. + for (const key in quickKeyData) { + if (Object.hasOwnProperty.call(quickKeyData, key)) { + const selector = quickKeyData[key]; + const qk = new QuickKey(key, selector, rootNode); + if (qk.nodes.length > 0) { + this.quickKeys.set(key, new QuickKey(key, selector, rootNode)); + } + } + } + } + + /** + * Binds the specified function to the node returned + * by the pressed quick key. The lowercase quick key yields + * the next matching node, and the uppercase quick key yields + * the previous matching node. + * + * The specified function must take two parameters: + * - The node returned by the quick key. + * - The event processed by the "keydown" listener. + * @method + * @param {func} - The function to call on the node returned by pressing the quick key. + */ + bindQuickKeysToFunction(func) { + document.addEventListener( 'keydown', function (e) { + // If the lowercase quick key is pressed... + // use the next matching node. + var node = null; + if (this.quickKeys.has(e.key)) { + node = this.quickKeys.get(e.key).nextNode(); + } + // If the uppercase quick key is pressed, + // use the previous matching node. + else if (e.key === e.key.toUpperCase() && this.quickKeys.has(e.key.toLowerCase)) { + node = this.quickKeys.get(e.key.toLowerCase()).previousNode(); + } + if (node) { + func(node, e); + } + }); + } +} \ No newline at end of file diff --git a/build/assets/js/modules/quick-key.js b/build/assets/js/modules/quick-key.js new file mode 100644 index 00000000..43ead3b1 --- /dev/null +++ b/build/assets/js/modules/quick-key.js @@ -0,0 +1,120 @@ +/** + * @class + * Class to represent a screen reader quick key to iterate through a set of + * tags in the DOM when a certain key is pressed. + */ + + "use strict"; + + export class QuickKey { + + // Key to press to advance to the next node. + key = null; + + // CSS selector for DOM nodes matched by this quick key. + selector = ''; + + // List of nodes that that match this quick key. + nodes = []; + + // Index of current node in list of nodes. + currentNodeIndex = -1; + + /** + * @constructor + * + * @param {String} key - Key to press to advance to the next node. + * @param {String} selector - CSS selector for DOM nodes matched by this quick key + * @param {Node} rootNode - Root node against which selector is run (optional). + * @returns {QuickKey} - A new instance of the QuickKey class. + */ + constructor(key, selector, rootNode = null) { + this.key = key; + this.selector = selector; + if (rootNode) { + this.nodes = this.findNodes(rootNode); + } + } + + /** + * Finds and returns a list of nodes which matches the object's selector property. + * @method + * @param {NodeListOf} rootNode - Root node against which selector is run. + * @returns {Array[Node]} - List of nodes matching the object's selector. + */ + findNodes(rootNode) { + var nodes = []; + if (rootNode) { + nodes = rootNode.querySelectorAll(this.selector); + } + return nodes; + } + + /** + * Returns the current node matched by this quick key. + * @method + * @returns {Node} - The current matching node or undefined if there are none + * or the list of nodes has not been traversed yet. + */ + currentNode() { + if (!this.nodes.length || this.currentNodeIndex < 0) { + return; + } + return this.nodes[this.currentNodeIndex]; + } + + /** + * Returns the next node in the DOM matched by this quick key. + * @method + * @returns {Node} - The next matching node (which could be the first one + * if we are at the end of the list), or undefined if there are no matching nodes. + */ + nextNode() { + // Make sure there are matching nodes for this quick key. + if (!this.nodes.length) { + return; + } + + // Return this node if it's the only one in the list. + if (this.nodes.length == 1) { + this.currentNodeIndex = 0; + } + // Get the next node in the list. + else if (this.currentNodeIndex < this.nodes.length - 1) { + this.currentNodeIndex += 1; + } + // Or loop around to to the start of the list if we are at the end. + else { + this.currentNodeIndex = 0; + } + + return this.nodes[this.currentNodeIndex]; + } + + /** + * Returns the previous node in the DOM matched by this quick key. + * @method + * @returns {Node} - The previous matching node (which could be the last one + * if we are at the start of the list), or null if there are no matching nodes. + */ + previousNode() { + // Make sure there are matching nodes for this quick key. + if (!this.nodes.length) { + return; + } + + // Return this node if it's the only one in the list. + if (this.nodes.length == 1) { + this.currentNodeIndex = 0; + } + // Get the previous node in the list. + else if (this.currentNodeIndex > 0) { + this.currentNodeIndex -= 1; + } + // Or loop around to to the start of the list if we are at the end. + else { + this.currentNodeIndex = this.nodes.length - 1; + } + return this.nodes[this.currentNodeIndex]; + } + } \ No newline at end of file diff --git a/build/assets/js/quick-key-setup.js b/build/assets/js/quick-key-setup.js new file mode 100644 index 00000000..e877e540 --- /dev/null +++ b/build/assets/js/quick-key-setup.js @@ -0,0 +1,41 @@ +"use strict"; + +import { QuickKeyManager } from './modules/quick-key-manager.js'; + +// Keys/values for quick keys and the CSS selectors they match. +let keyData = { + // Press h/H to move forward/backward through headings. + 'h': 'h1, h2, h3, h4, h5, h6', + // Press k/K to move forward/backward through links. + 'k': 'a, [role="link"]', + // Press r/R to move forward/backward through page regions and landmarks. + 'r': ' header, nav, main, footer, [role="region"], [role="banner"], [role="navigation"], [role="main"], [role="contentinfo"], [role="search"]', + // Press f/F to move forward/backward through form controls. + 'f': 'input, select, button, [role="form"], [role="textbox"], [role="checkbox"], [role="button"]', + // Press b/B to move forward/backward through buttons. + 'b': 'button, input[type="button"], input[type="submit"], input[type="reset"], [role="button"]', + // Press l/L to move forward/backward through lists. + 'l': 'ul, ol, dl, [role="list"]' +}; + +let qkm = new QuickKeyManager(keyData, document.getElementById('content')); + +// Check if the pressed key is a quick key. +document.addEventListener( 'keydown', function (e) { + // If the pressed key is the lowercase version of a quick key, + // target the next matching node. + var node = null; + if (qkm.quickKeys.has(e.key)) { + node = qkm.quickKeys.get(e.key).nextNode(); + } + // If the pressed key is the uppercase version of a quick key, + // target the next matching node. + else if (e.key === e.key.toUpperCase() && qkm.quickKeys.has(e.key.toLowerCase())) { + node = qkm.quickKeys.get(e.key.toLowerCase()).previousNode(); + } + // Move keyboard focus to the matching node (if any). + if (node) { + // alert( e.key + " ==> " + node.nodeName + ": " + node.textContent ); + node.focus(); + } +}); diff --git a/build/index.html b/build/index.html index 55566311..2a774a92 100644 --- a/build/index.html +++ b/build/index.html @@ -1,4 +1,4 @@ -Inclusiville Arts Festival 2021
Cobblestone alley with colourful umbrellas hanging overhead

Inclusiville Arts Festival 2021

A festival to celebrate the local creative talent held at Inclusiville Showgrounds this year.

View schedule

Welcome to IAF 2021

This is a great space to write long text about your company and your services. You can use this space to go into a little more detail about your company. Talk about your team and what services you provide. Tell your visitors the story of how you came up with the idea for your business and what makes you different from your competitors. Make your company stand out and show.

Celebrating local talent

Event Schedule

A person dancing

The Digital Navigator: an interpretive dance — 12pm

Headbanging Keyboards the band

Headbanging Keyboards – 1pm

The Empaths the band

The Empaths – 2pm

Wucag Tang Clang the band

Wucag Tang Clan — 3pm

Multiple fireworks in the sky with a man holding a sparkler on top of a hill at twilight

Closing event: Fireworks on the hills — 6pm

FAQ

All events are general admission, meaning that you are able to choose where to sit when you arrive. This works on a first come, first served basis.

IAF is an alcohol free, family friendly event held during the day. We welcome attendees of all ages, however, children under the age of 16 must be accompanied by a guardian.

Inclusiville Arts Festival allows guide dogs. If you find that you need assitance after you arrive at the showgrounds, there will be event staff available to assist you. Please indicate to the ticketing staff as you enter the grounds.

If you find that you need assitance after you arrive at the showgrounds, there will be event staff available to assist you. Please indicate to the ticketing staff as you enter the grounds.

Register

Thank you, your registration was successful.
Festival date

3rd March 10:00 AM - 6:00 PM

Festival location

Inclusiville Showgrounds, Inclusiville
XYZ 54321.

Enquiries

(000) 123 - 4567

\ No newline at end of file + });