diff --git a/package.json b/package.json new file mode 100644 index 0000000..4fa8dbe --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "name": "js-spatial-navigation", + "main": "spatial_navigation.js", + "version": "0.0.0" +} \ No newline at end of file diff --git a/spatial_navigation.js b/spatial_navigation.js index d229e4f..be12784 100644 --- a/spatial_navigation.js +++ b/spatial_navigation.js @@ -6,1251 +6,1260 @@ * * Licensed under the MPL 2.0. */ -;(function($) { - 'use strict'; - - /************************/ - /* Global Configuration */ - /************************/ - // Note: an can be one of following types: - // - a valid selector string for "querySelectorAll" or jQuery (if it exists) - // - a NodeList or an array containing DOM elements - // - a single DOM element - // - a jQuery object - // - a string "@" to indicate the specified section - // - a string "@" to indicate the default section - var GlobalConfig = { - selector: '', // can be a valid except "@" syntax. - straightOnly: false, - straightOverlapThreshold: 0.5, - rememberSource: false, - disabled: false, - defaultElement: '', // except "@" syntax. - enterTo: '', // '', 'last-focused', 'default-element' - leaveFor: null, // {left: , right: , - // up: , down: } - restrict: 'self-first', // 'self-first', 'self-only', 'none' - tabIndexIgnoreList: - 'a, input, select, textarea, button, iframe, [contentEditable=true]', - navigableFilter: null - }; - - /*********************/ - /* Constant Variable */ - /*********************/ - var KEYMAPPING = { +(function(root, factory) { + if(typeof define === 'function' && define.amd) { + define([], factory); + } else if(typeof module === 'object' && module.exports) { + var jQuery; + try { + jQuery = require('jquery'); + } catch (e) {} + module.exports = factory(require(jQuery)); + } else { + root.SpatialNavigation = factory(root.jQuery); + } +}(this, function($) { +'use strict'; + +/************************/ +/* Global Configuration */ +/************************/ +// Note: an can be one of following types: +// - a valid selector string for "querySelectorAll" or jQuery (if it exists) +// - a NodeList or an array containing DOM elements +// - a single DOM element +// - a jQuery object +// - a string "@" to indicate the specified section +// - a string "@" to indicate the default section +var GlobalConfig = { + selector: '', // can be a valid except "@" syntax. + straightOnly: false, + straightOverlapThreshold: 0.5, + rememberSource: false, + disabled: false, + defaultElement: '', // except "@" syntax. + enterTo: '', // '', 'last-focused', 'default-element' + leaveFor: null, // {left: , right: , + // up: , down: } + restrict: 'self-first', // 'self-first', 'self-only', 'none' + tabIndexIgnoreList: + 'a, input, select, textarea, button, iframe, [contentEditable=true]', + navigableFilter: null, + directionKeys: { '37': 'left', '38': 'up', '39': 'right', '40': 'down' + }, + enterKey: 13 +}; + +var REVERSE = { + 'left': 'right', + 'up': 'down', + 'right': 'left', + 'down': 'up' +}; + +var EVENT_PREFIX = 'sn:'; +var ID_POOL_PREFIX = 'section-'; + +/********************/ +/* Private Variable */ +/********************/ +var _idPool = 0; +var _ready = false; +var _pause = false; +var _sections = {}; +var _sectionCount = 0; +var _defaultSectionId = ''; +var _lastSectionId = ''; +var _duringFocusChange = false; + +/************/ +/* Polyfill */ +/************/ +var elementMatchesSelector = + Element.prototype.matches || + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.webkitMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + function (selector) { + var matchedNodes = + (this.parentNode || this.document).querySelectorAll(selector); + return [].slice.call(matchedNodes).indexOf(this) >= 0; }; - var REVERSE = { - 'left': 'right', - 'up': 'down', - 'right': 'left', - 'down': 'up' +/*****************/ +/* Core Function */ +/*****************/ +function getRect(elem) { + var cr = elem.getBoundingClientRect(); + var rect = { + left: cr.left, + top: cr.top, + right: cr.right, + bottom: cr.bottom, + width: cr.width, + height: cr.height }; + rect.element = elem; + rect.center = { + x: rect.left + Math.floor(rect.width / 2), + y: rect.top + Math.floor(rect.height / 2) + }; + rect.center.left = rect.center.right = rect.center.x; + rect.center.top = rect.center.bottom = rect.center.y; + return rect; +} + +function partition(rects, targetRect, straightOverlapThreshold) { + var groups = [[], [], [], [], [], [], [], [], []]; + + for (var i = 0; i < rects.length; i++) { + var rect = rects[i]; + var center = rect.center; + var x, y, groupId; + + if (center.x < targetRect.left) { + x = 0; + } else if (center.x <= targetRect.right) { + x = 1; + } else { + x = 2; + } - var EVENT_PREFIX = 'sn:'; - var ID_POOL_PREFIX = 'section-'; - - /********************/ - /* Private Variable */ - /********************/ - var _idPool = 0; - var _ready = false; - var _pause = false; - var _sections = {}; - var _sectionCount = 0; - var _defaultSectionId = ''; - var _lastSectionId = ''; - var _duringFocusChange = false; - - /************/ - /* Polyfill */ - /************/ - var elementMatchesSelector = - Element.prototype.matches || - Element.prototype.matchesSelector || - Element.prototype.mozMatchesSelector || - Element.prototype.webkitMatchesSelector || - Element.prototype.msMatchesSelector || - Element.prototype.oMatchesSelector || - function (selector) { - var matchedNodes = - (this.parentNode || this.document).querySelectorAll(selector); - return [].slice.call(matchedNodes).indexOf(this) >= 0; - }; - - /*****************/ - /* Core Function */ - /*****************/ - function getRect(elem) { - var cr = elem.getBoundingClientRect(); - var rect = { - left: cr.left, - top: cr.top, - right: cr.right, - bottom: cr.bottom, - width: cr.width, - height: cr.height - }; - rect.element = elem; - rect.center = { - x: rect.left + Math.floor(rect.width / 2), - y: rect.top + Math.floor(rect.height / 2) - }; - rect.center.left = rect.center.right = rect.center.x; - rect.center.top = rect.center.bottom = rect.center.y; - return rect; - } - - function partition(rects, targetRect, straightOverlapThreshold) { - var groups = [[], [], [], [], [], [], [], [], []]; - - for (var i = 0; i < rects.length; i++) { - var rect = rects[i]; - var center = rect.center; - var x, y, groupId; - - if (center.x < targetRect.left) { - x = 0; - } else if (center.x <= targetRect.right) { - x = 1; - } else { - x = 2; - } - - if (center.y < targetRect.top) { - y = 0; - } else if (center.y <= targetRect.bottom) { - y = 1; - } else { - y = 2; - } + if (center.y < targetRect.top) { + y = 0; + } else if (center.y <= targetRect.bottom) { + y = 1; + } else { + y = 2; + } - groupId = y * 3 + x; - groups[groupId].push(rect); + groupId = y * 3 + x; + groups[groupId].push(rect); - if ([0, 2, 6, 8].indexOf(groupId) !== -1) { - var threshold = straightOverlapThreshold; + if ([0, 2, 6, 8].indexOf(groupId) !== -1) { + var threshold = straightOverlapThreshold; - if (rect.left <= targetRect.right - targetRect.width * threshold) { - if (groupId === 2) { - groups[1].push(rect); - } else if (groupId === 8) { - groups[7].push(rect); - } + if (rect.left <= targetRect.right - targetRect.width * threshold) { + if (groupId === 2) { + groups[1].push(rect); + } else if (groupId === 8) { + groups[7].push(rect); } + } - if (rect.right >= targetRect.left + targetRect.width * threshold) { - if (groupId === 0) { - groups[1].push(rect); - } else if (groupId === 6) { - groups[7].push(rect); - } + if (rect.right >= targetRect.left + targetRect.width * threshold) { + if (groupId === 0) { + groups[1].push(rect); + } else if (groupId === 6) { + groups[7].push(rect); } + } - if (rect.top <= targetRect.bottom - targetRect.height * threshold) { - if (groupId === 6) { - groups[3].push(rect); - } else if (groupId === 8) { - groups[5].push(rect); - } + if (rect.top <= targetRect.bottom - targetRect.height * threshold) { + if (groupId === 6) { + groups[3].push(rect); + } else if (groupId === 8) { + groups[5].push(rect); } + } - if (rect.bottom >= targetRect.top + targetRect.height * threshold) { - if (groupId === 0) { - groups[3].push(rect); - } else if (groupId === 2) { - groups[5].push(rect); - } + if (rect.bottom >= targetRect.top + targetRect.height * threshold) { + if (groupId === 0) { + groups[3].push(rect); + } else if (groupId === 2) { + groups[5].push(rect); } } } - - return groups; } - function generateDistanceFunction(targetRect) { - return { - nearPlumbLineIsBetter: function(rect) { - var d; - if (rect.center.x < targetRect.center.x) { - d = targetRect.center.x - rect.right; - } else { - d = rect.left - targetRect.center.x; - } - return d < 0 ? 0 : d; - }, - nearHorizonIsBetter: function(rect) { - var d; - if (rect.center.y < targetRect.center.y) { - d = targetRect.center.y - rect.bottom; - } else { - d = rect.top - targetRect.center.y; - } - return d < 0 ? 0 : d; - }, - nearTargetLeftIsBetter: function(rect) { - var d; - if (rect.center.x < targetRect.center.x) { - d = targetRect.left - rect.right; - } else { - d = rect.left - targetRect.left; - } - return d < 0 ? 0 : d; - }, - nearTargetTopIsBetter: function(rect) { - var d; - if (rect.center.y < targetRect.center.y) { - d = targetRect.top - rect.bottom; - } else { - d = rect.top - targetRect.top; - } - return d < 0 ? 0 : d; - }, - topIsBetter: function(rect) { - return rect.top; - }, - bottomIsBetter: function(rect) { - return -1 * rect.bottom; - }, - leftIsBetter: function(rect) { - return rect.left; - }, - rightIsBetter: function(rect) { - return -1 * rect.right; - } - }; - } + return groups; +} - function prioritize(priorities) { - var destPriority = null; - for (var i = 0; i < priorities.length; i++) { - if (priorities[i].group.length) { - destPriority = priorities[i]; - break; +function generateDistanceFunction(targetRect) { + return { + nearPlumbLineIsBetter: function(rect) { + var d; + if (rect.center.x < targetRect.center.x) { + d = targetRect.center.x - rect.right; + } else { + d = rect.left - targetRect.center.x; + } + return d < 0 ? 0 : d; + }, + nearHorizonIsBetter: function(rect) { + var d; + if (rect.center.y < targetRect.center.y) { + d = targetRect.center.y - rect.bottom; + } else { + d = rect.top - targetRect.center.y; } + return d < 0 ? 0 : d; + }, + nearTargetLeftIsBetter: function(rect) { + var d; + if (rect.center.x < targetRect.center.x) { + d = targetRect.left - rect.right; + } else { + d = rect.left - targetRect.left; + } + return d < 0 ? 0 : d; + }, + nearTargetTopIsBetter: function(rect) { + var d; + if (rect.center.y < targetRect.center.y) { + d = targetRect.top - rect.bottom; + } else { + d = rect.top - targetRect.top; + } + return d < 0 ? 0 : d; + }, + topIsBetter: function(rect) { + return rect.top; + }, + bottomIsBetter: function(rect) { + return -1 * rect.bottom; + }, + leftIsBetter: function(rect) { + return rect.left; + }, + rightIsBetter: function(rect) { + return -1 * rect.right; } - - if (!destPriority) { - return null; + }; +} + +function prioritize(priorities) { + var destPriority = null; + for (var i = 0; i < priorities.length; i++) { + if (priorities[i].group.length) { + destPriority = priorities[i]; + break; } + } - var destDistance = destPriority.distance; - - destPriority.group.sort(function(a, b) { - for (var i = 0; i < destDistance.length; i++) { - var distance = destDistance[i]; - var delta = distance(a) - distance(b); - if (delta) { - return delta; - } - } - return 0; - }); - - return destPriority.group; + if (!destPriority) { + return null; } - function navigate(target, direction, candidates, config) { - if (!target || !direction || !candidates || !candidates.length) { - return null; - } + var destDistance = destPriority.distance; - var rects = []; - for (var i = 0; i < candidates.length; i++) { - var rect = getRect(candidates[i]); - if (rect) { - rects.push(rect); + destPriority.group.sort(function(a, b) { + for (var i = 0; i < destDistance.length; i++) { + var distance = destDistance[i]; + var delta = distance(a) - distance(b); + if (delta) { + return delta; } } - if (!rects.length) { - return null; - } - - var targetRect = getRect(target); - if (!targetRect) { - return null; - } + return 0; + }); - var distanceFunction = generateDistanceFunction(targetRect); + return destPriority.group; +} - var groups = partition( - rects, - targetRect, - config.straightOverlapThreshold - ); - - var internalGroups = partition( - groups[4], - targetRect.center, - config.straightOverlapThreshold - ); - - var priorities; - - switch (direction) { - case 'left': - priorities = [ - { - group: internalGroups[0].concat(internalGroups[3]) - .concat(internalGroups[6]), - distance: [ - distanceFunction.nearPlumbLineIsBetter, - distanceFunction.topIsBetter - ] - }, - { - group: groups[3], - distance: [ - distanceFunction.nearPlumbLineIsBetter, - distanceFunction.topIsBetter - ] - }, - { - group: groups[0].concat(groups[6]), - distance: [ - distanceFunction.nearHorizonIsBetter, - distanceFunction.rightIsBetter, - distanceFunction.nearTargetTopIsBetter - ] - } - ]; - break; - case 'right': - priorities = [ - { - group: internalGroups[2].concat(internalGroups[5]) - .concat(internalGroups[8]), - distance: [ - distanceFunction.nearPlumbLineIsBetter, - distanceFunction.topIsBetter - ] - }, - { - group: groups[5], - distance: [ - distanceFunction.nearPlumbLineIsBetter, - distanceFunction.topIsBetter - ] - }, - { - group: groups[2].concat(groups[8]), - distance: [ - distanceFunction.nearHorizonIsBetter, - distanceFunction.leftIsBetter, - distanceFunction.nearTargetTopIsBetter - ] - } - ]; - break; - case 'up': - priorities = [ - { - group: internalGroups[0].concat(internalGroups[1]) - .concat(internalGroups[2]), - distance: [ - distanceFunction.nearHorizonIsBetter, - distanceFunction.leftIsBetter - ] - }, - { - group: groups[1], - distance: [ - distanceFunction.nearHorizonIsBetter, - distanceFunction.leftIsBetter - ] - }, - { - group: groups[0].concat(groups[2]), - distance: [ - distanceFunction.nearPlumbLineIsBetter, - distanceFunction.bottomIsBetter, - distanceFunction.nearTargetLeftIsBetter - ] - } - ]; - break; - case 'down': - priorities = [ - { - group: internalGroups[6].concat(internalGroups[7]) - .concat(internalGroups[8]), - distance: [ - distanceFunction.nearHorizonIsBetter, - distanceFunction.leftIsBetter - ] - }, - { - group: groups[7], - distance: [ - distanceFunction.nearHorizonIsBetter, - distanceFunction.leftIsBetter - ] - }, - { - group: groups[6].concat(groups[8]), - distance: [ - distanceFunction.nearPlumbLineIsBetter, - distanceFunction.topIsBetter, - distanceFunction.nearTargetLeftIsBetter - ] - } - ]; - break; - default: - return null; - } +function navigate(target, direction, candidates, config) { + if (!target || !direction || !candidates || !candidates.length) { + return null; + } - if (config.straightOnly) { - priorities.pop(); + var rects = []; + for (var i = 0; i < candidates.length; i++) { + var rect = getRect(candidates[i]); + if (rect) { + rects.push(rect); } + } + if (!rects.length) { + return null; + } - var destGroup = prioritize(priorities); - if (!destGroup) { - return null; - } + var targetRect = getRect(target); + if (!targetRect) { + return null; + } - var dest = null; - if (config.rememberSource && - config.previous && - config.previous.destination === target && - config.previous.reverse === direction) { - for (var j = 0; j < destGroup.length; j++) { - if (destGroup[j].element === config.previous.target) { - dest = destGroup[j].element; - break; + var distanceFunction = generateDistanceFunction(targetRect); + + var groups = partition( + rects, + targetRect, + config.straightOverlapThreshold + ); + + var internalGroups = partition( + groups[4], + targetRect.center, + config.straightOverlapThreshold + ); + + var priorities; + + switch (direction) { + case 'left': + priorities = [ + { + group: internalGroups[0].concat(internalGroups[3]) + .concat(internalGroups[6]), + distance: [ + distanceFunction.nearPlumbLineIsBetter, + distanceFunction.topIsBetter + ] + }, + { + group: groups[3], + distance: [ + distanceFunction.nearPlumbLineIsBetter, + distanceFunction.topIsBetter + ] + }, + { + group: groups[0].concat(groups[6]), + distance: [ + distanceFunction.nearHorizonIsBetter, + distanceFunction.rightIsBetter, + distanceFunction.nearTargetTopIsBetter + ] } - } - } + ]; + break; + case 'right': + priorities = [ + { + group: internalGroups[2].concat(internalGroups[5]) + .concat(internalGroups[8]), + distance: [ + distanceFunction.nearPlumbLineIsBetter, + distanceFunction.topIsBetter + ] + }, + { + group: groups[5], + distance: [ + distanceFunction.nearPlumbLineIsBetter, + distanceFunction.topIsBetter + ] + }, + { + group: groups[2].concat(groups[8]), + distance: [ + distanceFunction.nearHorizonIsBetter, + distanceFunction.leftIsBetter, + distanceFunction.nearTargetTopIsBetter + ] + } + ]; + break; + case 'up': + priorities = [ + { + group: internalGroups[0].concat(internalGroups[1]) + .concat(internalGroups[2]), + distance: [ + distanceFunction.nearHorizonIsBetter, + distanceFunction.leftIsBetter + ] + }, + { + group: groups[1], + distance: [ + distanceFunction.nearHorizonIsBetter, + distanceFunction.leftIsBetter + ] + }, + { + group: groups[0].concat(groups[2]), + distance: [ + distanceFunction.nearPlumbLineIsBetter, + distanceFunction.bottomIsBetter, + distanceFunction.nearTargetLeftIsBetter + ] + } + ]; + break; + case 'down': + priorities = [ + { + group: internalGroups[6].concat(internalGroups[7]) + .concat(internalGroups[8]), + distance: [ + distanceFunction.nearHorizonIsBetter, + distanceFunction.leftIsBetter + ] + }, + { + group: groups[7], + distance: [ + distanceFunction.nearHorizonIsBetter, + distanceFunction.leftIsBetter + ] + }, + { + group: groups[6].concat(groups[8]), + distance: [ + distanceFunction.nearPlumbLineIsBetter, + distanceFunction.topIsBetter, + distanceFunction.nearTargetLeftIsBetter + ] + } + ]; + break; + default: + return null; + } - if (!dest) { - dest = destGroup[0].element; - } + if (config.straightOnly) { + priorities.pop(); + } - return dest; + var destGroup = prioritize(priorities); + if (!destGroup) { + return null; } - /********************/ - /* Private Function */ - /********************/ - function generateId() { - var id; - while(true) { - id = ID_POOL_PREFIX + String(++_idPool); - if (!_sections[id]) { + var dest = null; + if (config.rememberSource && + config.previous && + config.previous.destination === target && + config.previous.reverse === direction) { + for (var j = 0; j < destGroup.length; j++) { + if (destGroup[j].element === config.previous.target) { + dest = destGroup[j].element; break; } } - return id; } - function parseSelector(selector) { - var result; - if ($) { - result = $(selector).get(); - } else if (typeof selector === 'string') { - result = [].slice.call(document.querySelectorAll(selector)); - } else if (typeof selector === 'object' && selector.length) { - result = [].slice.call(selector); - } else if (typeof selector === 'object' && selector.nodeType === 1) { - result = [selector]; - } else { - result = []; - } - return result; + if (!dest) { + dest = destGroup[0].element; } - function matchSelector(elem, selector) { - if ($) { - return $(elem).is(selector); - } else if (typeof selector === 'string') { - return elementMatchesSelector.call(elem, selector); - } else if (typeof selector === 'object' && selector.length) { - return selector.indexOf(elem) >= 0; - } else if (typeof selector === 'object' && selector.nodeType === 1) { - return elem === selector; + return dest; +} + +/********************/ +/* Private Function */ +/********************/ +function generateId() { + var id; + while(true) { + id = ID_POOL_PREFIX + String(++_idPool); + if (!_sections[id]) { + break; } - return false; } + return id; +} - function getCurrentFocusedElement() { - var activeElement = document.activeElement; - if (activeElement && activeElement !== document.body) { - return activeElement; - } +function parseSelector(selector) { + var result; + if ($) { + result = $(selector).get(); + } else if (typeof selector === 'string') { + result = [].slice.call(document.querySelectorAll(selector)); + } else if (typeof selector === 'object' && selector.length) { + result = [].slice.call(selector); + } else if (typeof selector === 'object' && selector.nodeType === 1) { + result = [selector]; + } else { + result = []; } + return result; +} - function extend(out) { - out = out || {}; - for (var i = 1; i < arguments.length; i++) { - if (!arguments[i]) { - continue; - } - for (var key in arguments[i]) { - if (arguments[i].hasOwnProperty(key) && - arguments[i][key] !== undefined) { - out[key] = arguments[i][key]; - } - } - } - return out; +function matchSelector(elem, selector) { + if ($) { + return $(elem).is(selector); + } else if (typeof selector === 'string') { + return elementMatchesSelector.call(elem, selector); + } else if (typeof selector === 'object' && selector.length) { + return selector.indexOf(elem) >= 0; + } else if (typeof selector === 'object' && selector.nodeType === 1) { + return elem === selector; + } + return false; +} + +function getCurrentFocusedElement() { + var activeElement = document.activeElement; + if (activeElement && activeElement !== document.body) { + return activeElement; } +} - function exclude(elemList, excludedElem) { - if (!Array.isArray(excludedElem)) { - excludedElem = [excludedElem]; +function extend(out) { + out = out || {}; + for (var i = 1; i < arguments.length; i++) { + if (!arguments[i]) { + continue; } - for (var i = 0, index; i < excludedElem.length; i++) { - index = elemList.indexOf(excludedElem[i]); - if (index >= 0) { - elemList.splice(index, 1); + for (var key in arguments[i]) { + if (arguments[i].hasOwnProperty(key) && + arguments[i][key] !== undefined) { + out[key] = arguments[i][key]; } } - return elemList; } + return out; +} - function isNavigable(elem, sectionId, verifySectionSelector) { - if (! elem || !sectionId || - !_sections[sectionId] || _sections[sectionId].disabled) { - return false; +function exclude(elemList, excludedElem) { + if (!Array.isArray(excludedElem)) { + excludedElem = [excludedElem]; + } + for (var i = 0, index; i < excludedElem.length; i++) { + index = elemList.indexOf(excludedElem[i]); + if (index >= 0) { + elemList.splice(index, 1); } - if ((elem.offsetWidth <= 0 && elem.offsetHeight <= 0) || - elem.hasAttribute('disabled')) { + } + return elemList; +} + +function isNavigable(elem, sectionId, verifySectionSelector) { + if (! elem || !sectionId || + !_sections[sectionId] || _sections[sectionId].disabled) { + return false; + } + if ((elem.offsetWidth <= 0 && elem.offsetHeight <= 0) || + elem.hasAttribute('disabled')) { + return false; + } + if (verifySectionSelector && + !matchSelector(elem, _sections[sectionId].selector)) { + return false; + } + if (typeof _sections[sectionId].navigableFilter === 'function') { + if (_sections[sectionId].navigableFilter(elem, sectionId) === false) { return false; } - if (verifySectionSelector && - !matchSelector(elem, _sections[sectionId].selector)) { + } else if (typeof GlobalConfig.navigableFilter === 'function') { + if (GlobalConfig.navigableFilter(elem, sectionId) === false) { return false; } - if (typeof _sections[sectionId].navigableFilter === 'function') { - if (_sections[sectionId].navigableFilter(elem, sectionId) === false) { - return false; - } - } else if (typeof GlobalConfig.navigableFilter === 'function') { - if (GlobalConfig.navigableFilter(elem, sectionId) === false) { - return false; - } - } - return true; } - - function getSectionId(elem) { - for (var id in _sections) { - if (!_sections[id].disabled && - matchSelector(elem, _sections[id].selector)) { - return id; - } + return true; +} + +function getSectionId(elem) { + for (var id in _sections) { + if (!_sections[id].disabled && + matchSelector(elem, _sections[id].selector)) { + return id; } } +} - function getSectionNavigableElements(sectionId) { - return parseSelector(_sections[sectionId].selector).filter(function(elem) { - return isNavigable(elem, sectionId); - }); - } +function getSectionNavigableElements(sectionId) { + return parseSelector(_sections[sectionId].selector).filter(function(elem) { + return isNavigable(elem, sectionId); + }); +} - function getSectionDefaultElement(sectionId) { - var defaultElement = _sections[sectionId].defaultElement; - if (!defaultElement) { - return null; - } - if (typeof defaultElement === 'string') { - defaultElement = parseSelector(defaultElement)[0]; - } else if ($ && defaultElement instanceof $) { - defaultElement = defaultElement.get(0); - } - if (isNavigable(defaultElement, sectionId, true)) { - return defaultElement; - } +function getSectionDefaultElement(sectionId) { + var defaultElement = _sections[sectionId].defaultElement; + if (!defaultElement) { return null; } - - function getSectionLastFocusedElement(sectionId) { - var lastFocusedElement = _sections[sectionId].lastFocusedElement; - if (!isNavigable(lastFocusedElement, sectionId, true)) { - return null; - } - return lastFocusedElement; + if (typeof defaultElement === 'string') { + defaultElement = parseSelector(defaultElement)[0]; + } else if ($ && defaultElement instanceof $) { + defaultElement = defaultElement.get(0); } + if (isNavigable(defaultElement, sectionId, true)) { + return defaultElement; + } + return null; +} - function fireEvent(elem, type, details, cancelable) { - if (arguments.length < 4) { - cancelable = true; - } - var evt = document.createEvent('CustomEvent'); - evt.initCustomEvent(EVENT_PREFIX + type, true, cancelable, details); - return elem.dispatchEvent(evt); +function getSectionLastFocusedElement(sectionId) { + var lastFocusedElement = _sections[sectionId].lastFocusedElement; + if (!isNavigable(lastFocusedElement, sectionId, true)) { + return null; } + return lastFocusedElement; +} - function focusElement(elem, sectionId, direction) { - if (!elem) { - return false; - } +function fireEvent(elem, type, details, cancelable) { + if (arguments.length < 4) { + cancelable = true; + } + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(EVENT_PREFIX + type, true, cancelable, details); + return elem.dispatchEvent(evt); +} - var currentFocusedElement = getCurrentFocusedElement(); +function focusElement(elem, sectionId, direction) { + if (!elem) { + return false; + } - var silentFocus = function() { - if (currentFocusedElement) { - currentFocusedElement.blur(); - } - elem.focus(); - focusChanged(elem, sectionId); - }; + var currentFocusedElement = getCurrentFocusedElement(); - if (_duringFocusChange) { - silentFocus(); - return true; + var silentFocus = function() { + if (currentFocusedElement) { + currentFocusedElement.blur(); } + elem.focus(); + focusChanged(elem, sectionId); + }; - _duringFocusChange = true; + if (_duringFocusChange) { + silentFocus(); + return true; + } - if (_pause) { - silentFocus(); - _duringFocusChange = false; - return true; - } + _duringFocusChange = true; - if (currentFocusedElement) { - var unfocusProperties = { - nextElement: elem, - nextSectionId: sectionId, - direction: direction, - native: false - }; - if (!fireEvent(currentFocusedElement, 'willunfocus', unfocusProperties)) { - _duringFocusChange = false; - return false; - } - currentFocusedElement.blur(); - fireEvent(currentFocusedElement, 'unfocused', unfocusProperties, false); - } + if (_pause) { + silentFocus(); + _duringFocusChange = false; + return true; + } - var focusProperties = { - previousElement: currentFocusedElement, - sectionId: sectionId, + if (currentFocusedElement) { + var unfocusProperties = { + nextElement: elem, + nextSectionId: sectionId, direction: direction, native: false }; - if (!fireEvent(elem, 'willfocus', focusProperties)) { + if (!fireEvent(currentFocusedElement, 'willunfocus', unfocusProperties)) { _duringFocusChange = false; return false; } - elem.focus(); - fireEvent(elem, 'focused', focusProperties, false); + currentFocusedElement.blur(); + fireEvent(currentFocusedElement, 'unfocused', unfocusProperties, false); + } + var focusProperties = { + previousElement: currentFocusedElement, + sectionId: sectionId, + direction: direction, + native: false + }; + if (!fireEvent(elem, 'willfocus', focusProperties)) { _duringFocusChange = false; - - focusChanged(elem, sectionId); - return true; + return false; } + elem.focus(); + fireEvent(elem, 'focused', focusProperties, false); - function focusChanged(elem, sectionId) { - if (!sectionId) { - sectionId = getSectionId(elem); - } - if (sectionId) { - _sections[sectionId].lastFocusedElement = elem; - _lastSectionId = sectionId; - } + _duringFocusChange = false; + + focusChanged(elem, sectionId); + return true; +} + +function focusChanged(elem, sectionId) { + if (!sectionId) { + sectionId = getSectionId(elem); } + if (sectionId) { + _sections[sectionId].lastFocusedElement = elem; + _lastSectionId = sectionId; + } +} - function focusExtendedSelector(selector, direction) { - if (selector.charAt(0) == '@') { - if (selector.length == 1) { - return focusSection(); - } else { - var sectionId = selector.substr(1); - return focusSection(sectionId); - } +function focusExtendedSelector(selector, direction) { + if (selector.charAt(0) == '@') { + if (selector.length == 1) { + return focusSection(); } else { - var next = parseSelector(selector)[0]; - if (next) { - var nextSectionId = getSectionId(next); - if (isNavigable(next, nextSectionId)) { - return focusElement(next, nextSectionId, direction); - } + var sectionId = selector.substr(1); + return focusSection(sectionId); + } + } else { + var next = parseSelector(selector)[0]; + if (next) { + var nextSectionId = getSectionId(next); + if (isNavigable(next, nextSectionId)) { + return focusElement(next, nextSectionId, direction); } } - return false; } - - function focusSection(sectionId) { - var range = []; - var addRange = function(id) { - if (id && range.indexOf(id) < 0 && - _sections[id] && !_sections[id].disabled) { - range.push(id); - } - }; - - if (sectionId) { - addRange(sectionId); - } else { - addRange(_defaultSectionId); - addRange(_lastSectionId); - Object.keys(_sections).map(addRange); + return false; +} + +function focusSection(sectionId) { + var range = []; + var addRange = function(id) { + if (id && range.indexOf(id) < 0 && + _sections[id] && !_sections[id].disabled) { + range.push(id); } + }; - for (var i = 0; i < range.length; i++) { - var id = range[i]; - var next; + if (sectionId) { + addRange(sectionId); + } else { + addRange(_defaultSectionId); + addRange(_lastSectionId); + Object.keys(_sections).map(addRange); + } - if (_sections[id].enterTo == 'last-focused') { - next = getSectionLastFocusedElement(id) || - getSectionDefaultElement(id) || - getSectionNavigableElements(id)[0]; - } else { - next = getSectionDefaultElement(id) || - getSectionLastFocusedElement(id) || - getSectionNavigableElements(id)[0]; - } + for (var i = 0; i < range.length; i++) { + var id = range[i]; + var next; - if (next) { - return focusElement(next, id); - } + if (_sections[id].enterTo == 'last-focused') { + next = getSectionLastFocusedElement(id) || + getSectionDefaultElement(id) || + getSectionNavigableElements(id)[0]; + } else { + next = getSectionDefaultElement(id) || + getSectionLastFocusedElement(id) || + getSectionNavigableElements(id)[0]; } - return false; + if (next) { + return focusElement(next, id); + } } - function fireNavigatefailed(elem, direction) { - fireEvent(elem, 'navigatefailed', { - direction: direction - }, false); - } + return false; +} - function gotoLeaveFor(sectionId, direction) { - if (_sections[sectionId].leaveFor && - _sections[sectionId].leaveFor[direction] !== undefined) { - var next = _sections[sectionId].leaveFor[direction]; +function fireNavigatefailed(elem, direction) { + fireEvent(elem, 'navigatefailed', { + direction: direction + }, false); +} - if (typeof next === 'string') { - if (next === '') { - return null; - } - return focusExtendedSelector(next, direction); - } +function gotoLeaveFor(sectionId, direction) { + if (_sections[sectionId].leaveFor && + _sections[sectionId].leaveFor[direction] !== undefined) { + var next = _sections[sectionId].leaveFor[direction]; - if ($ && next instanceof $) { - next = next.get(0); - } - - var nextSectionId = getSectionId(next); - if (isNavigable(next, nextSectionId)) { - return focusElement(next, nextSectionId, direction); + if (typeof next === 'string') { + if (next === '') { + return null; } + return focusExtendedSelector(next, direction); } - return false; - } - function focusNext(direction, currentFocusedElement, currentSectionId) { - var extSelector = - currentFocusedElement.getAttribute('data-sn-' + direction); - if (typeof extSelector === 'string') { - if (extSelector === '' || - !focusExtendedSelector(extSelector, direction)) { - fireNavigatefailed(currentFocusedElement, direction); - return false; - } - return true; + if ($ && next instanceof $) { + next = next.get(0); } - var sectionNavigableElements = {}; - var allNavigableElements = []; - for (var id in _sections) { - sectionNavigableElements[id] = getSectionNavigableElements(id); - allNavigableElements = - allNavigableElements.concat(sectionNavigableElements[id]); + var nextSectionId = getSectionId(next); + if (isNavigable(next, nextSectionId)) { + return focusElement(next, nextSectionId, direction); + } + } + return false; +} + +function focusNext(direction, currentFocusedElement, currentSectionId) { + var extSelector = + currentFocusedElement.getAttribute('data-sn-' + direction); + if (typeof extSelector === 'string') { + if (extSelector === '' || + !focusExtendedSelector(extSelector, direction)) { + fireNavigatefailed(currentFocusedElement, direction); + return false; } + return true; + } - var config = extend({}, GlobalConfig, _sections[currentSectionId]); - var next; + var sectionNavigableElements = {}; + var allNavigableElements = []; + for (var id in _sections) { + sectionNavigableElements[id] = getSectionNavigableElements(id); + allNavigableElements = + allNavigableElements.concat(sectionNavigableElements[id]); + } - if (config.restrict == 'self-only' || config.restrict == 'self-first') { - var currentSectionNavigableElements = - sectionNavigableElements[currentSectionId]; + var config = extend({}, GlobalConfig, _sections[currentSectionId]); + var next; - next = navigate( - currentFocusedElement, - direction, - exclude(currentSectionNavigableElements, currentFocusedElement), - config - ); + if (config.restrict == 'self-only' || config.restrict == 'self-first') { + var currentSectionNavigableElements = + sectionNavigableElements[currentSectionId]; - if (!next && config.restrict == 'self-first') { - next = navigate( - currentFocusedElement, - direction, - exclude(allNavigableElements, currentSectionNavigableElements), - config - ); - } - } else { + next = navigate( + currentFocusedElement, + direction, + exclude(currentSectionNavigableElements, currentFocusedElement), + config + ); + + if (!next && config.restrict == 'self-first') { next = navigate( currentFocusedElement, direction, - exclude(allNavigableElements, currentFocusedElement), + exclude(allNavigableElements, currentSectionNavigableElements), config ); } + } else { + next = navigate( + currentFocusedElement, + direction, + exclude(allNavigableElements, currentFocusedElement), + config + ); + } - if (next) { - _sections[currentSectionId].previous = { - target: currentFocusedElement, - destination: next, - reverse: REVERSE[direction] - }; - - var nextSectionId = getSectionId(next); + if (next) { + _sections[currentSectionId].previous = { + target: currentFocusedElement, + destination: next, + reverse: REVERSE[direction] + }; - if (currentSectionId != nextSectionId) { - var result = gotoLeaveFor(currentSectionId, direction); - if (result) { - return true; - } else if (result === null) { - fireNavigatefailed(currentFocusedElement, direction); - return false; - } + var nextSectionId = getSectionId(next); - var enterToElement; - switch (_sections[nextSectionId].enterTo) { - case 'last-focused': - enterToElement = getSectionLastFocusedElement(nextSectionId) || - getSectionDefaultElement(nextSectionId); - break; - case 'default-element': - enterToElement = getSectionDefaultElement(nextSectionId); - break; - } - if (enterToElement) { - next = enterToElement; - } + if (currentSectionId != nextSectionId) { + var result = gotoLeaveFor(currentSectionId, direction); + if (result) { + return true; + } else if (result === null) { + fireNavigatefailed(currentFocusedElement, direction); + return false; } - return focusElement(next, nextSectionId, direction); - } else if (gotoLeaveFor(currentSectionId, direction)) { - return true; + var enterToElement; + switch (_sections[nextSectionId].enterTo) { + case 'last-focused': + enterToElement = getSectionLastFocusedElement(nextSectionId) || + getSectionDefaultElement(nextSectionId); + break; + case 'default-element': + enterToElement = getSectionDefaultElement(nextSectionId); + break; + } + if (enterToElement) { + next = enterToElement; + } } - fireNavigatefailed(currentFocusedElement, direction); - return false; + return focusElement(next, nextSectionId, direction); + } else if (gotoLeaveFor(currentSectionId, direction)) { + return true; } - function onKeyDown(evt) { - if (!_sectionCount || _pause || - evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) { - return; - } + fireNavigatefailed(currentFocusedElement, direction); + return false; +} - var currentFocusedElement; - var preventDefault = function() { - evt.preventDefault(); - evt.stopPropagation(); - return false; - }; +function onKeyDown(evt) { + if (!_sectionCount || _pause || + evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) { + return; + } - var direction = KEYMAPPING[evt.keyCode]; - if (!direction) { - if (evt.keyCode == 13) { - currentFocusedElement = getCurrentFocusedElement(); - if (currentFocusedElement && getSectionId(currentFocusedElement)) { - if (!fireEvent(currentFocusedElement, 'enter-down')) { - return preventDefault(); - } + var currentFocusedElement; + var preventDefault = function() { + evt.preventDefault(); + evt.stopPropagation(); + return false; + }; + + var direction = GlobalConfig.directionKeys[evt.keyCode]; + if (!direction) { + if (evt.keyCode == GlobalConfig.enterKey) { + currentFocusedElement = getCurrentFocusedElement(); + if (currentFocusedElement && getSectionId(currentFocusedElement)) { + if (!fireEvent(currentFocusedElement, 'enter-down')) { + return preventDefault(); } } - return; } + return; + } - currentFocusedElement = getCurrentFocusedElement(); + currentFocusedElement = getCurrentFocusedElement(); - if (!currentFocusedElement) { - if (_lastSectionId) { - currentFocusedElement = getSectionLastFocusedElement(_lastSectionId); - } - if (!currentFocusedElement) { - focusSection(); - return preventDefault(); - } + if (!currentFocusedElement) { + if (_lastSectionId) { + currentFocusedElement = getSectionLastFocusedElement(_lastSectionId); } - - var currentSectionId = getSectionId(currentFocusedElement); - if (!currentSectionId) { - return; + if (!currentFocusedElement) { + focusSection(); + return preventDefault(); } + } - var willmoveProperties = { - direction: direction, - sectionId: currentSectionId, - cause: 'keydown' - }; + var currentSectionId = getSectionId(currentFocusedElement); + if (!currentSectionId) { + return; + } - if (fireEvent(currentFocusedElement, 'willmove', willmoveProperties)) { - focusNext(direction, currentFocusedElement, currentSectionId); - } + var willmoveProperties = { + direction: direction, + sectionId: currentSectionId, + cause: 'keydown' + }; - return preventDefault(); + if (fireEvent(currentFocusedElement, 'willmove', willmoveProperties)) { + focusNext(direction, currentFocusedElement, currentSectionId); } - function onKeyUp(evt) { - if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) { - return - } - if (!_pause && _sectionCount && evt.keyCode == 13) { - var currentFocusedElement = getCurrentFocusedElement(); - if (currentFocusedElement && getSectionId(currentFocusedElement)) { - if (!fireEvent(currentFocusedElement, 'enter-up')) { - evt.preventDefault(); - evt.stopPropagation(); - } + return preventDefault(); +} + +function onKeyUp(evt) { + if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) { + return + } + if (!_pause && _sectionCount && evt.keyCode == GlobalConfig.enterKey) { + var currentFocusedElement = getCurrentFocusedElement(); + if (currentFocusedElement && getSectionId(currentFocusedElement)) { + if (!fireEvent(currentFocusedElement, 'enter-up')) { + evt.preventDefault(); + evt.stopPropagation(); } } } +} - function onFocus(evt) { - var target = evt.target; - if (target !== window && target !== document && - _sectionCount && !_duringFocusChange) { - var sectionId = getSectionId(target); - if (sectionId) { - if (_pause) { - focusChanged(target, sectionId); - return; - } - - var focusProperties = { - sectionId: sectionId, - native: true - }; - - if (!fireEvent(target, 'willfocus', focusProperties)) { - _duringFocusChange = true; - target.blur(); - _duringFocusChange = false; - } else { - fireEvent(target, 'focused', focusProperties, false); - focusChanged(target, sectionId); - } +function onFocus(evt) { + var target = evt.target; + if (target !== window && target !== document && + _sectionCount && !_duringFocusChange) { + var sectionId = getSectionId(target); + if (sectionId) { + if (_pause) { + focusChanged(target, sectionId); + return; } - } - } - function onBlur(evt) { - var target = evt.target; - if (target !== window && target !== document && !_pause && - _sectionCount && !_duringFocusChange && getSectionId(target)) { - var unfocusProperties = { + var focusProperties = { + sectionId: sectionId, native: true }; - if (!fireEvent(target, 'willunfocus', unfocusProperties)) { + + if (!fireEvent(target, 'willfocus', focusProperties)) { _duringFocusChange = true; - setTimeout(function() { - target.focus(); - _duringFocusChange = false; - }); + target.blur(); + _duringFocusChange = false; } else { - fireEvent(target, 'unfocused', unfocusProperties, false); + fireEvent(target, 'focused', focusProperties, false); + focusChanged(target, sectionId); } } } - - /*******************/ - /* Public Function */ - /*******************/ - var SpatialNavigation = { - init: function() { - if (!_ready) { - window.addEventListener('keydown', onKeyDown); - window.addEventListener('keyup', onKeyUp); - window.addEventListener('focus', onFocus, true); - window.addEventListener('blur', onBlur, true); - _ready = true; - } - }, - - uninit: function() { - window.removeEventListener('blur', onBlur, true); - window.removeEventListener('focus', onFocus, true); - window.removeEventListener('keyup', onKeyUp); - window.removeEventListener('keydown', onKeyDown); - SpatialNavigation.clear(); - _idPool = 0; - _ready = false; - }, - - clear: function() { - _sections = {}; - _sectionCount = 0; - _defaultSectionId = ''; - _lastSectionId = ''; - _duringFocusChange = false; - }, - - // set(); - // set(, ); - set: function() { - var sectionId, config; - - if (typeof arguments[0] === 'object') { - config = arguments[0]; - } else if (typeof arguments[0] === 'string' && - typeof arguments[1] === 'object') { - sectionId = arguments[0]; - config = arguments[1]; - if (!_sections[sectionId]) { - throw new Error('Section "' + sectionId + '" doesn\'t exist!'); - } - } else { - return; +} + +function onBlur(evt) { + var target = evt.target; + if (target !== window && target !== document && !_pause && + _sectionCount && !_duringFocusChange && getSectionId(target)) { + var unfocusProperties = { + native: true + }; + if (!fireEvent(target, 'willunfocus', unfocusProperties)) { + _duringFocusChange = true; + setTimeout(function() { + target.focus(); + _duringFocusChange = false; + }); + } else { + fireEvent(target, 'unfocused', unfocusProperties, false); + } + } +} + +/*******************/ +/* Public Function */ +/*******************/ +var SpatialNavigation = { + init: function() { + if (!_ready) { + window.addEventListener('keydown', onKeyDown); + window.addEventListener('keyup', onKeyUp); + window.addEventListener('focus', onFocus, true); + window.addEventListener('blur', onBlur, true); + _ready = true; + } + }, + + uninit: function() { + window.removeEventListener('blur', onBlur, true); + window.removeEventListener('focus', onFocus, true); + window.removeEventListener('keyup', onKeyUp); + window.removeEventListener('keydown', onKeyDown); + SpatialNavigation.clear(); + _idPool = 0; + _ready = false; + }, + + clear: function() { + _sections = {}; + _sectionCount = 0; + _defaultSectionId = ''; + _lastSectionId = ''; + _duringFocusChange = false; + }, + + // set(); + // set(, ); + set: function() { + var sectionId, config; + + if (typeof arguments[0] === 'object') { + config = arguments[0]; + } else if (typeof arguments[0] === 'string' && + typeof arguments[1] === 'object') { + sectionId = arguments[0]; + config = arguments[1]; + if (!_sections[sectionId]) { + throw new Error('Section "' + sectionId + '" doesn\'t exist!'); } + } else { + return; + } - for (var key in config) { - if (GlobalConfig[key] !== undefined) { - if (sectionId) { - _sections[sectionId][key] = config[key]; - } else if (config[key] !== undefined) { - GlobalConfig[key] = config[key]; - } + for (var key in config) { + if (GlobalConfig[key] !== undefined) { + if (sectionId) { + _sections[sectionId][key] = config[key]; + } else if (config[key] !== undefined) { + GlobalConfig[key] = config[key]; } } + } - if (sectionId) { - // remove "undefined" items - _sections[sectionId] = extend({}, _sections[sectionId]); - } - }, - - // add(); - // add(, ); - add: function() { - var sectionId; - var config = {}; - - if (typeof arguments[0] === 'object') { - config = arguments[0]; - } else if (typeof arguments[0] === 'string' && - typeof arguments[1] === 'object') { - sectionId = arguments[0]; - config = arguments[1]; - } - - if (!sectionId) { - sectionId = (typeof config.id === 'string') ? config.id : generateId(); - } - - if (_sections[sectionId]) { - throw new Error('Section "' + sectionId + '" has already existed!'); - } - - _sections[sectionId] = {}; - _sectionCount++; - - SpatialNavigation.set(sectionId, config); + if (sectionId) { + // remove "undefined" items + _sections[sectionId] = extend({}, _sections[sectionId]); + } + }, + + // add(); + // add(, ); + add: function() { + var sectionId; + var config = {}; + + if (typeof arguments[0] === 'object') { + config = arguments[0]; + } else if (typeof arguments[0] === 'string' && + typeof arguments[1] === 'object') { + sectionId = arguments[0]; + config = arguments[1]; + } - return sectionId; - }, + if (!sectionId) { + sectionId = (typeof config.id === 'string') ? config.id : generateId(); + } - remove: function(sectionId) { - if (!sectionId || typeof sectionId !== 'string') { - throw new Error('Please assign the "sectionId"!'); - } - if (_sections[sectionId]) { - _sections[sectionId] = undefined; - _sections = extend({}, _sections); - _sectionCount--; - return true; - } - return false; - }, + if (_sections[sectionId]) { + throw new Error('Section "' + sectionId + '" has already existed!'); + } - disable: function(sectionId) { - if (_sections[sectionId]) { - _sections[sectionId].disabled = true; - return true; - } - return false; - }, + _sections[sectionId] = {}; + _sectionCount++; - enable: function(sectionId) { - if (_sections[sectionId]) { - _sections[sectionId].disabled = false; - return true; - } - return false; - }, + SpatialNavigation.set(sectionId, config); - pause: function() { - _pause = true; - }, + return sectionId; + }, - resume: function() { - _pause = false; - }, + remove: function(sectionId) { + if (!sectionId || typeof sectionId !== 'string') { + throw new Error('Please assign the "sectionId"!'); + } + if (_sections[sectionId]) { + _sections[sectionId] = undefined; + _sections = extend({}, _sections); + _sectionCount--; + return true; + } + return false; + }, - // focus([silent]) - // focus(, [silent]) - // focus(, [silent]) - // Note: "silent" is optional and default to false - focus: function(elem, silent) { - var result = false; + disable: function(sectionId) { + if (_sections[sectionId]) { + _sections[sectionId].disabled = true; + return true; + } + return false; + }, - if (silent === undefined && typeof elem === 'boolean') { - silent = elem; - elem = undefined; - } + enable: function(sectionId) { + if (_sections[sectionId]) { + _sections[sectionId].disabled = false; + return true; + } + return false; + }, + + pause: function() { + _pause = true; + }, + + resume: function() { + _pause = false; + }, + + // focus([silent]) + // focus(, [silent]) + // focus(, [silent]) + // Note: "silent" is optional and default to false + focus: function(elem, silent) { + var result = false; + + if (silent === undefined && typeof elem === 'boolean') { + silent = elem; + elem = undefined; + } - var autoPause = !_pause && silent; + var autoPause = !_pause && silent; - if (autoPause) { - SpatialNavigation.pause(); - } + if (autoPause) { + SpatialNavigation.pause(); + } - if (!elem) { - result = focusSection(); - } else { - if (typeof elem === 'string') { - if (_sections[elem]) { - result = focusSection(elem); - } else { - result = focusExtendedSelector(elem); - } + if (!elem) { + result = focusSection(); + } else { + if (typeof elem === 'string') { + if (_sections[elem]) { + result = focusSection(elem); } else { - if ($ && elem instanceof $) { - elem = elem.get(0); - } - - var nextSectionId = getSectionId(elem); - if (isNavigable(elem, nextSectionId)) { - result = focusElement(elem, nextSectionId); - } + result = focusExtendedSelector(elem); + } + } else { + if ($ && elem instanceof $) { + elem = elem.get(0); } - } - if (autoPause) { - SpatialNavigation.resume(); + var nextSectionId = getSectionId(elem); + if (isNavigable(elem, nextSectionId)) { + result = focusElement(elem, nextSectionId); + } } + } - return result; - }, + if (autoPause) { + SpatialNavigation.resume(); + } - // move() - // move(, ) - move: function(direction, selector) { - direction = direction.toLowerCase(); - if (!REVERSE[direction]) { - return false; - } + return result; + }, - var elem = selector ? - parseSelector(selector)[0] : getCurrentFocusedElement(); - if (!elem) { - return false; - } + // move() + // move(, ) + move: function(direction, selector) { + direction = direction.toLowerCase(); + if (!REVERSE[direction]) { + return false; + } - var sectionId = getSectionId(elem); - if (!sectionId) { - return false; - } + var elem = selector ? + parseSelector(selector)[0] : getCurrentFocusedElement(); + if (!elem) { + return false; + } - var willmoveProperties = { - direction: direction, - sectionId: sectionId, - cause: 'api' - }; + var sectionId = getSectionId(elem); + if (!sectionId) { + return false; + } - if (!fireEvent(elem, 'willmove', willmoveProperties)) { - return false; - } + var willmoveProperties = { + direction: direction, + sectionId: sectionId, + cause: 'api' + }; - return focusNext(direction, elem, sectionId); - }, + if (!fireEvent(elem, 'willmove', willmoveProperties)) { + return false; + } - // makeFocusable() - // makeFocusable() - makeFocusable: function(sectionId) { - var doMakeFocusable = function(section) { - var tabIndexIgnoreList = section.tabIndexIgnoreList !== undefined ? - section.tabIndexIgnoreList : GlobalConfig.tabIndexIgnoreList; - parseSelector(section.selector).forEach(function(elem) { - if (!matchSelector(elem, tabIndexIgnoreList)) { - if (!elem.getAttribute('tabindex')) { - elem.setAttribute('tabindex', '-1'); - } + return focusNext(direction, elem, sectionId); + }, + + // makeFocusable() + // makeFocusable() + makeFocusable: function(sectionId) { + var doMakeFocusable = function(section) { + var tabIndexIgnoreList = section.tabIndexIgnoreList !== undefined ? + section.tabIndexIgnoreList : GlobalConfig.tabIndexIgnoreList; + parseSelector(section.selector).forEach(function(elem) { + if (!matchSelector(elem, tabIndexIgnoreList)) { + if (!elem.getAttribute('tabindex')) { + elem.setAttribute('tabindex', '-1'); } - }); - }; - - if (sectionId) { - if (_sections[sectionId]) { - doMakeFocusable(_sections[sectionId]); - } else { - throw new Error('Section "' + sectionId + '" doesn\'t exist!'); } - } else { - for (var id in _sections) { - doMakeFocusable(_sections[id]); - } - } - }, + }); + }; - setDefaultSection: function(sectionId) { - if (!sectionId) { - _defaultSectionId = ''; - } else if (!_sections[sectionId]) { - throw new Error('Section "' + sectionId + '" doesn\'t exist!'); + if (sectionId) { + if (_sections[sectionId]) { + doMakeFocusable(_sections[sectionId]); } else { - _defaultSectionId = sectionId; + throw new Error('Section "' + sectionId + '" doesn\'t exist!'); + } + } else { + for (var id in _sections) { + doMakeFocusable(_sections[id]); } } - }; + }, - window.SpatialNavigation = SpatialNavigation; + setDefaultSection: function(sectionId) { + if (!sectionId) { + _defaultSectionId = ''; + } else if (!_sections[sectionId]) { + throw new Error('Section "' + sectionId + '" doesn\'t exist!'); + } else { + _defaultSectionId = sectionId; + } + } +}; - /********************/ - /* jQuery Interface */ - /********************/ - if ($) { - $.SpatialNavigation = function() { - SpatialNavigation.init(); - - if (arguments.length > 0) { - if ($.isPlainObject(arguments[0])) { - return SpatialNavigation.add(arguments[0]); - } else if ($.type(arguments[0]) === 'string' && - $.isFunction(SpatialNavigation[arguments[0]])) { - return SpatialNavigation[arguments[0]] - .apply(SpatialNavigation, [].slice.call(arguments, 1)); - } +/********************/ +/* jQuery Interface */ +/********************/ +if ($) { + $.SpatialNavigation = function() { + SpatialNavigation.init(); + + if (arguments.length > 0) { + if ($.isPlainObject(arguments[0])) { + return SpatialNavigation.add(arguments[0]); + } else if ($.type(arguments[0]) === 'string' && + $.isFunction(SpatialNavigation[arguments[0]])) { + return SpatialNavigation[arguments[0]] + .apply(SpatialNavigation, [].slice.call(arguments, 1)); } + } - return $.extend({}, SpatialNavigation); - }; + return $.extend({}, SpatialNavigation); + }; - $.fn.SpatialNavigation = function() { - var config; + $.fn.SpatialNavigation = function() { + var config; - if ($.isPlainObject(arguments[0])) { - config = arguments[0]; - } else { - config = { - id: arguments[0] - }; - } + if ($.isPlainObject(arguments[0])) { + config = arguments[0]; + } else { + config = { + id: arguments[0] + }; + } - config.selector = this; + config.selector = this; - SpatialNavigation.init(); - if (config.id) { - SpatialNavigation.remove(config.id); - } - SpatialNavigation.add(config); - SpatialNavigation.makeFocusable(config.id); + SpatialNavigation.init(); + if (config.id) { + SpatialNavigation.remove(config.id); + } + SpatialNavigation.add(config); + SpatialNavigation.makeFocusable(config.id); - return this; - }; - } -})(window.jQuery); + return this; + }; +} + +return SpatialNavigation; +}));