', {
'class': 'able-tooltip',
'id': tooltipId
}).hide();
$newButton.on('mouseenter focus', function (e) {
var label = $(this).attr('aria-label');
var tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(label);
// get height of the tooltip
var tooltipHeight = tooltip.height();
var tooltipY = (tooltipHeight + 2) * -1;
var tooltipX = 0;
var tooltipStyle = {
right: '',
left: tooltipX + 'px',
top: tooltipY + 'px'
};
tooltip.css(tooltipStyle);
thisObj.showTooltip(tooltip);
$(this).on('mouseleave blur', function () {
AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide();
});
});
// setup popup menu
$popup = this.setupPopups(windowName); // 'transcript-window' or 'sign-window'
// define vars and assemble all the parts
if (which === 'transcript') {
this.$transcriptPopupButton = $newButton;
this.$transcriptPopup = $popup;
this.$transcriptToolbar.prepend($newButton, $tooltip, $popup);
} else if (which === 'sign') {
this.$signPopupButton = $newButton;
this.$signPopup = $popup;
this.$signToolbar.append($newButton, $tooltip, $popup);
}
// handle button key activation and click separately
$newButton.on('keydown', function (e) {
if (thisObj.focusNotClick) {
return false;
}
if (thisObj.dragging) {
thisObj.dragKeys(which, e);
return false;
}
if (e.key !== ' ' && e.key !== 'Enter' && e.key !== 'Escape') {
return false;
}
e.stopPropagation();
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
}
if (!thisObj.finishingDrag) {
thisObj.handleWindowButtonClick(which, e);
}
thisObj.finishingDrag = false;
return false;
});
$newButton.on('click', function (e) {
if (thisObj.focusNotClick) {
return false;
}
if (thisObj.dragging) {
return false;
}
e.stopPropagation();
if (!thisObj.finishingDrag) {
thisObj.handleWindowButtonClick(which, e);
}
thisObj.finishingDrag = false;
return false;
});
this.addResizeDialog(which, $window);
};
AblePlayer.prototype.addResizeDialog = function (which) {
var thisObj, $windowPopup, $windowButton, widthId, heightId,
$resizeForm, $resizeWrapper, $resizeControls, $resizeWidthDiv, $resizeWidthInput, $resizeWidthLabel,
$resizeHeightDiv, $resizeHeightInput, $resizeHeightLabel, $saveButton, $cancelButton,
newWidth, newHeight, resizeDialog;
thisObj = this;
if (which === 'transcript') {
$windowPopup = this.$transcriptPopup;
$windowButton = this.$transcriptPopupButton;
} else if (which === 'sign') {
$windowPopup = this.$signPopup;
$windowButton = this.$signPopupButton;
}
widthId = this.mediaId + '-resize-' + which + '-width';
heightId = this.mediaId + '-resize-' + which + '-height';
$resizeForm = $('
', {
'class': 'able-resize-form'
});
// inner container for all content, will be assigned to modal div's aria-describedby
$resizeWrapper = $('
');
$resizeControls = $('
');
// width field
$resizeWidthDiv = $('
');
$resizeWidthInput = $('
', {
'type': 'number',
'id': widthId,
'min': 0,
'value': '',
});
$resizeWidthLabel = $('
', {
'for': widthId
}).text(this.translate('width', 'Width'));
// height field
$resizeHeightDiv = $('
');
$resizeHeightInput = $(' ', {
'type': 'number',
'id': heightId,
'min': 0,
'value': '',
});
$resizeHeightLabel = $('', {
'for': heightId
}).text(this.translate('height', 'Height'));
// Add save and cancel buttons.
$saveButton = $('' + this.translate('save', 'Save') + ' ');
$cancelButton = $('' + this.translate('cancel', 'Cancel') + ' ');
$saveButton.on('click', function () {
newWidth = $('#' + widthId).val();
newHeight = $('#' + heightId).val();
thisObj.resizeObject(which, newWidth, newHeight);
thisObj.updatePreferences(which);
resizeDialog.hide();
$windowPopup.hide();
$windowButton.trigger('focus');
});
$cancelButton.on('click', function () {
resizeDialog.hide();
$windowPopup.hide();
$windowButton.trigger('focus');
});
// Now assemble all the parts
$resizeWidthDiv.append($resizeWidthLabel, $resizeWidthInput);
$resizeHeightDiv.append($resizeHeightLabel, $resizeHeightInput);
$resizeWrapper.append($resizeWidthDiv, $resizeHeightDiv);
$resizeControls.append($saveButton, $cancelButton);
$resizeForm.append($resizeWrapper, $resizeControls);
// must be appended to the BODY!
// otherwise when aria-hidden="true" is applied to all background content
// that will include an ancestor of the dialog,
// which will render the dialog unreadable by screen readers
$('body').append($resizeForm);
resizeDialog = new AccessibleDialog(
$resizeForm,
$windowButton,
this.translate('windowResizeHeading', 'Resize Window'),
this.translate('closeButtonLabel', 'Close'),
);
if (which === 'transcript') {
this.transcriptResizeDialog = resizeDialog;
} else if (which === 'sign') {
this.signResizeDialog = resizeDialog;
}
};
AblePlayer.prototype.handleWindowButtonClick = function (which, e) {
var thisObj, $windowPopup, $windowButton, $toolbar, popupTop;
thisObj = this;
if (this.focusNotClick) {
// transcript or sign window has just opened,
// and focus moved to the window button
// ignore the keystroke that triggered the popup
return false;
}
if (which === 'transcript') {
$windowPopup = this.$transcriptPopup;
$windowButton = this.$transcriptPopupButton;
$toolbar = this.$transcriptToolbar;
} else if (which === 'sign') {
$windowPopup = this.$signPopup;
$windowButton = this.$signPopupButton;
$toolbar = this.$signToolbar;
}
if (e.type === 'keydown') {
// user pressed a key
if (e.key === ' ' || e.key === 'Enter') {
} else if (e.key === 'Escape') {
if ($windowPopup.is(':visible')) {
// close the popup menu
$windowPopup.hide();
// also restore menu items to their original state
$windowPopup.find('li').removeClass('able-focus').attr('tabindex', '-1');
$windowButton.attr('aria-expanded', 'false');
// also return focus to window options button
$windowButton.trigger('focus');
} else {
// popup isn't open. Close the window
if (which === 'sign') {
this.handleSignToggle();
} else if (which === 'transcript') {
this.handleTranscriptToggle();
}
}
return false;
} else {
return false;
}
}
if ($windowPopup.is(':visible')) {
$windowPopup.hide();
$windowPopup.find('li').removeClass('able-focus');
$windowButton.attr('aria-expanded', 'false').trigger('focus');
} else {
// first, be sure window is on top
this.updateZIndex(which);
popupTop = $toolbar.outerHeight() - 1;
$windowPopup.css('top', popupTop);
$windowPopup.show();
$windowButton.attr('aria-expanded', 'true');
$windowPopup.find('li').first().attr('tabindex', '0').trigger('focus').addClass('able-focus');
}
};
AblePlayer.prototype.handleMenuChoice = function (which, choice, e) {
var thisObj, $window, $windowPopup, $windowButton, resizeDialog, startingWidth, startingHeight,
aspectRatio, tempWidth, tempHeight;
thisObj = this;
if (which === 'transcript') {
$window = this.$transcriptArea;
$windowPopup = this.$transcriptPopup;
$windowButton = this.$transcriptPopupButton;
resizeDialog = this.transcriptResizeDialog;
} else if (which === 'sign') {
$window = this.$signWindow;
$windowPopup = this.$signPopup;
$windowButton = this.$signPopupButton;
resizeDialog = this.signResizeDialog;
startingWidth = $window.outerWidth();
startingHeight = $window.outerHeight();
aspectRatio = startingWidth / startingHeight;
// make height a read-only field
// and calculate its value based on width to preserve aspect ratio
let widthId = this.mediaId + '-resize-' + which + '-width';
let heightId = this.mediaId + '-resize-' + which + '-height';
$('#' + heightId).prop('readonly', true);
$('#' + widthId).on('input', function () {
tempWidth = $(this).val();
tempHeight = Math.round(tempWidth / aspectRatio);
$('#' + heightId).val(tempHeight);
});
}
this.$activeWindow = $window;
if (e.type === 'keydown') {
if (e.key === 'Escape') { // escape
// hide the popup menu
$windowPopup.hide();
// also restore menu items to their original state
$windowPopup.find('li').removeClass('able-focus').attr('tabindex', '-1');
$windowButton.attr('aria-expanded', 'false');
// also return focus to window options button
$windowButton.trigger('focus');
return false;
} else {
// all other keys will be handled by upstream functions
if (choice !== 'close') {
this.$activeWindow = $window;
}
return false;
}
}
// hide the popup menu
$windowPopup.hide();
// also restore menu items to their original state
$windowPopup.find('li').removeClass('able-focus').attr('tabindex', '-1');
$windowButton.attr('aria-expanded', 'false');
if (choice !== 'close') {
$windowButton.trigger('focus');
}
if (choice === 'move') {
// temporarily add role="application" to activeWindow
// otherwise, screen readers incercept arrow keys and moving window will not work
this.$activeWindow.attr('role', 'application');
if (!this.showedAlert(which)) {
this.showAlert(this.translate('windowMoveAlert', 'Drag or use arrow keys to move the window; Enter to stop'), which);
if (which === 'transcript') {
this.showedTranscriptAlert = true;
} else if (which === 'sign') {
this.showedSignAlert = true;
}
}
this.dragDevice = (e.type === 'keydown') ? 'keyboard' : 'mouse';
this.startDrag(which, $window);
$windowPopup.hide().parent().attr('tabindex', '-1').trigger('focus');
} else if (choice == 'resize') {
// resize through the menu uses a form, not drag
var resizeFields = resizeDialog.getInputs();
if (resizeFields) {
// reset width and height values in form
resizeFields[0].value = Math.round($window.outerWidth());
resizeFields[1].value = Math.round($window.outerHeight());
}
resizeDialog.show();
} else if (choice == 'close') {
// close window, place focus on corresponding button on controller bar
if (which === 'transcript') {
this.closingTranscript = true; // stopgap to prevent double-firing of keypress
this.handleTranscriptToggle();
} else if (which === 'sign') {
this.closingSign = true; // stopgap to prevent double-firing of keypress
this.handleSignToggle();
}
}
};
AblePlayer.prototype.startDrag = function (which, $element) {
var thisObj, $windowPopup, startPos, newX, newY;
thisObj = this;
if (!this.$activeWindow) {
this.$activeWindow = $element;
}
this.dragging = true;
if (which === 'transcript') {
$windowPopup = this.$transcriptPopup;
} else if (which === 'sign') {
$windowPopup = this.$signPopup;
}
// if window's popup menu is open, close it
if ($windowPopup.is(':visible')) {
$windowPopup.hide();
}
// be sure this window is on top
this.updateZIndex(which);
// get starting position of element
startPos = this.$activeWindow.position();
this.dragStartX = startPos.left;
this.dragStartY = startPos.top;
if (typeof this.startMouseX === 'undefined') {
this.dragDevice = 'keyboard';
this.dragKeyX = this.dragStartX;
this.dragKeyY = this.dragStartY;
// add stopgap to prevent the Enter that triggered startDrag() from also triggering dragEnd()
this.startingDrag = true;
} else {
this.dragDevice = 'mouse';
// get offset between mouse position and top left corner of draggable element
this.dragOffsetX = this.startMouseX - this.dragStartX;
this.dragOffsetY = this.startMouseY - this.dragStartY;
}
// prepare element for dragging
this.$activeWindow.addClass('able-drag').css({
'position': 'absolute',
'top': this.dragStartY + 'px',
'left': this.dragStartX + 'px'
}).trigger('focus');
// add device-specific event listeners
if (this.dragDevice === 'mouse') { // pointer input, including mouse/touch/pen
$(window).on('pointermove.ableDrag', function (e) {
if (!thisObj.dragging) {
return;
}
if (typeof thisObj.dragPointerId !== 'undefined' && e.pointerId !== thisObj.dragPointerId) {
return;
}
// calculate new top left based on current pointer position - offset
newX = e.pageX - thisObj.dragOffsetX;
newY = e.pageY - thisObj.dragOffsetY;
thisObj.resetDraggedObject(newX, newY);
});
$(window).on('pointerup.ableDrag pointercancel.ableDrag', function (e) {
if (!thisObj.dragging) {
return;
}
if (typeof thisObj.dragPointerId !== 'undefined' && e.pointerId !== thisObj.dragPointerId) {
return;
}
thisObj.endDrag(which);
});
} else if (this.dragDevice === 'keyboard') {
this.$activeWindow.on('keydown', function (e) {
if (thisObj.dragging) {
thisObj.dragKeys(which, e);
}
});
}
return false;
};
/**
* Handle moving the transcript or sign window from the keyboard.
*
* @param {string} which 'transcript' or 'sign' window.
* @param {Event} e Triggered event.
*/
AblePlayer.prototype.dragKeys = function (which, e) {
var key, keySpeed;
// stopgap to prevent firing on initial Enter or space
// that selected "Move" from menu
if (this.startingDrag) {
this.startingDrag = false;
return false;
}
key = e.key;
keySpeed = 10; // pixels per keypress event
switch (key) {
case 'ArrowLeft': // left
this.dragKeyX -= keySpeed;
this.$srAlertBox.text(this.translate('windowMoveLeft', 'Window moved left'));
break;
case 'ArrowUp': // up
this.dragKeyY -= keySpeed;
this.$srAlertBox.text(this.translate('windowMoveUp', 'Window moved up'));
break;
case 'ArrowRight': // right
this.dragKeyX += keySpeed;
this.$srAlertBox.text(this.translate('windowMoveRight', 'Window moved right'));
break;
case 'ArrowDown': // down
this.dragKeyY += keySpeed;
this.$srAlertBox.text(this.translate('windowMoveDown', 'Window moved down'));
break;
case 'Enter': // enter
case 'Escape': // escape
this.$srAlertBox.text(this.translate('windowMoveStopped', 'Window move stopped'));
this.endDrag(which);
return false;
default:
return false;
}
this.resetDraggedObject(this.dragKeyX, this.dragKeyY);
if (e.preventDefault) {
e.preventDefault();
}
return false;
};
AblePlayer.prototype.resetDraggedObject = function (x, y) {
setTimeout(() => {
this.$srAlertBox.text('');
}, 2000);
this.$activeWindow.css({
'left': x + 'px',
'top': y + 'px'
});
},
AblePlayer.prototype.resizeObject = function (which, width, height) {
var innerHeight;
// which is either 'transcript' or 'sign'
this.$activeWindow.css({
'width': width + 'px',
'height': height + 'px'
});
if (which === 'transcript') {
// $activeWindow is the outer $transcriptArea
// but the inner able-transcript also needs to be resized proportionally
// (it's 50px less than its outer container)
innerHeight = height - 50;
this.$transcriptDiv.css('height', innerHeight + 'px');
}
};
AblePlayer.prototype.endDrag = function (which) {
var thisObj, $windowButton;
thisObj = this;
if (which === 'transcript') {
$windowButton = this.$transcriptPopupButton;
} else if (which === 'sign') {
$windowButton = this.$signPopupButton;
}
$(window).off('.ableDrag');
this.$activeWindow.off('keydown').removeClass('able-drag');
// restore activeWindow role from 'application' to 'dialog'
this.$activeWindow.attr('role', 'dialog');
this.$activeWindow = null;
if (this.dragDevice === 'keyboard') {
$windowButton.trigger('focus');
}
this.dragging = false;
// save final position of dragged element
this.updatePreferences(which);
// reset starting mouse positions
this.startMouseX = undefined;
this.startMouseY = undefined;
this.dragPointerId = undefined;
this.finishingDrag = true; // will be reset after window click event
// finishingDrag should be reset after window click event,
// which is triggered automatically after pointerup
// However, in case that's not reliable in some browsers
// need to ensure this gets cancelled
setTimeout(function () {
thisObj.finishingDrag = false;
}, 100);
};
AblePlayer.prototype.startResize = function (which, $element) {
var thisObj, $windowPopup, newWidth, newHeight;
thisObj = this;
this.$activeWindow = $element;
this.resizing = true;
$windowPopup = (which === 'transcript') ? this.$transcriptPopup : this.$signPopup;
// if window's popup menu is open, close it & place focus on button (???)
if ($windowPopup.is(':visible')) {
$windowPopup.hide().parent().trigger('focus');
}
// get starting width and height
this.dragKeyX = this.dragStartX;
this.dragKeyY = this.dragStartY;
this.dragStartWidth = this.$activeWindow.width();
this.dragStartHeight = this.$activeWindow.outerHeight();
// add event listeners
$(window).on('pointermove.ableResize', function (e) {
if (!thisObj.resizing) {
return;
}
if (typeof thisObj.resizePointerId !== 'undefined' && e.pointerId !== thisObj.resizePointerId) {
return;
}
// calculate new width and height based on changes to pointer position
let aspectRatio = thisObj.dragStartWidth / thisObj.dragStartHeight;
newWidth = thisObj.dragStartWidth + (e.pageX - thisObj.startMouseX);
if ('transcript' === which) {
newHeight = thisObj.dragStartHeight + (e.pageY - thisObj.startMouseY);
} else {
newHeight = thisObj.dragStartHeight + ((e.pageX - thisObj.startMouseX) / aspectRatio);
}
thisObj.resizeObject(which, newWidth, newHeight);
});
$(window).on('pointerup.ableResize pointercancel.ableResize', function (e) {
if (!thisObj.resizing) {
return;
}
if (typeof thisObj.resizePointerId !== 'undefined' && e.pointerId !== thisObj.resizePointerId) {
return;
}
thisObj.endResize(which);
});
return false;
};
AblePlayer.prototype.endResize = function (which) {
var $windowButton;
if (which === 'transcript') {
$windowButton = this.$transcriptPopupButton;
} else if (which === 'sign') {
$windowButton = this.$signPopupButton;
}
$(window).off('.ableResize');
this.$activeWindow.off('keydown');
$windowButton.show().trigger('focus');
this.resizing = false;
this.$activeWindow.removeClass('able-resize');
this.resizePointerId = undefined;
// save final width and height of dragged element
this.updatePreferences(which);
// Boolean for preventing stray click events
this.finishingDrag = true;
// finishingDrag should e reset after window click event,
// which is triggered automatically after pointerup
// However, in case that's not reliable in some browsers
// need to ensure this gets cancelled
setTimeout(function () {
this.finishingDrag = false;
}, 100);
};
}
export default addDragdropFunctions;