(function ($) { AblePlayer.prototype.initDragDrop = function ( which ) { // supported values of which: 'sign', 'transcript' // NOTE: "Drag and Drop" for Able Player is a metaphor only!!! // HTML5 Drag & Drop API enables moving elements to new locations in the DOM // Thats not our purpose; we're simply changing the visible position on-screen // Therefore, the drag & drop interface was overhauled in v2.3.41 to simple // use mouse (and keyboard) events to change CSS positioning properties // There are nevertheless lessons to be learned from Drag & Drop about accessibility: // http://dev.opera.com/articles/accessible-drag-and-drop/ var thisObj, $window, $toolbar, windowName, $resizeHandle, resizeZIndex; thisObj = this; if (which === 'transcript') { $window = this.$transcriptArea; windowName = 'transcript-window'; $toolbar = this.$transcriptToolbar; } else if (which === 'sign') { $window = this.$signWindow; windowName = 'sign-window'; $toolbar = this.$signToolbar; } // add class to trigger change in cursor on hover $toolbar.addClass('able-draggable'); // add resize handle selector to bottom right corner $resizeHandle = $('
',{ 'class': 'able-resizable' }); // assign z-index that's slightly higher than parent window resizeZIndex = parseInt($window.css('z-index')) + 100; $resizeHandle.css('z-index',resizeZIndex); $window.append($resizeHandle); // add event listener to toolbar to start and end drag // other event listeners will be added when drag starts $toolbar.on('mousedown', function(event) { event.stopPropagation(); if (!thisObj.windowMenuClickRegistered) { thisObj.windowMenuClickRegistered = true; thisObj.startMouseX = event.pageX; thisObj.startMouseY = event.pageY; thisObj.dragDevice = 'mouse'; thisObj.startDrag(which, $window); } return false; }); $toolbar.on('mouseup', function(event) { event.stopPropagation(); if (thisObj.dragging && thisObj.dragDevice === 'mouse') { thisObj.endDrag(which); } return false; }); // add event listeners for resizing $resizeHandle.on('mousedown', function(event) { event.stopPropagation(); if (!thisObj.windowMenuClickRegistered) { thisObj.windowMenuClickRegistered = true; thisObj.startMouseX = event.pageX; thisObj.startMouseY = event.pageY; thisObj.startResize(which, $window); return false; } }); $resizeHandle.on('mouseup', function(event) { event.stopPropagation(); if (thisObj.resizing) { thisObj.endResize(which); } return false; }); // whenever a window is clicked, bring it to the foreground $window.on('click', function() { if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { thisObj.updateZIndex(which); } thisObj.finishingDrag = false; }); this.addWindowMenu(which,$window,windowName); }; AblePlayer.prototype.addWindowMenu = function(which, $window, windowName) { var thisObj, $windowAlert, $newButton, $buttonIcon, buttonImgSrc, $buttonImg, $buttonLabel, tooltipId, $tooltip, $popup, label, position, buttonHeight, buttonWidth, tooltipY, tooltipX, tooltipStyle, tooltip, $optionList, radioName, options, i, $optionItem, option, radioId, $radioButton, $radioLabel; thisObj = this; // Add a Boolean that will be set to true temporarily if window button or a menu item is clicked // This will prevent the click event from also triggering a mousedown event on the toolbar // (which would unexpectedly send the window into drag mode) this.windowMenuClickRegistered = false; // Add another Boolean that will be set to true temporarily when mouseup fires at the end of a drag // this will prevent the click event from being triggered this.finishingDrag = false; // create an alert div and add it to window $windowAlert = $('
'); $windowAlert.addClass('able-alert'); $windowAlert.appendTo(this.$activeWindow); $windowAlert.css({ top: $window.offset().top }); // add button to draggable window which triggers a popup menu // for now, re-use preferences icon for this purpose $newButton = $(''); $cancelButton = $(''); $saveButton.on('click',function () { newWidth = $('#' + widthId).val(); newHeight = $('#' + heightId).val(); if (newWidth !== startingWidth || newHeight !== startingHeight) { $window.css({ 'width': newWidth + 'px', 'height': newHeight + 'px' }); thisObj.updateCookie(which); } resizeDialog.hide(); $windowPopup.hide(); $windowButton.focus(); }); $cancelButton.on('click',function () { resizeDialog.hide(); $windowPopup.hide(); $windowButton.focus(); }); // Now assemble all the parts $resizeWidthDiv.append($resizeWidthLabel,$resizeWidthInput); $resizeHeightDiv.append($resizeHeightLabel,$resizeHeightInput); $resizeWrapper.append($resizeWidthDiv,$resizeHeightDiv); $resizeForm.append($resizeWrapper,'
',$saveButton,$cancelButton); // 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, 'alert', this.tt.windowResizeHeading, $resizeWrapper, this.tt.closeButtonLabel, '20em'); 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 (e.type === 'keydown') { // user pressed a key if (e.which === 32 || e.which === 13 || e.which === 27) { // this was Enter, space, or escape this.windowMenuClickRegistered = true; } else { return false; } } else { // this was a mouse event this.windowMenuClickRegistered = true; } 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 ($windowPopup.is(':visible')) { $windowPopup.hide(200,'',function() { thisObj.windowMenuClickRegistered = false; // reset }); $windowPopup.find('li').removeClass('able-focus'); $windowButton.focus(); } else { // first, be sure window is on top this.updateZIndex(which); popupTop = $windowButton.position().top + $windowButton.outerHeight(); $windowPopup.css('top', popupTop); $windowPopup.show(200,'',function() { $(this).find('input').first().focus().parent().addClass('able-focus'); thisObj.windowMenuClickRegistered = false; // reset }); } }; AblePlayer.prototype.handleMenuChoice = function (which, choice, eventType) { var thisObj, $window, $windowPopup, $windowButton, resizeDialog, $thisRadio; 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; } // hide the popup menu, and reset the Boolean $windowPopup.hide('fast', function() { thisObj.windowMenuClickRegistered = false; // reset }); $windowButton.focus(); if (choice === 'move') { if (!this.showedAlert(which)) { this.showAlert(this.tt.windowMoveAlert,which); if (which === 'transcript') { this.showedTranscriptAlert = true; } else if (which === 'sign') { this.showedSignAlert = true; } } if (eventType === 'keydown') { this.dragDevice = 'keyboard'; } else { this.dragDevice = 'mouse'; } this.startDrag(which, $window); $windowPopup.hide().parent().focus(); } else if (choice == 'resize') { // resize through the menu uses a form, not drag resizeDialog.show(); } }; AblePlayer.prototype.startDrag = function(which, $element) { var thisObj, $windowPopup, zIndex, startPos, newX, newY; thisObj = this; this.$activeWindow = $element; this.dragging = true; if (which === 'transcript') { $windowPopup = this.$transcriptPopup; } else if (which === 'sign') { $windowPopup = this.$signPopup; } if (!this.showedAlert(which)) { this.showAlert(this.tt.windowMoveAlert,which); if (which === 'transcript') { this.showedTranscriptAlert = true; } else if (which === 'sign') { this.showedSignAlert = true; } } // 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' }).focus(); // add device-specific event listeners if (this.dragDevice === 'mouse') { $(document).on('mousemove',function(e) { if (thisObj.dragging) { // calculate new top left based on current mouse position - offset newX = e.pageX - thisObj.dragOffsetX; newY = e.pageY - thisObj.dragOffsetY; thisObj.resetDraggedObject( newX, newY ); } }); } else if (this.dragDevice === 'keyboard') { this.$activeWindow.on('keydown',function(e) { if (thisObj.dragging) { thisObj.dragKeys(which, e); } }); } return false; }; AblePlayer.prototype.dragKeys = function(which, e) { var key, keySpeed; var thisObj = this; // stopgap to prevent firing on initial Enter or space // that selected "Move" from menu if (this.startingDrag) { this.startingDrag = false; return false; } key = e.which; keySpeed = 10; // pixels per keypress event switch (key) { case 37: // left case 63234: this.dragKeyX -= keySpeed; break; case 38: // up case 63232: this.dragKeyY -= keySpeed; break; case 39: // right case 63235: this.dragKeyX += keySpeed; break; case 40: // down case 63233: this.dragKeyY += keySpeed; break; case 13: // enter case 27: // escape 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) { 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 proporitionally // (it's 50px less than its outer container) innerHeight = height - 50; this.$transcriptDiv.css('height', innerHeight + 'px'); } }; AblePlayer.prototype.endDrag = function(which) { var $window, $windowPopup, $windowButton; if (which === 'transcript') { $windowPopup = this.$transcriptPopup; $windowButton = this.$transcriptPopupButton; } else if (which === 'sign') { $windowPopup = this.$signPopup; $windowButton = this.$signPopupButton; } $(document).off('mousemove mouseup'); this.$activeWindow.off('keydown').removeClass('able-drag'); if (this.dragDevice === 'keyboard') { $windowButton.focus(); } this.dragging = false; // save final position of dragged element this.updateCookie(which); // reset starting mouse positions this.startMouseX = undefined; this.startMouseY = undefined; // Boolean to stop stray events from firing this.windowMenuClickRegistered = false; this.finishingDrag = true; // will be reset after window click event // finishingDrag should e reset after window click event, // which is triggered automatically after mouseup // However, in case that's not reliable in some browsers // need to ensure this gets cancelled setTimeout(function() { this.finishingDrag = false; }, 100); }; AblePlayer.prototype.isCloseToCorner = function($window, mouseX, mouseY) { // return true if mouse is close to bottom right corner (resize target) var tolerance, position, top, left, width, height, bottom, right; tolerance = 10; // number of pixels in both directions considered "close enough" // first, get position of element position = $window.offset(); top = position.top; left = position.left; width = $window.width(); height = $window.height(); bottom = top + height; right = left + width; if ((Math.abs(bottom-mouseY) <= tolerance) && (Math.abs(right-mouseX) <= tolerance)) { return true; } return false; }; AblePlayer.prototype.startResize = function(which, $element) { var thisObj, $windowPopup, zIndex, startPos, newWidth, newHeight; thisObj = this; this.$activeWindow = $element; this.resizing = true; if (which === 'transcript') { $windowPopup = this.$transcriptPopup; } else if (which === 'sign') { $windowPopup = this.$signPopup; } // if window's popup menu is open, close it & place focus on button (???) if ($windowPopup.is(':visible')) { $windowPopup.hide().parent().focus(); } // get starting width and height startPos = this.$activeWindow.position(); this.dragKeyX = this.dragStartX; this.dragKeyY = this.dragStartY; this.dragStartWidth = this.$activeWindow.width(); this.dragStartHeight = this.$activeWindow.height(); // add event listeners $(document).on('mousemove',function(e) { if (thisObj.resizing) { // calculate new width and height based on changes to mouse position newWidth = thisObj.dragStartWidth + (e.pageX - thisObj.startMouseX); newHeight = thisObj.dragStartHeight + (e.pageY - thisObj.startMouseY); thisObj.resizeObject( which, newWidth, newHeight ); } }); return false; }; AblePlayer.prototype.endResize = function(which) { var $window, $windowPopup, $windowButton; if (which === 'transcript') { $windowPopup = this.$transcriptPopup; $windowButton = this.$transcriptPopupButton; } else if (which === 'sign') { $windowPopup = this.$signPopup; $windowButton = this.$signPopupButton; } $(document).off('mousemove mouseup'); this.$activeWindow.off('keydown'); $windowButton.show().focus(); this.resizing = false; this.$activeWindow.removeClass('able-resize'); // save final width and height of dragged element this.updateCookie(which); // Booleans for preventing stray events this.windowMenuClickRegistered = false; this.finishingDrag = true; // finishingDrag should e reset after window click event, // which is triggered automatically after mouseup // However, in case that's not reliable in some browsers // need to ensure this gets cancelled setTimeout(function() { this.finishingDrag = false; }, 100); }; })(jQuery);