Merge branch 'MDL-42340-master' of git://github.com/andrewnicols/moodle
[moodle.git] / lib / yui / src / notification / js / dialogue.js
index e6335ac..59214e9 100644 (file)
@@ -10,7 +10,8 @@ var DIALOGUE_NAME = 'Moodle dialogue',
     DIALOGUE_FULLSCREEN_CLASS = DIALOGUE_PREFIX + '-fullscreen',
     DIALOGUE_HIDDEN_CLASS = DIALOGUE_PREFIX + '-hidden',
     DIALOGUE_SELECTOR =' [role=dialog]',
-    MENUBAR_SELECTOR = '[role=menubar]';
+    MENUBAR_SELECTOR = '[role=menubar]',
+    CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]';
 
 /**
  * A re-usable dialogue box with Moodle classes applied.
@@ -112,6 +113,7 @@ Y.extend(DIALOGUE, Y.Panel, {
         // been positioned it will scroll back to the top of the page.
         if (config.visible) {
             this.show();
+            this.keyDelegation();
         }
     },
 
@@ -167,10 +169,11 @@ Y.extend(DIALOGUE, Y.Panel, {
      * @return void
      */
     visibilityChanged : function(e) {
-        var titlebar;
+        var titlebar, bb;
         if (e.attrName === 'visible') {
             this.get('maskNode').addClass(CSS.LIGHTBOX);
             if (e.prevVal && !e.newVal) {
+                bb = this.get('boundingBox');
                 if (this._resizeevent) {
                     this._resizeevent.detach();
                     this._resizeevent = null;
@@ -179,6 +182,7 @@ Y.extend(DIALOGUE, Y.Panel, {
                     this._orientationevent.detach();
                     this._orientationevent = null;
                 }
+                bb.detach('key', this.keyDelegation);
             }
             if (!e.prevVal && e.newVal) {
                 // This needs to be done each time the dialog is shown as new dialogs may have been opened.
@@ -192,6 +196,7 @@ Y.extend(DIALOGUE, Y.Panel, {
                         Y.one(titlebar).setStyle('cursor', 'move');
                     }
                 }
+                this.keyDelegation();
             }
             if (this.get('center') && !e.prevVal && e.newVal) {
                 this.centerDialogue();
@@ -292,6 +297,42 @@ Y.extend(DIALOGUE, Y.Panel, {
             content.focus();
         }
         return result;
+    },
+    /**
+     * Setup key delegation to keep tabbing within the open dialogue.
+     *
+     * @method keyDelegation
+     */
+    keyDelegation : function() {
+        var bb = this.get('boundingBox');
+        bb.delegate('key', function(e){
+            var target = e.target;
+            var direction = 'forward';
+            if (e.shiftKey) {
+                direction = 'backward';
+            }
+            if (this.trapFocus(target, direction)) {
+                e.preventDefault();
+            }
+        }, 'down:9', CAN_RECEIVE_FOCUS_SELECTOR, this);
+    },
+    /**
+     * Trap the tab focus within the open modal.
+     *
+     * @param string target the element target
+     * @param string direction tab key for forward and tab+shift for backward
+     * @returns bool
+     */
+    trapFocus : function(target, direction) {
+        var bb = this.get('boundingBox'),
+            firstitem = bb.one(CAN_RECEIVE_FOCUS_SELECTOR),
+            lastitem = bb.all(CAN_RECEIVE_FOCUS_SELECTOR).pop();
+
+        if (target === lastitem && direction === 'forward') { // Tab key.
+            return firstitem.focus();
+        } else if (target === firstitem && direction === 'backward') {  // Tab+shift key.
+            return lastitem.focus();
+        }
     }
 }, {
     NAME : DIALOGUE_NAME,