forked from lbarchive/jquery-jknav
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jquery.jknav.js
204 lines (198 loc) · 5.59 KB
/
jquery.jknav.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/**
* @preserve jknav
* @name jquery.jknav.js
* @author Yu-Jie Lin
* @version 0.5.2
* @date 2014-03-04
* @copyright (C) 2010-2013 Yu-Jie Lin <[email protected]>
* @copyright (C) 2014 Kyle J. Davis
* @license MIT License
* @homepage https://github.com/livibetter/jquery-jknav
* @example http://livibetter.github.io/jquery-jknav/jquery.jknav.demo.html
*/
(function ($) {
/**
* Print out debug infomation via console object
* @param {String} debug information
*/
function log (message) {
var console = window.console;
if ($.jknav.DEBUG && console && console.log)
console.log('jknav: ' + message);
}
/**
* Simple check if it is webkit, inspired by the jQuery Migrate plugin
*/
function isWebkit(ua) {
return ua.toLowerCase().match(/(chrome|webkit)[ \/]([\w.]+)/)
};
/**
* Add jQuery objects to navgation list
*
* @param {Function} callback Callback function to be invoked after plugin scroll to item
* @param {String} name Navagation set name
* @return {jQuery} <code>this</code> for chaining
*/
$.fn.jknav = function (callback, name) {
if (name == null)
name = 'default';
if ($.jknav.items[name] == null)
$.jknav.items[name] = [];
return this.each(function () {
$.jknav.items[name].push([this, callback]);
$.jknav.items[name].sort(function (a, b) {
var a_top = $(a[0]).offset().top;
var b_top = $(b[0]).offset().top;
if (a_top < b_top)
return -1;
if (a_top > b_top)
return 1;
if (a_top == b_top) {
var a_left = $(a[0]).offset().left;
var b_left = $(b[0]).offset().left;
if (a_left < b_left)
return -1;
if (a_left > b_left)
return 1;
return 0;
}
});
});
};
/**
* A helper to do callback
* @param {Number} index of the item navgation set
* @param {Object} opts Options
*/
function do_callback(index, opts) {
var callback = $.jknav.items[opts.name][index][1];
if (callback)
callback($.jknav.items[opts.name][index][0]);
}
/**
* Calculate the index of next item
* @param {Number} offset Indicates move forword or backward
* @param {Object} opts Options
*/
function calc_index(offset, opts) {
var index = $.jknav.index[opts.name];
log('Calculating index for ' + opts.name + ', current index = ' + index);
if (index == null) {
// Initialize index
var top = $($.jknav.TARGET).scrollTop();
log($.jknav.TARGET + ' top = ' + top);
$.each($.jknav.items[opts.name], function (idx, item) {
// Got a strange case: top = 180, item_top = 180.35...
var item_top = Math.floor($(item).offset().top);
if (top >= item_top)
index = idx;
});
if (index == null) {
if (offset > 0)
index = 0
else
index = $.jknav.items[opts.name].length - 1;
}
else {
if (offset > 0 && ++index >= $.jknav.items[opts.name].length)
index = 0
else if (offset < 0 && top == Math.floor($($.jknav.items[opts.name][index]).offset().top) && --index < 0)
index = $.jknav.items[opts.name].length - 1;
}
}
else {
if (!opts.circular && ((index == 0 && offset == -1) || (index == $.jknav.items[opts.name].length - 1 && offset == 1)))
return index;
index += offset;
if (index >= $.jknav.items[opts.name].length)
index = 0;
if (index < 0)
index = $.jknav.items[opts.name].length - 1;
}
log('new index = ' + index);
$.jknav.index[opts.name] = index;
return index;
}
/**
* Keyup handler
* @param {Event} e jQuery event object
* @param {Object} opts Options
*/
function keyup(e, opts) {
if (e.target.tagName.toLowerCase() == 'input' ||
e.target.tagName.toLowerCase() == 'button' ||
e.target.tagName.toLowerCase() == 'select' ||
e.target.tagName.toLowerCase() == 'textarea') {
log('keyup: ' + e.target.tagName + ', target is INPUT ignored.');
return
}
var ch = String.fromCharCode(e.keyCode).toLowerCase();
log('keyup: ' + e.target.tagName + ', key: ' + ch);
if (ch == opts.up.toLowerCase() || ch == opts.down.toLowerCase()) {
if (opts.reevaluate)
$.jknav.index[opts.name] = null;
var index = calc_index((ch == opts.down.toLowerCase()) ? 1 : -1, opts);
var $item = $($.jknav.items[opts.name][index][0]);
$($.jknav.TARGET).animate(
{
scrollLeft: Math.floor($item.offset().left),
scrollTop: Math.floor($item.offset().top)
},
opts.speed,
opts.easing,
function () {
do_callback(index, opts)
}
);
}
}
$.jknav = {
index: {},
items: {},
opts: {},
default_options: {
up: 'k',
down: 'j',
name: 'default',
easing: 'swing',
speed: 'normal',
circular: true,
reevaluate: false
},
DEBUG: false,
TARGET_KEYUP: 'html',
// IE, Firefox, and Opera must use <html> to scroll
// Webkit must use <body> to scroll
TARGET: isWebkit(navigator.userAgent) ? 'body' : 'html',
/**
* Initialization function
* @param {Object} options Options
*/
init: function (options) {
var opts = $.extend($.extend({}, $.jknav.default_options), options);
$.jknav.index[opts.name] = null;
$.jknav.opts[opts.name] = opts;
$($.jknav.TARGET_KEYUP).keyup(function (e) {
keyup(e, opts);
});
log('new set "' + opts.name + '" initialzed.');
},
/**
* Navigate up
* @param {String} name name of set
*/
up: function (name) {
var opts = $.jknav.opts[name || 'default'];
keyup({target: {tagName: ''}, keyCode: opts.up.charCodeAt(0)}, opts);
},
/**
* Navigate down
* @param {String} name name of set
*/
down: function (name) {
var opts = $.jknav.opts[name || 'default'];
keyup({target: {tagName: ''}, keyCode: opts.down.charCodeAt(0)}, opts);
}
};
})(jQuery);
// vim: ts=2: sw=2