%PDF- %PDF-
| Direktori : /proc/self/root/data/old/home/stash/atlassian-stash/static/widget/ |
| Current File : //proc/self/root/data/old/home/stash/atlassian-stash/static/widget/keyboard-controller.js |
define('widget/keyboard-controller', [
'jquery'
], function(
$
) {
'use strict';
/**
*
* @param eventTarget
* @param handlers
* @return {KeyboardController}
* @constructor
*/
function KeyboardController(eventTarget, handlers) {
if (!(this instanceof KeyboardController)) {
return new KeyboardController(eventTarget, handlers);
}
var $eventTarget = $(eventTarget);
var rawHandler;
$eventTarget.on('keydown', rawHandler = function(e) {
if (e.keyCode in handlers) {
if (!handlers[e.keyCode](e)) {
e.preventDefault();
}
}
});
this.destroy = function() {
$eventTarget.off('keydown', rawHandler);
};
return this;
}
/**
* TabKeyboardController overrides native browser tabbing for two reasons:
* 1. to force Safari to tab to anchors just like all our other browsers.
* 2. to allow us to force tabbing to cycle between focusable elements within a given context (such as a dialog).
*
* This is currently only used in the Branch Selector.
*
* NOTE: the selector used in this component is rather aggressive. Please be aware that a large number of descendant
* elements will cause this control to perform poorly.
*
* @param context {Object} an element within which to control tabbing.
* @param options {Object} an object matching the shape of TabKeyboardController.defaults
* @return {KeyboardController}
* @constructor
*/
function TabKeyboardController(context, options) {
options = $.extend({}, TabKeyboardController.defaults, options);
function doFocus($el) {
$el.focus().addClass(options.focusedClass);
}
var handlers = {};
handlers[$.ui.keyCode.TAB] = function (e) {
var $selectable, $first_selectable, $last_selectable, target = e.target;
$selectable = $("a:visible, :input:visible:enabled, :checkbox:visible:enabled, :radio:visible:enabled, [tabindex]", context)
.not("[tabindex=-1]")
.filter(function() {
return $(this).css('visibility') !== 'hidden';
});
$last_selectable = $selectable.last();
$first_selectable = $selectable.first();
$selectable.removeClass(options.focusedClass);
if (e.shiftKey && target === $first_selectable[0]) {
if (!options.wrapAround) {
return true; // let it go natively outside the context
}
doFocus($last_selectable);
} else if (!e.shiftKey && target === $last_selectable[0]) {
if (!options.wrapAround) {
return true; // let it go natively outside the context
}
doFocus($first_selectable);
} else if (e.shiftKey) {
doFocus($selectable.eq($selectable.index(target) - 1));
} else {
doFocus($selectable.eq($selectable.index(target) + 1));
}
};
return new KeyboardController(context, handlers);
}
TabKeyboardController.defaults = {
focusedClass : 'item-focused', // necessary for IE which doesn't allow :focus styles.
wrapAround : true
};
/**
* @param eventTarget {Element} the element to listen for events on
* @param listElement {Element} the list element to traverse items within
* @param options {Object} an object in the shape of ListKeyboardController.default
* @return {KeyboardController}
* @constructor
*/
function ListKeyboardController(eventTarget, listElement, options) {
options = $.extend({}, ListKeyboardController.defaults, options);
var $listEl = $(listElement);
var selectCallbacks = $.Callbacks();
var focusCallbacks = $.Callbacks();
if (options.onSelect) {
selectCallbacks.add(options.onSelect);
}
if (options.onFocus) {
focusCallbacks.add(options.onFocus);
}
var reachedEnd = false, awaitingMore = false, waitingInterrupted = false;
function traverseForwards($current) {
do {
var $firstChild = $current.find('>:first-child');
var $nextSibling = $current.next();
if ($firstChild.length) {
// Attempt to traverse children first
$current = $firstChild;
} else if ($nextSibling.length) {
// Otherwise go for the next sibling
$current = $nextSibling;
} else {
// Otherwise find the next sibling of the closest parent that has a sibling
$current = $current.parentsUntil($listEl[0]).next().first();
}
} while ($current.length && !$current.is(options.itemSelector));
return $current;
}
function traverseBackwards($current) {
do {
var $previous = $current.prev();
if ($previous.length) {
var $lastChild;
// Work down the tree of last-children until a node with no children is reached
while(($lastChild = $previous.find('>:last-child')).length) {
$previous = $lastChild;
}
$current = $previous;
} else {
// If there are none before the current element, move to the parent
$current = $current.parent();
// Unless it's the containing element
if ($current[0] === $listEl[0]) {
$current = $();
}
}
} while ($current.length && !$current.is(options.itemSelector));
return $current;
}
function getPrevNextFunc(nextPrev) {
return function prevNextFunc(e) {
var $items = $(options.itemSelector, $listEl);
var $focusedItem = $items.filter(function() {
return $(this).hasClass(options.focusedClass);
});
var cycled = false;
var movingDown = nextPrev === 'next';
var $next;
if ($focusedItem.length) {
if (options.adjacentItems) {
$next = $focusedItem[nextPrev](options.itemSelector);
} else {
if (nextPrev === 'next') {
$next = traverseForwards($focusedItem);
} else {
$next = traverseBackwards($focusedItem);
}
}
} else if (nextPrev === 'next') {
$next = $items.first();
} else if (options.wrapAround) {
$next = $items.last();
} else {
return;
}
//ignore any waiting requests since we have a newer one
waitingInterrupted = true;
if (!$next.length) {
// if we reached the bottom, check if we need to load more
if (nextPrev === 'next' && options.requestMore && !reachedEnd) {
if (!awaitingMore) {
waitingInterrupted = false;
var promise = options.requestMore();
if (promise) {
awaitingMore = true;
promise.done(function(didReachEnd) {
reachedEnd = didReachEnd;
if (!waitingInterrupted) {
prevNextFunc(e);
}
}).always(function() {
awaitingMore = false;
waitingInterrupted = false;
});
return;
} else {
reachedEnd = true;
prevNextFunc(e);
return;
}
} else {
waitingInterrupted = false;
return;
}
} else if (options.wrapAround) { //cycle from last to first/first to last when then press down/up.
cycled = true;
movingDown = !movingDown;
$next = $items[nextPrev === 'next' ? 'first' : 'last']();
} else {
return;
}
}
$focusedItem.removeClass(options.focusedClass);
$next.addClass(options.focusedClass);
var $nextNext = $next[nextPrev](),
$toScrollTo = !cycled && $nextNext.length ?
$nextNext :
$next;
var activeEl = document.activeElement;
if (options.focusIntoView && activeEl) {
// A hack for scrolling the virtually focused item into view - Used by the branch-selector
var oldTabindex = $toScrollTo.attr('tabindex');
$toScrollTo.attr('tabindex', '0');
$toScrollTo.focus();
activeEl.focus();
if (oldTabindex == null) {
$toScrollTo.removeAttr('tabindex');
} else {
$toScrollTo.attr('tabindex', oldTabindex);
}
} else {
$next[0].scrollIntoView(false);
}
focusCallbacks.fire($next, e);
};
}
var handlers = {};
var prevHandler = handlers[$.ui.keyCode.UP] = getPrevNextFunc('prev');
var nextHandler = handlers[$.ui.keyCode.DOWN] = getPrevNextFunc('next');
handlers[$.ui.keyCode.ENTER] = function(e) {
var $items = $(options.itemSelector, $listEl);
var $focusedItem = $items.filter(function() {
return $(this).hasClass(options.focusedClass);
});
if ($focusedItem.length) {
selectCallbacks.fire($focusedItem, e);
}
};
var controller = new KeyboardController(eventTarget, handlers);
controller.setListElement = function(listEl) {
$listEl = $(listEl);
};
controller.blur = function() {
$(options.itemSelector, $listEl).removeClass(options.focusedClass);
};
controller.focus = function() {
focusCallbacks.add.apply(focusCallbacks, arguments);
return this;
};
controller.select = function() {
selectCallbacks.add.apply(selectCallbacks, arguments);
return this;
};
controller.moveToNext = nextHandler;
controller.moveToPrev = prevHandler;
return controller;
}
ListKeyboardController.defaults = {
itemSelector : 'li',
focusedClass : 'item-focused',
wrapAround : false,
adjacentItems: true,
requestMore : undefined,
onFocus : undefined,
onSelect : undefined
};
return {
KeyboardController : KeyboardController,
TabKeyboardController : TabKeyboardController,
ListKeyboardController : ListKeyboardController
};
});