forked from moodle/moodle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MDL-44101 Javascript: Introduce a module to focus after widget close
This module supports focusing on a specified Node, or attempting to determine the Node which caused the displayed the Widget to return focus to that location.
- Loading branch information
1 parent
dea614e
commit 7ae6ce0
Showing
7 changed files
with
535 additions
and
0 deletions.
There are no files selected for viewing
176 changes: 176 additions & 0 deletions
176
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-debug.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
YUI.add('moodle-core-widget-focusafterclose', function (Y, NAME) { | ||
|
||
/** | ||
* Provides support for focusing on different nodes after the Widget is | ||
* hidden. | ||
* | ||
* If the focusOnPreviousTargetAfterHide attribute is true, then the module hooks | ||
* into the show function for that Widget to try and determine which Node | ||
* caused the Widget to be shown. | ||
* | ||
* Alternatively, the focusAfterHide attribute can be passed a Node. | ||
* | ||
* @module moodle-core-widget-focusafterhide | ||
*/ | ||
|
||
var CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex], [contenteditable="true"]'; | ||
|
||
/** | ||
* Provides support for focusing on different nodes after the Widget is | ||
* hidden. | ||
* | ||
* @class M.core.WidgetFocusAfterHide | ||
*/ | ||
function WidgetFocusAfterHide() { | ||
Y.after(this._bindUIFocusAfterHide, this, 'bindUI'); | ||
if (this.get('rendered')) { | ||
this._bindUIFocusAfterHide(); | ||
} | ||
} | ||
|
||
WidgetFocusAfterHide.ATTRS = { | ||
/** | ||
* Whether to focus on the target that caused the Widget to be shown. | ||
* | ||
* <em>If this is true, and a valid Node is found, any Node specified to focusAfterHide | ||
* will be ignored.</em> | ||
* | ||
* @attribute focusOnPreviousTargetAfterHide | ||
* @default false | ||
* @type boolean | ||
*/ | ||
focusOnPreviousTargetAfterHide: { | ||
value: false | ||
}, | ||
|
||
/** | ||
* The Node to focus on after hiding the Widget. | ||
* | ||
* <em>Note: If focusOnPreviousTargetAfterHide is true, and a valid Node is found, then this | ||
* value will be ignored. If it is true and not found, then this value will be used as | ||
* a fallback.</em> | ||
* | ||
* @attribute focusAfterHide | ||
* @default null | ||
* @type Node | ||
*/ | ||
focusAfterHide: { | ||
value: null, | ||
type: Y.Node | ||
} | ||
}; | ||
|
||
WidgetFocusAfterHide.prototype = { | ||
/** | ||
* The list of Event Handles which we should cancel when the dialogue is destroyed. | ||
* | ||
* @property uiHandleFocusAfterHide | ||
* @type array | ||
* @protected | ||
*/ | ||
_uiHandlesFocusAfterHide: [], | ||
|
||
/** | ||
* A reference to the real show method which is being overwritten. | ||
* | ||
* @property _showFocusAfterHide | ||
* @type function | ||
* @default null | ||
* @protected | ||
*/ | ||
_showFocusAfterHide: null, | ||
|
||
/** | ||
* A reference to the detected previous target. | ||
* | ||
* @property _previousTargetFocusAfterHide | ||
* @type function | ||
* @default null | ||
* @protected | ||
*/ | ||
_previousTargetFocusAfterHide: null, | ||
|
||
initializer: function() { | ||
|
||
if (this.get('focusOnPreviousTargetAfterHide') && this.show) { | ||
// Overwrite the parent method so that we can get the focused | ||
// target. | ||
this._showFocusAfterHide = this.show; | ||
this.show = function(e) { | ||
this._showFocusAfterHide.apply(this, arguments); | ||
|
||
// We use a property rather than overriding the focusAfterHide parameter in | ||
// case the target cannot be found at hide time. | ||
this._previousTargetFocusAfterHide = null; | ||
if (e && e.currentTarget) { | ||
Y.log("Determined a Node which caused the Widget to be shown", | ||
'debug', 'moodle-core-widget-focusafterhide'); | ||
this._previousTargetFocusAfterHide = e.currentTarget; | ||
} | ||
}; | ||
} | ||
}, | ||
|
||
destructor: function() { | ||
new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); | ||
}, | ||
|
||
/** | ||
* Set up the event handling required for this module to work. | ||
* | ||
* @method _bindUIFocusAfterHide | ||
* @private | ||
*/ | ||
_bindUIFocusAfterHide: function() { | ||
// Detach the old handles first. | ||
new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); | ||
this.uiHandleFocusAfterHide = [ | ||
this.after('visibleChange', this._afterHostVisibleChangeFocusAfterHide) | ||
]; | ||
}, | ||
|
||
/** | ||
* Handle the change in UI visibility. | ||
* | ||
* This method changes the focus after the hide has taken place. | ||
* | ||
* @method _afterHostVisibleChangeFocusAfterHide | ||
* @private | ||
*/ | ||
_afterHostVisibleChangeFocusAfterHide: function() { | ||
if (!this.get('visible')) { | ||
if (this._attemptFocus(this._previousTargetFocusAfterHide)) { | ||
Y.log("Focusing on the target automatically determined when the Widget was opened", | ||
'debug', 'moodle-core-widget-focusafterhide'); | ||
|
||
} else if (this._attemptFocus(this.get('focusAfterHide'))) { | ||
// Fall back to the focusAfterHide value if one was specified. | ||
Y.log("Focusing on the target provided to focusAfterHide", | ||
'debug', 'moodle-core-widget-focusafterhide'); | ||
|
||
} else { | ||
Y.log("No valid focus target found - not returning focus.", | ||
'debug', 'moodle-core-widget-focusafterhide'); | ||
|
||
} | ||
} | ||
}, | ||
|
||
_attemptFocus: function(node) { | ||
var focusTarget = Y.one(node); | ||
if (focusTarget) { | ||
focusTarget = focusTarget.ancestor(CAN_RECEIVE_FOCUS_SELECTOR, true); | ||
if (focusTarget) { | ||
focusTarget.focus(); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
}; | ||
|
||
var NS = Y.namespace('M.core'); | ||
NS.WidgetFocusAfterHide = WidgetFocusAfterHide; | ||
|
||
|
||
}, '@VERSION@', {"requires": ["base-build", "widget"]}); |
1 change: 1 addition & 0 deletions
1
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-min.js
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
168 changes: 168 additions & 0 deletions
168
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
YUI.add('moodle-core-widget-focusafterclose', function (Y, NAME) { | ||
|
||
/** | ||
* Provides support for focusing on different nodes after the Widget is | ||
* hidden. | ||
* | ||
* If the focusOnPreviousTargetAfterHide attribute is true, then the module hooks | ||
* into the show function for that Widget to try and determine which Node | ||
* caused the Widget to be shown. | ||
* | ||
* Alternatively, the focusAfterHide attribute can be passed a Node. | ||
* | ||
* @module moodle-core-widget-focusafterhide | ||
*/ | ||
|
||
var CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex], [contenteditable="true"]'; | ||
|
||
/** | ||
* Provides support for focusing on different nodes after the Widget is | ||
* hidden. | ||
* | ||
* @class M.core.WidgetFocusAfterHide | ||
*/ | ||
function WidgetFocusAfterHide() { | ||
Y.after(this._bindUIFocusAfterHide, this, 'bindUI'); | ||
if (this.get('rendered')) { | ||
this._bindUIFocusAfterHide(); | ||
} | ||
} | ||
|
||
WidgetFocusAfterHide.ATTRS = { | ||
/** | ||
* Whether to focus on the target that caused the Widget to be shown. | ||
* | ||
* <em>If this is true, and a valid Node is found, any Node specified to focusAfterHide | ||
* will be ignored.</em> | ||
* | ||
* @attribute focusOnPreviousTargetAfterHide | ||
* @default false | ||
* @type boolean | ||
*/ | ||
focusOnPreviousTargetAfterHide: { | ||
value: false | ||
}, | ||
|
||
/** | ||
* The Node to focus on after hiding the Widget. | ||
* | ||
* <em>Note: If focusOnPreviousTargetAfterHide is true, and a valid Node is found, then this | ||
* value will be ignored. If it is true and not found, then this value will be used as | ||
* a fallback.</em> | ||
* | ||
* @attribute focusAfterHide | ||
* @default null | ||
* @type Node | ||
*/ | ||
focusAfterHide: { | ||
value: null, | ||
type: Y.Node | ||
} | ||
}; | ||
|
||
WidgetFocusAfterHide.prototype = { | ||
/** | ||
* The list of Event Handles which we should cancel when the dialogue is destroyed. | ||
* | ||
* @property uiHandleFocusAfterHide | ||
* @type array | ||
* @protected | ||
*/ | ||
_uiHandlesFocusAfterHide: [], | ||
|
||
/** | ||
* A reference to the real show method which is being overwritten. | ||
* | ||
* @property _showFocusAfterHide | ||
* @type function | ||
* @default null | ||
* @protected | ||
*/ | ||
_showFocusAfterHide: null, | ||
|
||
/** | ||
* A reference to the detected previous target. | ||
* | ||
* @property _previousTargetFocusAfterHide | ||
* @type function | ||
* @default null | ||
* @protected | ||
*/ | ||
_previousTargetFocusAfterHide: null, | ||
|
||
initializer: function() { | ||
|
||
if (this.get('focusOnPreviousTargetAfterHide') && this.show) { | ||
// Overwrite the parent method so that we can get the focused | ||
// target. | ||
this._showFocusAfterHide = this.show; | ||
this.show = function(e) { | ||
this._showFocusAfterHide.apply(this, arguments); | ||
|
||
// We use a property rather than overriding the focusAfterHide parameter in | ||
// case the target cannot be found at hide time. | ||
this._previousTargetFocusAfterHide = null; | ||
if (e && e.currentTarget) { | ||
this._previousTargetFocusAfterHide = e.currentTarget; | ||
} | ||
}; | ||
} | ||
}, | ||
|
||
destructor: function() { | ||
new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); | ||
}, | ||
|
||
/** | ||
* Set up the event handling required for this module to work. | ||
* | ||
* @method _bindUIFocusAfterHide | ||
* @private | ||
*/ | ||
_bindUIFocusAfterHide: function() { | ||
// Detach the old handles first. | ||
new Y.EventHandle(this.uiHandleFocusAfterHide).detach(); | ||
this.uiHandleFocusAfterHide = [ | ||
this.after('visibleChange', this._afterHostVisibleChangeFocusAfterHide) | ||
]; | ||
}, | ||
|
||
/** | ||
* Handle the change in UI visibility. | ||
* | ||
* This method changes the focus after the hide has taken place. | ||
* | ||
* @method _afterHostVisibleChangeFocusAfterHide | ||
* @private | ||
*/ | ||
_afterHostVisibleChangeFocusAfterHide: function() { | ||
if (!this.get('visible')) { | ||
if (this._attemptFocus(this._previousTargetFocusAfterHide)) { | ||
|
||
} else if (this._attemptFocus(this.get('focusAfterHide'))) { | ||
// Fall back to the focusAfterHide value if one was specified. | ||
|
||
} else { | ||
|
||
} | ||
} | ||
}, | ||
|
||
_attemptFocus: function(node) { | ||
var focusTarget = Y.one(node); | ||
if (focusTarget) { | ||
focusTarget = focusTarget.ancestor(CAN_RECEIVE_FOCUS_SELECTOR, true); | ||
if (focusTarget) { | ||
focusTarget.focus(); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
}; | ||
|
||
var NS = Y.namespace('M.core'); | ||
NS.WidgetFocusAfterHide = WidgetFocusAfterHide; | ||
|
||
|
||
}, '@VERSION@', {"requires": ["base-build", "widget"]}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "moodle-core-widget-focusafterclose", | ||
"builds": { | ||
"moodle-core-widget-focusafterclose": { | ||
"jsfiles": [ | ||
"focusafter.js" | ||
] | ||
} | ||
} | ||
} |
Oops, something went wrong.