dom-navigator
Advanced tools
Comparing version 1.0.0 to 1.0.1
{ | ||
"name": "dom-navigator", | ||
"description": "Library that allow keyboard navigation through DOM elements (←↑→↓).", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"main": "dist/dom-navigator.js", | ||
@@ -6,0 +6,0 @@ "license": "MIT", |
@@ -1,2 +0,2 @@ | ||
/*! dom-navigator - v1.0.0 - 2014-08-13 | ||
/*! dom-navigator - v1.0.1 - 2014-08-16 | ||
* https://github.com/rmariuzzo/dom-navigator | ||
@@ -11,7 +11,7 @@ * Copyright (c) 2014 Rubens Mariuzzo; Licensed MIT */ | ||
define([], function() { | ||
return factory(jQuery); | ||
return factory(window.jQuery); | ||
}); | ||
} else { | ||
// Browser globals | ||
factory(jQuery); | ||
factory(window.jQuery); | ||
} | ||
@@ -59,2 +59,16 @@ | ||
/** | ||
* Unbox an object from jQuery or array. | ||
* | ||
* @param obj {Object} The object to unbox. | ||
* | ||
* @return {Element} An element. | ||
*/ | ||
function unboxElement(obj) { | ||
if (obj.jquery || Array.isArray(obj)) { | ||
return obj[0]; | ||
} | ||
return obj; | ||
} | ||
//-------------// | ||
@@ -64,7 +78,16 @@ // Constructor // | ||
var Navigator = function(element, options) { | ||
/** | ||
* Create a new DOM Navigator. | ||
* | ||
* @param container {Element} The container of the element to navigate. | ||
* @param options {Object} The options to configure the DOM navigator. | ||
* | ||
* @return void. | ||
*/ | ||
var Navigator = function(container, options) { | ||
this.$doc = window.document; | ||
this.$element = element; | ||
this.$container = container; | ||
this.$options = extend({}, Navigator.defaults, options); | ||
this.$selected = null; | ||
this.$keydownHandler = null; | ||
this.$keys = {}; | ||
@@ -75,6 +98,8 @@ this.$keys[this.$options.left] = this.left; | ||
this.$keys[this.$options.down] = this.down; | ||
this.$keydownHandler = null; | ||
this.enable(); | ||
}; | ||
/** | ||
* Defaults options. | ||
*/ | ||
Navigator.defaults = { | ||
@@ -88,2 +113,12 @@ selected: 'selected', | ||
/** | ||
* Direction constants. | ||
*/ | ||
var DIRECTION = { | ||
left: 'left', | ||
up: 'up', | ||
right: 'right', | ||
down: 'down' | ||
}; | ||
//---------// | ||
@@ -93,2 +128,7 @@ // Methods // | ||
/** | ||
* Enable this navigator. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.enable = function() { | ||
@@ -102,2 +142,7 @@ var self = this; | ||
/** | ||
* Disable this navigator. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.disable = function() { | ||
@@ -109,9 +154,19 @@ if (this.$keydownHandler) { | ||
/** | ||
* Destroy this navigator removing any event registered and any other data. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.destroy = function() { | ||
this.disable(); | ||
if (this.$element.domNavigator) { | ||
delete this.$element.domNavigator; | ||
if (this.$container.domNavigator) { | ||
delete this.$container.domNavigator; | ||
} | ||
}; | ||
/** | ||
* Navigate left to the next element if any. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.left = function() { | ||
@@ -125,5 +180,3 @@ if (!this.$selected) { | ||
var next = this.elementsBefore(left, Infinity).reduce(function(prev, curr) { | ||
curr = $(curr); | ||
var currDistance = curr.position(); | ||
currDistance = Math.abs(left - currDistance.left) + Math.abs(top - currDistance.top); | ||
var currDistance = Math.abs(left - curr.offsetLeft) + Math.abs(top - curr.offsetTop); | ||
if (currDistance < prev.distance) { | ||
@@ -140,6 +193,11 @@ return { | ||
this.select(next.element); | ||
this.select(next.element, DIRECTION.left); | ||
} | ||
}; | ||
/** | ||
* Navigate up to the next element if any. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.up = function() { | ||
@@ -153,5 +211,3 @@ if (!this.$selected) { | ||
var next = this.elementsBefore(Infinity, top).reduce(function(prev, curr) { | ||
curr = $(curr); | ||
var currDistance = curr.position(); | ||
currDistance = Math.abs(left - currDistance.left) + Math.abs(top - currDistance.top); | ||
var currDistance = Math.abs(left - curr.offsetLeft) + Math.abs(top - curr.offsetTop); | ||
if (currDistance < prev.distance) { | ||
@@ -168,6 +224,11 @@ return { | ||
this.select(next.element); | ||
this.select(next.element, DIRECTION.up); | ||
} | ||
}; | ||
/** | ||
* Navigate right to the next element if any. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.right = function() { | ||
@@ -181,5 +242,3 @@ if (!this.$selected) { | ||
var next = this.elementsAfter(left, 0).reduce(function(prev, curr) { | ||
curr = $(curr); | ||
var currDistance = curr.position(); | ||
currDistance = Math.abs(currDistance.left - left) + Math.abs(currDistance.top - top); | ||
var currDistance = Math.abs(curr.offsetLeft - left) + Math.abs(curr.offsetTop - top); | ||
if (currDistance < prev.distance) { | ||
@@ -196,6 +255,9 @@ return { | ||
this.select(next.element); | ||
this.select(next.element, DIRECTION.right); | ||
} | ||
}; | ||
/** | ||
* Navigate down to the next element if any. | ||
*/ | ||
Navigator.prototype.down = function() { | ||
@@ -209,5 +271,3 @@ if (!this.$selected) { | ||
var next = this.elementsAfter(0, top).reduce(function(prev, curr) { | ||
curr = $(curr); | ||
var currDistance = curr.position(); | ||
currDistance = Math.abs(currDistance.left - left) + Math.abs(currDistance.top - top); | ||
var currDistance = Math.abs(curr.offsetLeft - left) + Math.abs(curr.offsetTop - top); | ||
if (currDistance < prev.distance) { | ||
@@ -224,6 +284,11 @@ return { | ||
this.select(next.element); | ||
this.select(next.element, DIRECTION.down); | ||
} | ||
}; | ||
/** | ||
* Return the selected DOM element. | ||
* | ||
* @return {Element} The selected DOM element. | ||
*/ | ||
Navigator.prototype.selected = function() { | ||
@@ -233,24 +298,92 @@ return this.$selected; | ||
Navigator.prototype.select = function(element) { | ||
if (element && element !== this.$selected) { | ||
// Unbox element from jQuery or array. | ||
if (element.jquery || Array.isArray(element)) { | ||
element = element[0]; | ||
/** | ||
* Select the given element. | ||
* | ||
* @param el {Element} The DOM element to select. | ||
* | ||
* @return void | ||
*/ | ||
Navigator.prototype.select = function(el, direction) { | ||
// Is there an element or is it selected? | ||
if (!el || el === this.$selected) { | ||
return; // Nothing to do here. | ||
} | ||
el = unboxElement(el); | ||
// Unselect previous element. | ||
if (this.$selected) { | ||
removeClass(this.$selected, this.$options.selected); | ||
} | ||
// Scroll to given element. | ||
this.scrollTo(el, direction); | ||
// Select given element. | ||
addClass(el, this.$options.selected); | ||
this.$selected = el; | ||
}; | ||
/** | ||
* Scroll the container to an element. | ||
* | ||
* @param el {Element} The destination element. | ||
* @param direction {String} The direction of the current navigation. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.scrollTo = function(el, direction) { | ||
if (!this.inContainerViewport(el)) { | ||
switch (direction) { | ||
case DIRECTION.left: | ||
// TODO. | ||
break; | ||
case DIRECTION.up: | ||
this.$container.scrollTop = el.offsetTop - this.$container.offsetTop; | ||
break; | ||
case DIRECTION.right: | ||
// TODO. | ||
break; | ||
case DIRECTION.down: | ||
this.$container.scrollTop = el.offsetTop - this.$container.offsetTop - (this.$container.offsetHeight - el.offsetHeight); | ||
break; | ||
} | ||
// Unselect previous element. | ||
if (this.$selected) { | ||
removeClass(this.$selected, this.$options.selected); | ||
} | ||
// Select given element. | ||
addClass(element, this.$options.selected); | ||
this.$selected = element; | ||
} | ||
}; | ||
/** | ||
* Indicate if an element is in the container viewport. | ||
* | ||
* @param el {Element} The element to check. | ||
* | ||
* @return {Boolean} true if the given element is in the container viewport, otherwise false. | ||
*/ | ||
Navigator.prototype.inContainerViewport = function(el) { | ||
el = unboxElement(el); | ||
// Check on left side. | ||
if (el.offsetLeft - this.$container.scrollLeft < this.$container.offsetLeft) { | ||
return false; | ||
} | ||
// Check on top side. | ||
if (el.offsetTop - this.$container.scrollTop < this.$container.offsetTop) { | ||
return false; | ||
} | ||
// Check on right side. | ||
if ((el.offsetLeft + el.offsetWidth - this.$container.scrollLeft) > (this.$container.offsetLeft + this.$container.offsetWidth)) { | ||
return false; | ||
} | ||
// Check on down side. | ||
if ((el.offsetTop + el.offsetHeight - this.$container.scrollTop) > (this.$container.offsetTop + this.$container.offsetHeight)) { | ||
return false; | ||
} | ||
return true; | ||
}; | ||
/** | ||
* Return an array of the navigable elements. | ||
* | ||
* @return {Array} An array of elements. | ||
*/ | ||
Navigator.prototype.elements = function() { | ||
var children = []; | ||
for (var i = this.$element.children.length; i--;) { | ||
for (var i = this.$container.children.length; i--;) { | ||
// Skip comment nodes on IE8 | ||
if (this.$element.children[i].nodeType !== 8) { | ||
children.unshift(this.$element.children[i]); | ||
if (this.$container.children[i].nodeType !== 8) { | ||
children.unshift(this.$container.children[i]); | ||
} | ||
@@ -261,2 +394,10 @@ } | ||
/** | ||
* Return an array of navigable elements after an offset. | ||
* | ||
* @param left {Integer} The left offset. | ||
* @param top {Integer} The top offset. | ||
* | ||
* @return {Array} An array of elements. | ||
*/ | ||
Navigator.prototype.elementsAfter = function(left, top) { | ||
@@ -268,2 +409,10 @@ return this.elements().filter(function(el) { | ||
/** | ||
* Return an array of navigable elements before an offset. | ||
* | ||
* @param left {Integer} The left offset. | ||
* @param top {Integer} The top offset. | ||
* | ||
* @return {Array} An array of elements. | ||
*/ | ||
Navigator.prototype.elementsBefore = function(left, top) { | ||
@@ -275,2 +424,9 @@ return this.elements().filter(function(el) { | ||
/** | ||
* Handle the keydown event. | ||
* | ||
* @param {Event} The event object. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.handleKeydown = function(event) { | ||
@@ -277,0 +433,0 @@ if (this.$keys[event.which]) { |
@@ -1,4 +0,4 @@ | ||
/*! dom-navigator - v1.0.0 - 2014-08-13 | ||
/*! dom-navigator - v1.0.1 - 2014-08-16 | ||
* https://github.com/rmariuzzo/dom-navigator | ||
* Copyright (c) 2014 Rubens Mariuzzo; Licensed MIT */ | ||
!function(a){"function"==typeof define&&define.amd?define([],function(){return a(jQuery)}):a(jQuery)}(function(a){function b(a){a=a||{};for(var b=1;b<arguments.length;b++)if(arguments[b])for(var c in arguments[b])arguments[b].hasOwnProperty(c)&&(a[c]=arguments[b][c]);return a}function c(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function d(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}var e=function(a,c){this.$doc=window.document,this.$element=a,this.$options=b({},e.defaults,c),this.$selected=null,this.$keys={},this.$keys[this.$options.left]=this.left,this.$keys[this.$options.up]=this.up,this.$keys[this.$options.right]=this.right,this.$keys[this.$options.down]=this.down,this.$keydownHandler=null,this.enable()};if(e.defaults={selected:"selected",left:37,up:38,right:39,down:40},e.prototype.enable=function(){var a=this;this.$keydownHandler=function(b){a.handleKeydown.call(a,b)},this.$doc.addEventListener("keydown",this.$keydownHandler)},e.prototype.disable=function(){this.$keydownHandler&&this.$doc.removeEventListener("keydown",this.$keydownHandler)},e.prototype.destroy=function(){this.disable(),this.$element.domNavigator&&delete this.$element.domNavigator},e.prototype.left=function(){if(this.$selected){var b=this.$selected.offsetLeft-1,c=this.$selected.offsetTop,d=this.elementsBefore(b,1/0).reduce(function(d,e){e=a(e);var f=e.position();return f=Math.abs(b-f.left)+Math.abs(c-f.top),f<d.distance?{distance:f,element:e}:d},{distance:1/0});this.select(d.element)}else this.select(this.elements()[0])},e.prototype.up=function(){if(this.$selected){var b=this.$selected.offsetLeft,c=this.$selected.offsetTop-1,d=this.elementsBefore(1/0,c).reduce(function(d,e){e=a(e);var f=e.position();return f=Math.abs(b-f.left)+Math.abs(c-f.top),f<d.distance?{distance:f,element:e}:d},{distance:1/0});this.select(d.element)}else this.select(this.elements()[0])},e.prototype.right=function(){if(this.$selected){var b=this.$selected.offsetLeft+this.$selected.offsetWidth,c=this.$selected.offsetTop,d=this.elementsAfter(b,0).reduce(function(d,e){e=a(e);var f=e.position();return f=Math.abs(f.left-b)+Math.abs(f.top-c),f<d.distance?{distance:f,element:e}:d},{distance:1/0});this.select(d.element)}else this.select(this.elements()[0])},e.prototype.down=function(){if(this.$selected){var b=this.$selected.offsetLeft,c=this.$selected.offsetTop+this.$selected.offsetHeight,d=this.elementsAfter(0,c).reduce(function(d,e){e=a(e);var f=e.position();return f=Math.abs(f.left-b)+Math.abs(f.top-c),f<d.distance?{distance:f,element:e}:d},{distance:1/0});this.select(d.element)}else this.select(this.elements()[0])},e.prototype.selected=function(){return this.$selected},e.prototype.select=function(a){a&&a!==this.$selected&&((a.jquery||Array.isArray(a))&&(a=a[0]),this.$selected&&d(this.$selected,this.$options.selected),c(a,this.$options.selected),this.$selected=a)},e.prototype.elements=function(){for(var a=[],b=this.$element.children.length;b--;)8!==this.$element.children[b].nodeType&&a.unshift(this.$element.children[b]);return a},e.prototype.elementsAfter=function(a,b){return this.elements().filter(function(c){return c.offsetLeft>=a&&c.offsetTop>=b})},e.prototype.elementsBefore=function(a,b){return this.elements().filter(function(c){return c.offsetLeft<=a&&c.offsetTop<=b})},e.prototype.handleKeydown=function(a){this.$keys[a.which]&&(a.preventDefault(),this.$keys[a.which].call(this))},window.DomNavigator=e,a){var f=a.fn.domNavigator;a.fn.domNavigator=function(a){var b,c=Array.prototype.slice.call(arguments,1);return this.each(function(){this.domNavigator||(this.domNavigator=new e(this,"object"==typeof a&&a)),"string"==typeof a&&this.domNavigator[a]&&(b=this.domNavigator[a].apply(this.domNavigator,c))}),void 0===b&&(b=this),b},a.fn.domNavigator.Constructor=e,a.fn.domNavigator.noConflict=function(){return a.fn.domNavigator=f,this}}}); | ||
!function(a){"function"==typeof define&&define.amd?define([],function(){return a(window.jQuery)}):a(window.jQuery)}(function(a){function b(a){a=a||{};for(var b=1;b<arguments.length;b++)if(arguments[b])for(var c in arguments[b])arguments[b].hasOwnProperty(c)&&(a[c]=arguments[b][c]);return a}function c(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function d(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function e(a){return a.jquery||Array.isArray(a)?a[0]:a}var f=function(a,c){this.$doc=window.document,this.$container=a,this.$options=b({},f.defaults,c),this.$selected=null,this.$keydownHandler=null,this.$keys={},this.$keys[this.$options.left]=this.left,this.$keys[this.$options.up]=this.up,this.$keys[this.$options.right]=this.right,this.$keys[this.$options.down]=this.down,this.enable()};f.defaults={selected:"selected",left:37,up:38,right:39,down:40};var g={left:"left",up:"up",right:"right",down:"down"};if(f.prototype.enable=function(){var a=this;this.$keydownHandler=function(b){a.handleKeydown.call(a,b)},this.$doc.addEventListener("keydown",this.$keydownHandler)},f.prototype.disable=function(){this.$keydownHandler&&this.$doc.removeEventListener("keydown",this.$keydownHandler)},f.prototype.destroy=function(){this.disable(),this.$container.domNavigator&&delete this.$container.domNavigator},f.prototype.left=function(){if(this.$selected){var a=this.$selected.offsetLeft-1,b=this.$selected.offsetTop,c=this.elementsBefore(a,1/0).reduce(function(c,d){var e=Math.abs(a-d.offsetLeft)+Math.abs(b-d.offsetTop);return e<c.distance?{distance:e,element:d}:c},{distance:1/0});this.select(c.element,g.left)}else this.select(this.elements()[0])},f.prototype.up=function(){if(this.$selected){var a=this.$selected.offsetLeft,b=this.$selected.offsetTop-1,c=this.elementsBefore(1/0,b).reduce(function(c,d){var e=Math.abs(a-d.offsetLeft)+Math.abs(b-d.offsetTop);return e<c.distance?{distance:e,element:d}:c},{distance:1/0});this.select(c.element,g.up)}else this.select(this.elements()[0])},f.prototype.right=function(){if(this.$selected){var a=this.$selected.offsetLeft+this.$selected.offsetWidth,b=this.$selected.offsetTop,c=this.elementsAfter(a,0).reduce(function(c,d){var e=Math.abs(d.offsetLeft-a)+Math.abs(d.offsetTop-b);return e<c.distance?{distance:e,element:d}:c},{distance:1/0});this.select(c.element,g.right)}else this.select(this.elements()[0])},f.prototype.down=function(){if(this.$selected){var a=this.$selected.offsetLeft,b=this.$selected.offsetTop+this.$selected.offsetHeight,c=this.elementsAfter(0,b).reduce(function(c,d){var e=Math.abs(d.offsetLeft-a)+Math.abs(d.offsetTop-b);return e<c.distance?{distance:e,element:d}:c},{distance:1/0});this.select(c.element,g.down)}else this.select(this.elements()[0])},f.prototype.selected=function(){return this.$selected},f.prototype.select=function(a,b){a&&a!==this.$selected&&(a=e(a),this.$selected&&d(this.$selected,this.$options.selected),this.scrollTo(a,b),c(a,this.$options.selected),this.$selected=a)},f.prototype.scrollTo=function(a,b){if(!this.inContainerViewport(a))switch(b){case g.left:break;case g.up:this.$container.scrollTop=a.offsetTop-this.$container.offsetTop;break;case g.right:break;case g.down:this.$container.scrollTop=a.offsetTop-this.$container.offsetTop-(this.$container.offsetHeight-a.offsetHeight)}},f.prototype.inContainerViewport=function(a){return a=e(a),a.offsetLeft-this.$container.scrollLeft<this.$container.offsetLeft?!1:a.offsetTop-this.$container.scrollTop<this.$container.offsetTop?!1:a.offsetLeft+a.offsetWidth-this.$container.scrollLeft>this.$container.offsetLeft+this.$container.offsetWidth?!1:a.offsetTop+a.offsetHeight-this.$container.scrollTop>this.$container.offsetTop+this.$container.offsetHeight?!1:!0},f.prototype.elements=function(){for(var a=[],b=this.$container.children.length;b--;)8!==this.$container.children[b].nodeType&&a.unshift(this.$container.children[b]);return a},f.prototype.elementsAfter=function(a,b){return this.elements().filter(function(c){return c.offsetLeft>=a&&c.offsetTop>=b})},f.prototype.elementsBefore=function(a,b){return this.elements().filter(function(c){return c.offsetLeft<=a&&c.offsetTop<=b})},f.prototype.handleKeydown=function(a){this.$keys[a.which]&&(a.preventDefault(),this.$keys[a.which].call(this))},window.DomNavigator=f,a){var h=a.fn.domNavigator;a.fn.domNavigator=function(a){var b,c=Array.prototype.slice.call(arguments,1);return this.each(function(){this.domNavigator||(this.domNavigator=new f(this,"object"==typeof a&&a)),"string"==typeof a&&this.domNavigator[a]&&(b=this.domNavigator[a].apply(this.domNavigator,c))}),void 0===b&&(b=this),b},a.fn.domNavigator.Constructor=f,a.fn.domNavigator.noConflict=function(){return a.fn.domNavigator=h,this}}}); |
{ | ||
"name": "dom-navigator", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "Library that allow keyboard navigation through DOM elements (←↑→↓).", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -1,29 +0,73 @@ | ||
DOM Navigator | ||
============= | ||
# DOM Navigator | ||
> Library that allow keyboard navigation through DOM elements (←↑→↓). | ||
Installation | ||
------------ | ||
## Installation | ||
Using bower: | ||
#### Bower | ||
bower install domnavigator --save | ||
```sh | ||
bower install dom-navigator --save | ||
``` | ||
Using NPM: | ||
#### NPM | ||
npm install domnavigator --save | ||
```sh | ||
npm install dom-navigator --save | ||
``` | ||
Usage | ||
===== | ||
#### Manual | ||
Vanilla JavaScript: | ||
1. [Download the latest release](https://github.com/rmariuzzo/dom-navigator/releases). | ||
2. Then include `dom-navigator-###.min.js` into your HTML page. | ||
var el = document.querySelector('#grid'); | ||
var nav = new DomNavigator(el); | ||
## Usage | ||
#### Pure JavaScript | ||
```js | ||
var el = document.querySelector('#grid'); | ||
new DomNavigator(el); | ||
``` | ||
#### jQuery | ||
If jQuery is included you can use the library as a jQuery plugin: | ||
var el = $('#grid'); | ||
el.domNavigator(); | ||
```js | ||
var el = $('#grid'); | ||
el.domNavigator(); | ||
``` | ||
## Want to contribute? | ||
All help are more than welcome! | ||
#### Pre-requisites | ||
- [Node.js](http://nodejs.org/). | ||
- [Grunt](http://gruntjs.com/). | ||
- [Bower](http://bower.io/). | ||
#### Development Workflow | ||
1. **[Fork](https://github.com/rmariuzzo/dom-navigator/fork)** this respository. | ||
2. **Clone** your fork and create a feature branch from develop. | ||
git clone git@github.com:<your-username>/dom-navigator.git | ||
git fetch origin | ||
git checkout develop | ||
git checkout -b feature-<super-power> | ||
3. **Install** development dependencies. | ||
npm install | ||
bower install | ||
4. **Code** and be happy! | ||
5. **Test** your code using QUnit `grunt test`. | ||
6. Submit a **pull request** and grab popcorn. | ||
### Credits | ||
**dom-navigator** was created by [Rubens Mariuzzo](http://github.com/rmariuzzo) with all the love in the world. |
@@ -16,7 +16,7 @@ /* | ||
define([], function() { | ||
return factory(jQuery); | ||
return factory(window.jQuery); | ||
}); | ||
} else { | ||
// Browser globals | ||
factory(jQuery); | ||
factory(window.jQuery); | ||
} | ||
@@ -64,2 +64,16 @@ | ||
/** | ||
* Unbox an object from jQuery or array. | ||
* | ||
* @param obj {Object} The object to unbox. | ||
* | ||
* @return {Element} An element. | ||
*/ | ||
function unboxElement(obj) { | ||
if (obj.jquery || Array.isArray(obj)) { | ||
return obj[0]; | ||
} | ||
return obj; | ||
} | ||
//-------------// | ||
@@ -69,7 +83,16 @@ // Constructor // | ||
var Navigator = function(element, options) { | ||
/** | ||
* Create a new DOM Navigator. | ||
* | ||
* @param container {Element} The container of the element to navigate. | ||
* @param options {Object} The options to configure the DOM navigator. | ||
* | ||
* @return void. | ||
*/ | ||
var Navigator = function(container, options) { | ||
this.$doc = window.document; | ||
this.$element = element; | ||
this.$container = container; | ||
this.$options = extend({}, Navigator.defaults, options); | ||
this.$selected = null; | ||
this.$keydownHandler = null; | ||
this.$keys = {}; | ||
@@ -80,6 +103,8 @@ this.$keys[this.$options.left] = this.left; | ||
this.$keys[this.$options.down] = this.down; | ||
this.$keydownHandler = null; | ||
this.enable(); | ||
}; | ||
/** | ||
* Defaults options. | ||
*/ | ||
Navigator.defaults = { | ||
@@ -93,2 +118,12 @@ selected: 'selected', | ||
/** | ||
* Direction constants. | ||
*/ | ||
var DIRECTION = { | ||
left: 'left', | ||
up: 'up', | ||
right: 'right', | ||
down: 'down' | ||
}; | ||
//---------// | ||
@@ -98,2 +133,7 @@ // Methods // | ||
/** | ||
* Enable this navigator. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.enable = function() { | ||
@@ -107,2 +147,7 @@ var self = this; | ||
/** | ||
* Disable this navigator. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.disable = function() { | ||
@@ -114,9 +159,19 @@ if (this.$keydownHandler) { | ||
/** | ||
* Destroy this navigator removing any event registered and any other data. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.destroy = function() { | ||
this.disable(); | ||
if (this.$element.domNavigator) { | ||
delete this.$element.domNavigator; | ||
if (this.$container.domNavigator) { | ||
delete this.$container.domNavigator; | ||
} | ||
}; | ||
/** | ||
* Navigate left to the next element if any. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.left = function() { | ||
@@ -130,5 +185,3 @@ if (!this.$selected) { | ||
var next = this.elementsBefore(left, Infinity).reduce(function(prev, curr) { | ||
curr = $(curr); | ||
var currDistance = curr.position(); | ||
currDistance = Math.abs(left - currDistance.left) + Math.abs(top - currDistance.top); | ||
var currDistance = Math.abs(left - curr.offsetLeft) + Math.abs(top - curr.offsetTop); | ||
if (currDistance < prev.distance) { | ||
@@ -145,6 +198,11 @@ return { | ||
this.select(next.element); | ||
this.select(next.element, DIRECTION.left); | ||
} | ||
}; | ||
/** | ||
* Navigate up to the next element if any. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.up = function() { | ||
@@ -158,5 +216,3 @@ if (!this.$selected) { | ||
var next = this.elementsBefore(Infinity, top).reduce(function(prev, curr) { | ||
curr = $(curr); | ||
var currDistance = curr.position(); | ||
currDistance = Math.abs(left - currDistance.left) + Math.abs(top - currDistance.top); | ||
var currDistance = Math.abs(left - curr.offsetLeft) + Math.abs(top - curr.offsetTop); | ||
if (currDistance < prev.distance) { | ||
@@ -173,6 +229,11 @@ return { | ||
this.select(next.element); | ||
this.select(next.element, DIRECTION.up); | ||
} | ||
}; | ||
/** | ||
* Navigate right to the next element if any. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.right = function() { | ||
@@ -186,5 +247,3 @@ if (!this.$selected) { | ||
var next = this.elementsAfter(left, 0).reduce(function(prev, curr) { | ||
curr = $(curr); | ||
var currDistance = curr.position(); | ||
currDistance = Math.abs(currDistance.left - left) + Math.abs(currDistance.top - top); | ||
var currDistance = Math.abs(curr.offsetLeft - left) + Math.abs(curr.offsetTop - top); | ||
if (currDistance < prev.distance) { | ||
@@ -201,6 +260,9 @@ return { | ||
this.select(next.element); | ||
this.select(next.element, DIRECTION.right); | ||
} | ||
}; | ||
/** | ||
* Navigate down to the next element if any. | ||
*/ | ||
Navigator.prototype.down = function() { | ||
@@ -214,5 +276,3 @@ if (!this.$selected) { | ||
var next = this.elementsAfter(0, top).reduce(function(prev, curr) { | ||
curr = $(curr); | ||
var currDistance = curr.position(); | ||
currDistance = Math.abs(currDistance.left - left) + Math.abs(currDistance.top - top); | ||
var currDistance = Math.abs(curr.offsetLeft - left) + Math.abs(curr.offsetTop - top); | ||
if (currDistance < prev.distance) { | ||
@@ -229,6 +289,11 @@ return { | ||
this.select(next.element); | ||
this.select(next.element, DIRECTION.down); | ||
} | ||
}; | ||
/** | ||
* Return the selected DOM element. | ||
* | ||
* @return {Element} The selected DOM element. | ||
*/ | ||
Navigator.prototype.selected = function() { | ||
@@ -238,24 +303,92 @@ return this.$selected; | ||
Navigator.prototype.select = function(element) { | ||
if (element && element !== this.$selected) { | ||
// Unbox element from jQuery or array. | ||
if (element.jquery || Array.isArray(element)) { | ||
element = element[0]; | ||
/** | ||
* Select the given element. | ||
* | ||
* @param el {Element} The DOM element to select. | ||
* | ||
* @return void | ||
*/ | ||
Navigator.prototype.select = function(el, direction) { | ||
// Is there an element or is it selected? | ||
if (!el || el === this.$selected) { | ||
return; // Nothing to do here. | ||
} | ||
el = unboxElement(el); | ||
// Unselect previous element. | ||
if (this.$selected) { | ||
removeClass(this.$selected, this.$options.selected); | ||
} | ||
// Scroll to given element. | ||
this.scrollTo(el, direction); | ||
// Select given element. | ||
addClass(el, this.$options.selected); | ||
this.$selected = el; | ||
}; | ||
/** | ||
* Scroll the container to an element. | ||
* | ||
* @param el {Element} The destination element. | ||
* @param direction {String} The direction of the current navigation. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.scrollTo = function(el, direction) { | ||
if (!this.inContainerViewport(el)) { | ||
switch (direction) { | ||
case DIRECTION.left: | ||
// TODO. | ||
break; | ||
case DIRECTION.up: | ||
this.$container.scrollTop = el.offsetTop - this.$container.offsetTop; | ||
break; | ||
case DIRECTION.right: | ||
// TODO. | ||
break; | ||
case DIRECTION.down: | ||
this.$container.scrollTop = el.offsetTop - this.$container.offsetTop - (this.$container.offsetHeight - el.offsetHeight); | ||
break; | ||
} | ||
// Unselect previous element. | ||
if (this.$selected) { | ||
removeClass(this.$selected, this.$options.selected); | ||
} | ||
// Select given element. | ||
addClass(element, this.$options.selected); | ||
this.$selected = element; | ||
} | ||
}; | ||
/** | ||
* Indicate if an element is in the container viewport. | ||
* | ||
* @param el {Element} The element to check. | ||
* | ||
* @return {Boolean} true if the given element is in the container viewport, otherwise false. | ||
*/ | ||
Navigator.prototype.inContainerViewport = function(el) { | ||
el = unboxElement(el); | ||
// Check on left side. | ||
if (el.offsetLeft - this.$container.scrollLeft < this.$container.offsetLeft) { | ||
return false; | ||
} | ||
// Check on top side. | ||
if (el.offsetTop - this.$container.scrollTop < this.$container.offsetTop) { | ||
return false; | ||
} | ||
// Check on right side. | ||
if ((el.offsetLeft + el.offsetWidth - this.$container.scrollLeft) > (this.$container.offsetLeft + this.$container.offsetWidth)) { | ||
return false; | ||
} | ||
// Check on down side. | ||
if ((el.offsetTop + el.offsetHeight - this.$container.scrollTop) > (this.$container.offsetTop + this.$container.offsetHeight)) { | ||
return false; | ||
} | ||
return true; | ||
}; | ||
/** | ||
* Return an array of the navigable elements. | ||
* | ||
* @return {Array} An array of elements. | ||
*/ | ||
Navigator.prototype.elements = function() { | ||
var children = []; | ||
for (var i = this.$element.children.length; i--;) { | ||
for (var i = this.$container.children.length; i--;) { | ||
// Skip comment nodes on IE8 | ||
if (this.$element.children[i].nodeType !== 8) { | ||
children.unshift(this.$element.children[i]); | ||
if (this.$container.children[i].nodeType !== 8) { | ||
children.unshift(this.$container.children[i]); | ||
} | ||
@@ -266,2 +399,10 @@ } | ||
/** | ||
* Return an array of navigable elements after an offset. | ||
* | ||
* @param left {Integer} The left offset. | ||
* @param top {Integer} The top offset. | ||
* | ||
* @return {Array} An array of elements. | ||
*/ | ||
Navigator.prototype.elementsAfter = function(left, top) { | ||
@@ -273,2 +414,10 @@ return this.elements().filter(function(el) { | ||
/** | ||
* Return an array of navigable elements before an offset. | ||
* | ||
* @param left {Integer} The left offset. | ||
* @param top {Integer} The top offset. | ||
* | ||
* @return {Array} An array of elements. | ||
*/ | ||
Navigator.prototype.elementsBefore = function(left, top) { | ||
@@ -280,2 +429,9 @@ return this.elements().filter(function(el) { | ||
/** | ||
* Handle the keydown event. | ||
* | ||
* @param {Event} The event object. | ||
* | ||
* @return void. | ||
*/ | ||
Navigator.prototype.handleKeydown = function(event) { | ||
@@ -282,0 +438,0 @@ if (this.$keys[event.which]) { |
@@ -80,2 +80,25 @@ (function($) { | ||
module('jQuery#domNavigator("inContainerViewport", ...)', lifecycle); | ||
test('exists', function() { | ||
ok($.fn.domNavigator.Constructor.prototype.inContainerViewport, 'should exist'); | ||
}); | ||
test('should return expected values', function() { | ||
var target = this.target; | ||
var contTop = this.target.position().top; | ||
var contBottom = contTop + this.target.height(); | ||
var contScroll = this.target.scrollTop(); | ||
this.target.children().each(function(i, el) { | ||
el = $(el); | ||
var elTop = el.position().top; | ||
var elBottom = elTop + el.height(); | ||
var expected = elTop - contScroll >= contTop; | ||
expected = expected && elBottom - contScroll <= contBottom; | ||
ok(target.domNavigator("inContainerViewport", el) === expected, 'should return: ' + expected + ' for element: ' + el.text()); | ||
}); | ||
}); | ||
module('jQuery#domNavigator("left")', lifecycle); | ||
@@ -82,0 +105,0 @@ |
@@ -78,2 +78,24 @@ (function($) { | ||
module('DomNavigator.inContainerViewport()', lifecycle); | ||
test('exists', function() { | ||
ok(DomNavigator.prototype.inContainerViewport, 'should exist'); | ||
}); | ||
test('should return expected values', function() { | ||
var domNavigator = this.domNavigator; | ||
var contTop = this.target[0].offsetTop; | ||
var contBottom = contTop + this.target[0].offsetHeight; | ||
var contScroll = this.target[0].scrollTop; | ||
this.target.children().each(function(i, el) { | ||
var elTop = el.offsetTop; | ||
var elBottom = elTop + el.offsetHeight; | ||
var expected = elTop - contScroll >= contTop; | ||
expected = expected && elBottom - contScroll <= contBottom; | ||
ok(domNavigator.inContainerViewport(el) === expected, 'should return: ' + expected + ' for element: ' + el.textContent); | ||
}); | ||
}); | ||
module('DomNavigator.left()', lifecycle); | ||
@@ -80,0 +102,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
85628
23
1405
74