Skip to content

Commit 22693f2

Browse files
Commit of final version
1 parent 942f7ba commit 22693f2

File tree

1 file changed

+152
-10
lines changed

1 file changed

+152
-10
lines changed

techniques/client-side-script/SCR27.html

Lines changed: 152 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,7 @@ <h2>Description</h2>
2424

2525
<p>This example does up and down reordering. This approach can also be used for two-dimensional reordering by adding left and right options.</p>
2626
<p>The components in this example are list items in an unordered list. Unordered lists are a very good semantic model for sets of similar items, like these components. The menu approach can also be used for other types of groupings.</p>
27-
<p>The modules are list items, and each module, in addition to content in <code class="language-html">div</code> elements, contains a menu represented as a nested list.</p>
28-
29-
30-
<p>Since we've covered the showing and hiding of menus in the simple tree samples, we'll focus here just on the code that swaps the modules. Once we harmonize the events and cancel the default link action, we go to work. First, we set a bunch of local variables for the elements with which we'll be working: the menu, the module to be reordered, the menuLink. Then, after checking the reorder direction, we try to grab the node to swap. If we find one, we then call <code class="language-javascript">swapNode()</code> to swap our two modules, and <code class="language-javascript">PositionElement()</code> to move the absolutely-positioned menu along with the module, and then set focus back on the menu item which launched the whole thing.</p>
31-
32-
33-
27+
<p>In this example, the menu is always visible. This is a good approach for components that are not too numerous, as it allows users to see the options without having to open a menu.</p>
3428
<p>The <abbr title="Cascading Style Sheets">CSS</abbr> for swap using buttons is extensive to account for all WCAG accessibility conformance requirements. It includes media queries that adjust the text boxes based on the size of the screen. The example thus conforms to WCAG 2.2 </p>
3529

3630
<pre xml:space="preserve"><code class="language-css">
@@ -165,17 +159,165 @@ <h2>Description</h2>
165159
/* Improve text selection visibility */
166160
::selection { background: #b3d4fc; color: #000; }}
167161
</code></pre>
162+
</section>
163+
<pre xml:space="preserve"><code class="language-javascript">
164+
'use strict'; // Enable strict mode to catch common errors and enforce safer coding
168165

166+
(function main() {
167+
// Get references to the main list and the live region for announcements
168+
const list = document.getElementById('myList'); // The <ul> containing list items
169+
const live = document.getElementById('sr-live'); // The ARIA live region for screen readers
169170

170-
</section>
171+
// Safety check: if list is missing, log error and stop execution
172+
if (!list) {
173+
console.error('[swap-list] #myList not found. Aborting setup.');
174+
return;
175+
}
176+
177+
// Key constants to improve readability
178+
const KEY = Object.freeze({
179+
ENTER: 'Enter',
180+
ARROW_UP: 'ArrowUp',
181+
ARROW_DOWN: 'ArrowDown'
182+
});
183+
184+
/**
185+
* announce(msg)
186+
* Updates the live region for screen readers with a message
187+
* @param {string} msg - The message to announce
188+
*/
189+
function announce(msg) {
190+
if (!live) return; // Exit if live region does not exist
191+
live.textContent = String(msg); // Update the live region text
192+
}
193+
194+
/**
195+
* closestLI(el)
196+
* Finds the closest parent <li> element of a given element
197+
* @param {Element} el - The starting element
198+
* @returns {HTMLElement|null} - The closest <li> or null
199+
*/
200+
function closestLI(el) {
201+
if (!(el instanceof Element)) return null;
202+
return el.closest('li'); // Uses DOM traversal to find the parent <li>
203+
}
204+
205+
/**
206+
* directionFromButton(btn)
207+
* Determines the direction ("up" or "down") based on button class
208+
* @param {Element} btn - The button element
209+
* @returns {string|null} - "up", "down", or null
210+
*/
211+
function directionFromButton(btn) {
212+
if (!(btn instanceof Element)) return null;
213+
if (btn.classList.contains('up')) return 'up';
214+
if (btn.classList.contains('down')) return 'down';
215+
return null;
216+
}
217+
218+
/**
219+
* swap(li, direction, focusBtn)
220+
* Moves a list item up or down in the DOM and manages focus
221+
* @param {HTMLElement} li - The <li> element to move
222+
* @param {string} direction - "up" or "down"
223+
* @param {HTMLElement} focusBtn - Optional: the button to keep focus on
224+
*/
225+
function swap(li, direction, focusBtn) {
226+
if (!(li instanceof Element) || !li.parentNode) return; // Safety check
227+
const parent = li.parentNode;
228+
229+
if (direction === 'up') {
230+
const prev = li.previousElementSibling; // Get the previous item
231+
if (prev) {
232+
parent.insertBefore(li, prev); // Swap positions with previous
233+
announce('Moved item up.'); // Screen reader announcement
234+
} else {
235+
parent.appendChild(li); // Wrap first item to end
236+
announce('Moved first item to end.');
237+
}
238+
} else if (direction === 'down') {
239+
const next = li.nextElementSibling; // Get the next item
240+
if (next) {
241+
parent.insertBefore(next, li); // Swap with next
242+
announce('Moved item down.');
243+
} else {
244+
parent.insertBefore(li, parent.firstElementChild); // Wrap last to start
245+
announce('Moved last item to start.');
246+
}
247+
}
248+
249+
// === Focus management ===
250+
if (focusBtn instanceof HTMLElement) {
251+
focusBtn.focus(); // Keep focus on the button that triggered the swap
252+
} else {
253+
// Fallback: focus first focusable element inside the <li> or the <li> itself
254+
const focusable = li.querySelector('button, [href], input, select, textarea');
255+
if (focusable instanceof HTMLElement) {
256+
focusable.focus();
257+
} else {
258+
li.focus && li.focus();
259+
}
260+
}
261+
}
262+
263+
/**
264+
* Click event handler for the list
265+
* Detects if a button inside a <li> was clicked and swaps the item
266+
*/
267+
list.addEventListener('click', (e) => {
268+
const button = (e.target instanceof Element) ? e.target.closest('button') : null;
269+
if (!button) return; // Only proceed if a button was clicked
270+
271+
const dir = directionFromButton(button); // Determine swap direction
272+
if (!dir) return;
273+
274+
const li = closestLI(button); // Find the parent <li>
275+
if (!li) return;
276+
277+
swap(li, dir, button); // Perform the swap and keep focus
278+
});
279+
280+
/**
281+
* Keyboard event handler for accessibility
282+
* Supports Enter to activate button and arrow keys to move items
283+
*/
284+
list.addEventListener('keydown', (e) => {
285+
const btn = (e.target instanceof Element) ? e.target.closest('button') : null;
286+
287+
// Enter on a button triggers swap
288+
if (btn && e.key === KEY.ENTER) {
289+
const dir = directionFromButton(btn);
290+
if (!dir) return;
291+
const li = closestLI(btn);
292+
if (!li) return;
293+
swap(li, dir, btn); // Pass the button to preserve focus
294+
e.preventDefault();
295+
return;
296+
}
297+
298+
// Arrow keys on the <li> move items
299+
const li = closestLI(e.target);
300+
if (!li) return;
301+
302+
if (e.key === KEY.ARROW_UP) {
303+
swap(li, 'up');
304+
e.preventDefault();
305+
} else if (e.key === KEY.ARROW_DOWN) {
306+
swap(li, 'down');
307+
e.preventDefault();
308+
}
309+
});
310+
})();
311+
</code></pre>
171312
</section>
313+
172314
<section id="tests">
173315
<h2>Tests</h2>
174316
<section class="procedure">
175317
<h3>Procedure</h3>
176318
<ol>
177319
<li>Find all components which can be reordered via drag and drop.</li>
178-
<li>Check that there is also a mechanism to reorder them using menus containing buttons, or other appropriate controls</li>
320+
<li>Check that there is also a mechanism to reorder them using menus containing buttons, or other appropriate controls.</li>
179321
<li>Check that the menus are contained within the re-orderable items in the DOM.</li>
180322
<li>Check that scripts for reordering are triggered only from the onclick event of the menu controls.</li>
181323
<li>Check that items are reordered in the DOM, not only visually.</li>
@@ -188,6 +330,6 @@ <h3>Expected Results</h3>
188330
</ul>
189331
</section>
190332
</section>
191-
<p>Test paragraph to test git push functionality</p>
333+
192334
</body>
193335
</html>

0 commit comments

Comments
 (0)