Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,38 @@
$('.speeds').mouseenter().click();
expect($('.speeds')).not.toHaveClass('open');
});
// Tabbing depends on the following order:
// 1. Play anchor
// 2. Speed anchor
// 3. A number of speed entry anchors
// 4. Volume anchor
// If an other focusable element is inserted or if the order is changed, things will
// malfunction as a flag, state.previousFocus, is set in the 1,3,4 elements and is
// used to determine the behavior of foucus() and blur() for the speed anchor.
it('checks for a certain order in focusable elements in video controls', function() {
var playIndex, speedIndex, firstSpeedEntry, lastSpeedEntry, volumeIndex, foundFirst = false;
$('.video-controls').find('a, :focusable').each(function(index) {
if ($(this).hasClass('video_control')) {
playIndex = index;
}
else if ($(this).parent().hasClass('speeds')) {
speedIndex = index;
}
else if ($(this).hasClass('speed_link')) {
if (!foundFirst) {
firstSpeedEntry = index;
foundFirst = true;
}
lastSpeedEntry = index;
}
else if ($(this).parent().hasClass('volume')) {
volumeIndex = index;
}
});
expect(playIndex+1).toEqual(speedIndex);
expect(speedIndex+1).toEqual(firstSpeedEntry);
expect(lastSpeedEntry+1).toEqual(volumeIndex);
});
});
});

Expand Down
5 changes: 5 additions & 0 deletions common/lib/xmodule/xmodule/js/src/video/04_video_control.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ function () {
state.el.on('mousemove', state.videoControl.showControls);
state.el.on('keydown', state.videoControl.showControls);
}
// The state.previousFocus is used in video_speed_control to track
// the element that had the focus before it.
state.videoControl.playPauseEl.on('blur', function () {
state.previousFocus = 'playPause';
});
}

// ***************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ function () {
// We store the fact that previous element that lost focus was
// the volume clontrol.
state.volumeBlur = true;
// The following field is used in video_speed_control to track
// the element that had the focus before it.
state.previousFocus = 'volume';
});
}

Expand Down
112 changes: 68 additions & 44 deletions common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,68 +154,92 @@ function () {
});

// ******************************
// Attach 'focus', and 'blur' events to the speed button which
// The tabbing will cycle through the elements in the following
// order:
// 1. Play control
// 2. Speed control
// 3. Fastest speed called firstSpeed
// 4. Intermediary speed called otherSpeed
// 5. Slowest speed called lastSpeed
// 6. Volume control
// This field will keep track of where the focus is coming from.
state.previousFocus = '';

// ******************************
// Attach 'focus', and 'blur' events to the speed control which
// either brings up the speed dialog with individual speed entries,
// or closes it.
state.videoSpeedControl.el.children('a')
.on('focus', function () {
// If the focus is comming from the first speed entry, this
// means we are tabbing backwards. In this case we have to
// hide the speed entries which will allow us to change the
// focus further backwards.
if (state.firstSpeedBlur === true) {
state.videoSpeedControl.el.removeClass('open');

state.firstSpeedBlur = false;
}

// If the focus is comming from some other element, show
// the drop down with the speed entries.
else {
state.videoSpeedControl.el.addClass('open');
// If the focus is coming from the first speed entry
// (tabbing backwards) or last speed entry (tabbing forward)
// hide the speed entries dialog.
if (state.previousFocus === 'firstSpeed' ||
state.previousFocus === 'lastSpeed') {
state.videoSpeedControl.el.removeClass('open');
}
})
.on('blur', function () {
// When the focus leaves this element, if the speed entries
// dialog is shown (tabbing forwards), then we will set
// focus to the first speed entry.
//
// If the selector does not select anything, then this
// means that the speed entries dialog is closed, and we
// are tabbing backwads. The browser will select the
// previous element to tab to by itself.
state.videoSpeedControl.videoSpeedsEl
// When the focus leaves this element, the speed entries
// dialog will be shown.

// If we are tabbing forward (previous focus is play
// control), we open the dialog and set focus on the first
// speed entry.
if (state.previousFocus === 'playPause') {
state.videoSpeedControl.el.addClass('open');
state.videoSpeedControl.videoSpeedsEl
.find('a.speed_link:first')
.focus();
});
}

// If we are tabbing backwards (previous focus is volume
// control), we open the dialog and set focus on the
// last speed entry.
if (state.previousFocus === 'volume') {
state.videoSpeedControl.el.addClass('open');
state.videoSpeedControl.videoSpeedsEl
.find('a.speed_link:last')
.focus();
}

});

// ******************************
// Attach 'focus', and 'blur' events to elements which represent
// individual speed entries.
// Attach 'blur' event to elements which represent individual speed
// entries and use it to track the origin of the focus.
speedLinks = state.videoSpeedControl.videoSpeedsEl
.find('a.speed_link');

speedLinks.last().on('blur', function () {
// If we have reached the last speed entry, and the focus
// changes to the next element, we need to hide the speeds
// control drop-down.
state.videoSpeedControl.el.removeClass('open');
});
speedLinks.first().on('blur', function () {
// This flag will indicate that the focus to the next
// element that will receive it is comming from the first
// speed entry.
//
// This flag will be used to correctly handle scenario of
// tabbing backwards.
state.firstSpeedBlur = true;
// The previous focus is a speed entry (we are tabbing
// backwards), the dialog will close, set focus on the speed
// control and track the focus on first speed.
if (state.previousFocus === 'otherSpeed') {
state.previousFocus = 'firstSpeed';
state.videoSpeedControl.el.children('a').focus();
}
});
speedLinks.on('focus', function () {
// Clear the flag which is only set when we are un-focusing
// (the blur event) from the first speed entry.
state.firstSpeedBlur = false;

// Track the focus on intermediary speeds.
speedLinks
.filter(function (index) {
return index === 1 || index === 2
})
.on('blur', function () {
state.previousFocus = 'otherSpeed';
});

speedLinks.last().on('blur', function () {
// The previous focus is a speed entry (we are tabbing forward),
// the dialog will close, set focus on the speed control and
// track the focus on last speed.
if (state.previousFocus === 'otherSpeed') {
state.previousFocus = 'lastSpeed';
state.videoSpeedControl.el.children('a').focus();
}
});

}
}

Expand Down