mirror of
https://github.com/moodle/moodle.git
synced 2025-04-15 13:33:52 +02:00
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.
This commit is contained in:
parent
dea614e493
commit
7ae6ce009e
176
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-debug.js
vendored
Normal file
176
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-debug.js
vendored
Normal file
@ -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
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-min.js
vendored
Normal file
1
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
YUI.add("moodle-core-widget-focusafterclose",function(e,t){function r(){e.after(this._bindUIFocusAfterHide,this,"bindUI"),this.get("rendered")&&this._bindUIFocusAfterHide()}var n='input:not([type="hidden"]), a[href], button, textarea, select, [tabindex], [contenteditable="true"]';r.ATTRS={focusOnPreviousTargetAfterHide:{value:!1},focusAfterHide:{value:null,type:e.Node}},r.prototype={_uiHandlesFocusAfterHide:[],_showFocusAfterHide:null,_previousTargetFocusAfterHide:null,initializer:function(){this.get("focusOnPreviousTargetAfterHide")&&this.show&&(this._showFocusAfterHide=this.show,this.show=function(e){this._showFocusAfterHide.apply(this,arguments),this._previousTargetFocusAfterHide=null,e&&e.currentTarget&&(this._previousTargetFocusAfterHide=e.currentTarget)})},destructor:function(){(new e.EventHandle(this.uiHandleFocusAfterHide)).detach()},_bindUIFocusAfterHide:function(){(new e.EventHandle(this.uiHandleFocusAfterHide)).detach(),this.uiHandleFocusAfterHide=[this.after("visibleChange",this._afterHostVisibleChangeFocusAfterHide)]},_afterHostVisibleChangeFocusAfterHide:function(){this.get("visible")||this._attemptFocus(this._previousTargetFocusAfterHide)||this._attemptFocus(this.get("focusAfterHide"))},_attemptFocus:function(t){var r=e.one(t);if(r){r=r.ancestor(n,!0);if(r)return r.focus(),!0}return!1}};var i=e.namespace("M.core");i.WidgetFocusAfterHide=r},"@VERSION@",{requires:["base-build","widget"]});
|
168
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose.js
vendored
Normal file
168
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose.js
vendored
Normal file
@ -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"]});
|
10
lib/yui/src/widget-focusafterclose/build.json
Normal file
10
lib/yui/src/widget-focusafterclose/build.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "moodle-core-widget-focusafterclose",
|
||||
"builds": {
|
||||
"moodle-core-widget-focusafterclose": {
|
||||
"jsfiles": [
|
||||
"focusafter.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
171
lib/yui/src/widget-focusafterclose/js/focusafter.js
vendored
Normal file
171
lib/yui/src/widget-focusafterclose/js/focusafter.js
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 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;
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"moodle-core-widget-focusafterclose": {
|
||||
"requires": [
|
||||
"base-build",
|
||||
"widget"
|
||||
]
|
||||
}
|
||||
}
|
@ -223,6 +223,7 @@ while (count($parts)) {
|
||||
$yuimodules = array(
|
||||
'core/tooltip/tooltip',
|
||||
'core/popuphelp/popuphelp',
|
||||
'core/widget-focusafterclose/widget-focusafterclose',
|
||||
'core/dock/dock-loader',
|
||||
'core/notification/notification-dialogue',
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user