Skip to content

Commit d0cca74

Browse files
committed
Enhance JavaScript patterns documentation with new sections on keyboard shortcuts, event handling best practices, focus trapping for modals, and XSS prevention techniques.
1 parent 7e7be29 commit d0cca74

File tree

1 file changed

+128
-3
lines changed

1 file changed

+128
-3
lines changed

docs/ui/javascript-patterns.md

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ nav_order: 2
77

88
# JavaScript Patterns
99

10-
{: .warning }
11-
> This page is a stub. [Help us expand it!](https://github.com/mstrhakr/unraid-plugin-docs/blob/main/CONTRIBUTING.md)
12-
1310
## Overview
1411

1512
Unraid's web UI relies heavily on jQuery for DOM manipulation and AJAX. This page documents common patterns for interactivity in plugins.
@@ -465,6 +462,8 @@ location.reload(true);
465462

466463
## Keyboard Shortcuts
467464

465+
### Basic Shortcuts
466+
468467
```javascript
469468
$(document).on('keydown', function(e) {
470469
// Ctrl+S to save
@@ -480,8 +479,134 @@ $(document).on('keydown', function(e) {
480479
});
481480
```
482481

482+
### Namespaced Event Handlers
483+
484+
Avoid event collisions with Unraid's built-in handlers by using namespaced events:
485+
486+
```javascript
487+
// BAD - may conflict or accumulate handlers
488+
$(document).on('keydown', function(e) { ... });
489+
490+
// GOOD - namespaced event, easy to remove
491+
$(document).off('keydown.myplugin').on('keydown.myplugin', function(e) {
492+
if ($('#my-modal').hasClass('active')) {
493+
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
494+
e.preventDefault();
495+
saveCurrentTab();
496+
}
497+
if (e.key === 'Escape') {
498+
e.preventDefault();
499+
closeModal();
500+
}
501+
}
502+
});
503+
504+
// Remove handler when done
505+
$(document).off('keydown.myplugin');
506+
```
507+
508+
### Focus Trapping for Modals
509+
510+
Keep keyboard focus inside modal dialogs for accessibility:
511+
512+
```javascript
513+
$(document).on('keydown.mymodal', function(e) {
514+
if (e.key === 'Tab') {
515+
var $modal = $('#my-modal');
516+
var $focusable = $modal.find('a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])').filter(':visible:not(:disabled)');
517+
if ($focusable.length === 0) return;
518+
519+
var first = $focusable[0];
520+
var last = $focusable[$focusable.length - 1];
521+
522+
// Trap focus inside modal
523+
if (!e.shiftKey && document.activeElement === last) {
524+
e.preventDefault();
525+
first.focus();
526+
} else if (e.shiftKey && document.activeElement === first) {
527+
e.preventDefault();
528+
last.focus();
529+
}
530+
}
531+
});
532+
```
533+
483534
## Best Practices
484535

536+
### Namespace Your Timers
537+
538+
Avoid collision with Unraid's global `timers` object:
539+
540+
```javascript
541+
// BAD - conflicts with Unraid's global timers
542+
var timers = {};
543+
timers.refresh = setInterval(...);
544+
545+
// GOOD - plugin-specific namespace
546+
var myPluginTimers = {};
547+
myPluginTimers.refresh = setInterval(...);
548+
myPluginTimers.load = setTimeout(...);
549+
550+
// Clean up on page unload
551+
$(window).on('beforeunload', function() {
552+
clearInterval(myPluginTimers.refresh);
553+
clearTimeout(myPluginTimers.load);
554+
});
555+
```
556+
557+
### Async Loading for Expensive Operations
558+
559+
Don't block page render with slow operations (like Docker commands):
560+
561+
```javascript
562+
// Show spinner with delay to avoid flash on fast loads
563+
var myPluginTimers = {};
564+
565+
function loadList() {
566+
myPluginTimers.load = setTimeout(function(){
567+
$('div.spinner.fixed').show('slow');
568+
}, 500);
569+
570+
$.get('/plugins/myplugin/php/list.php', function(data) {
571+
clearTimeout(myPluginTimers.load);
572+
$('#list-container').html(data);
573+
initializeUI(); // Set up event handlers on new content
574+
$('div.spinner.fixed').hide('slow');
575+
}).fail(function() {
576+
clearTimeout(myPluginTimers.load);
577+
$('div.spinner.fixed').hide('slow');
578+
$('#list-container').html('<p style="color:red">Failed to load. Please refresh.</p>');
579+
});
580+
}
581+
582+
$(loadList); // Load on document ready
583+
```
584+
585+
### XSS Prevention
586+
587+
Never insert user content or error messages directly into HTML:
588+
589+
```javascript
590+
// BAD - XSS vulnerability
591+
$('#error-display').html(errorMessage);
592+
$('#name').html(userName);
593+
594+
// GOOD - safe text insertion
595+
$('#error-display').text(errorMessage);
596+
597+
// GOOD - using createTextNode for complex cases
598+
var textNode = document.createTextNode(errorMessage);
599+
$('#error-display').empty().append(textNode);
600+
601+
// GOOD - escape HTML entities if you need to insert HTML structure
602+
function escapeHtml(text) {
603+
return $('<div>').text(text).html();
604+
}
605+
$('#container').html('<span class="error">' + escapeHtml(errorMessage) + '</span>');
606+
```
607+
608+
### General Guidelines
609+
485610
1. **Always include CSRF token** in POST requests
486611
2. **Use proper error handling** - show user-friendly messages
487612
3. **Provide user feedback** - show loading states, confirmations

0 commit comments

Comments
 (0)