@@ -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
1512Unraid'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+
4856101 . ** Always include CSRF token** in POST requests
4866112 . ** Use proper error handling** - show user-friendly messages
4876123 . ** Provide user feedback** - show loading states, confirmations
0 commit comments