Completed main functionality. Ready for code review.

Now when controls are uatohidden and focus is on one of them, we
switch the focus to the mian Video element.

Code documented a bit.

When controls are autohidden, we make sure that speeds and volume
controls drop downs are closed.
This commit is contained in:
Valera Rozuvan
2013-09-10 14:58:02 +03:00
parent 28888bda5d
commit 0e850aff52
3 changed files with 70 additions and 33 deletions

View File

@@ -1,3 +1,30 @@
/*
* 025_focus_grabber.js
*
* Purpose: Provide a way to focus on autohidden Video controls.
*
*
* Because in HTML player mode we have a feature of autohiding controls on
* mouse inactivity, sometimes focus is lost from the currently selected
* control. What's more, when all controls are autohidden, we can't get to any
* of them because by default browser does not place hidden elements on the
* focus chain.
*
* To get around this minor annoyance, this module will manage 2 placeholder
* elements that will be invisible to the user's eye, but visible to the
* browser. This will allow for a sneaky stealing of focus and placing it where
* we need (on hidden controls).
*
* This code has been moved to a separate module because it provides a concrete
* block of functionality that can be turned on (off).
*/
/*
* "If you want to climb a mountain, begin at the top."
*
* ~ Zen saying
*/
(function (requirejs, require, define) {
// FocusGrabber module.
@@ -19,14 +46,15 @@ function () {
function _makeFunctionsPublic(state) {
state.focusGrabber.enableFocusGrabber = _.bind(enableFocusGrabber, state);
state.focusGrabber.disableFocusGrabber = _.bind(disableFocusGrabber, state);
state.focusGrabber.onFocus = _.bind(onFocus, state);
state.focusGrabber.onBlur = _.bind(onBlur, state);
}
function _renderElements(state) {
state.focusGrabber.elFirst = state.el.find('.focus_grabber.first');
state.focusGrabber.elLast = state.el.find('.focus_grabber.last');
// From the start, the Focus Grabber must be disabled so that
// tabbing (switching focus) does not land the user on one of the
// placeholder elements (elFirst, elLast).
state.focusGrabber.disableFocusGrabber();
}
@@ -34,8 +62,12 @@ function () {
state.focusGrabber.elFirst.on('focus', state.focusGrabber.onFocus);
state.focusGrabber.elLast.on('focus', state.focusGrabber.onFocus);
state.focusGrabber.elFirst.on('blur', state.focusGrabber.onBlur);
state.focusGrabber.elLast.on('blur', state.focusGrabber.onBlur);
// When the video container element receives programmatic focus, then
// on un-focus ('blur' event) we should trigger a 'mousemove' event so
// as to reveal autohidden controls.
state.el.on('blur', function () {
state.el.trigger('mousemove');
});
}
@@ -44,6 +76,14 @@ function () {
function enableFocusGrabber() {
var tabIndex;
// When the Focus Grabber is being enabled, there are two different
// scenarios:
//
// 1.) Currently focused element was inside the video player.
// 2.) Currently focused element was somewhere else on the page.
//
// In the first case we must make sure that the video player doesn't
// loose focus, even though the cfontrols are uatohidden.
if ($(document.activeElement).parents().hasClass('video')) {
tabIndex = -1;
} else {
@@ -53,48 +93,36 @@ function () {
this.focusGrabber.elFirst.attr('tabindex', tabIndex);
this.focusGrabber.elLast.attr('tabindex', tabIndex);
$(document.activeElement).blur();
// Don't loose focus. We are inside video player on some control, but
// because we can't remain focused on a hidden element, we will shift
// focus to the main video element.
//
// Once the main element will receive the un-focus ('blur') event, a
// 'mousemove' event will be triggered, and the video controls will
// receive focus once again.
if (tabIndex === -1) {
this.focusGrabber.elFirst.trigger(
'focus',
{
simpleFocus: true
}
);
this.el.focus();
this.focusGrabber.elFirst.attr('tabindex', 0);
this.focusGrabber.elLast.attr('tabindex', 0);
}
}
function disableFocusGrabber() {
// Only programmatic focusing on these elements will be available.
// We don't want the user to focus on them (for example with the 'Tab'
// key).
this.focusGrabber.elFirst.attr('tabindex', -1);
this.focusGrabber.elLast.attr('tabindex', -1);
}
function onFocus(event, params) {
if (params && params.simpleFocus) {
this.focusGrabber.elFirst.attr('tabindex', 0);
this.focusGrabber.elLast.attr('tabindex', 0);
return;
}
// Once the Focus Grabber placeholder elements will gain focus, we will
// trigger 'mousemove' event so that the autohidden controls will
// become visible.
this.el.trigger('mousemove');
this.el.trigger('focus');
$('html, body').animate({
scrollTop: this.el.offset().top
}, 200);
this.focusGrabber.disableFocusGrabber();
}
function onBlur(event) {
this.el.trigger('mousemove');
this.el.trigger('focus');
$('html, body').animate({
scrollTop: this.el.offset().top
}, 200);
}
});
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));

View File

@@ -129,6 +129,13 @@ function () {
this.videoControl.el.fadeOut(this.videoControl.fadeOutTimeout, function () {
_this.controlState = 'invisible';
// If the focus was on the video control or the volume control,
// then we must make sure to close these dialogs. Otherwise, after
// next autofocus, these dialogs will be open, but the focus will
// not be on them.
_this.videoVolumeControl.el.removeClass('open');
_this.videoSpeedControl.el.removeClass('open');
_this.focusGrabber.enableFocusGrabber();
});
}

View File

@@ -25,6 +25,8 @@
data-autoplay="${autoplay}"
data-yt-test-timeout="${yt_test_timeout}"
data-yt-test-url="${yt_test_url}"
tabindex="-1"
>
<div class="focus_grabber first"></div>