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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
|
/*
* jQuery ScrollSpy Plugin
* Author: @sxalexander, softwarespot
* Licensed under the MIT license
*/
(function jQueryScrollspy(window, $) {
// Plugin Logic
$.fn.extend({
scrollspy: function scrollspy(options, action) {
// If the options parameter is a string, then assume it's an 'action', therefore swap the parameters around
if (_isString(options)) {
var tempOptions = action;
// Set the action as the option parameter
action = options;
// Set to be the reference action pointed to
options = tempOptions;
}
// override the default options with those passed to the plugin
options = $.extend({}, _defaults, options);
// sanitize the following option with the default value if the predicate fails
_sanitizeOption(options, _defaults, 'container', _isObject);
// cache the jQuery object
var $container = $(options.container);
// check if it's a valid jQuery selector
if ($container.length === 0) {
return this;
}
// sanitize the following option with the default value if the predicate fails
_sanitizeOption(options, _defaults, 'namespace', _isString);
// check if the action is set to DESTROY/destroy
if (_isString(action) && action.toUpperCase() === 'DESTROY') {
$container.off('scroll.' + options.namespace);
return this;
}
// sanitize the following options with the default values if the predicates fails
_sanitizeOption(options, _defaults, 'buffer', $.isNumeric);
_sanitizeOption(options, _defaults, 'max', $.isNumeric);
_sanitizeOption(options, _defaults, 'min', $.isNumeric);
// callbacks
_sanitizeOption(options, _defaults, 'onEnter', $.isFunction);
_sanitizeOption(options, _defaults, 'onLeave', $.isFunction);
_sanitizeOption(options, _defaults, 'onLeaveTop', $.isFunction);
_sanitizeOption(options, _defaults, 'onLeaveBottom', $.isFunction);
_sanitizeOption(options, _defaults, 'onTick', $.isFunction);
if ($.isFunction(options.max)) {
options.max = options.max();
}
if ($.isFunction(options.min)) {
options.min = options.min();
}
// check if the mode is set to VERTICAL/vertical
var isVertical = window.String(options.mode).toUpperCase() === 'VERTICAL';
return this.each(function each() {
// cache this
var _this = this;
// cache the jQuery object
var $element = $(_this);
// count the number of times a container is entered
var enters = 0;
// determine if the scroll is with inside the container
var inside = false;
// count the number of times a container is left
var leaves = 0;
// create a scroll listener for the container
$container.on('scroll.' + options.namespace, function onScroll() {
// cache the jQuery object
var $this = $(this);
// create a position object literal
var position = {
top: $this.scrollTop(),
left: $this.scrollLeft(),
};
var containerHeight = $container.height();
var max = options.max;
var min = options.min;
var xAndY = isVertical ? position.top + options.buffer : position.left + options.buffer;
if (max === 0) {
// get the maximum value based on either the height or the outer width
max = isVertical ? containerHeight : $container.outerWidth() + $element.outerWidth();
}
// if we have reached the minimum bound, though are below the max
if (xAndY >= min && xAndY <= max) {
// trigger the 'scrollEnter' event
if (!inside) {
inside = true;
enters++;
// trigger the 'scrollEnter' event
$element.trigger('scrollEnter', {
position: position,
});
// call the 'onEnter' function
if (options.onEnter !== null) {
options.onEnter(_this, position);
}
}
// trigger the 'scrollTick' event
$element.trigger('scrollTick', {
position: position,
inside: inside,
enters: enters,
leaves: leaves,
});
// call the 'onTick' function
if (options.onTick !== null) {
options.onTick(_this, position, inside, enters, leaves);
}
} else {
if (inside) {
inside = false;
leaves++;
// trigger the 'scrollLeave' event
$element.trigger('scrollLeave', {
position: position,
leaves: leaves,
});
// call the 'onLeave' function
if (options.onLeave !== null) {
options.onLeave(_this, position);
}
if (xAndY <= min) {
// trigger the 'scrollLeaveTop' event
$element.trigger('scrollLeaveTop', {
position: position,
leaves: leaves,
});
// call the 'onLeaveTop' function
if (options.onLeaveTop !== null) {
options.onLeaveTop(_this, position);
}
} else if (xAndY >= max) {
// trigger the 'scrollLeaveBottom' event
$element.trigger('scrollLeaveBottom', {
position: position,
leaves: leaves,
});
// call the 'onLeaveBottom' function
if (options.onLeaveBottom !== null) {
options.onLeaveBottom(_this, position);
}
}
} else {
// Idea taken from: http://stackoverflow.com/questions/5353934/check-if-element-is-visible-on-screen
var containerScrollTop = $container.scrollTop();
// Get the element height
var elementHeight = $element.height();
// Get the element offset
var elementOffsetTop = $element.offset().top;
if ((elementOffsetTop < (containerHeight + containerScrollTop)) && (elementOffsetTop > (containerScrollTop - elementHeight))) {
// trigger the 'scrollView' event
$element.trigger('scrollView', {
position: position,
});
// call the 'onView' function
if (options.onView !== null) {
options.onView(_this, position);
}
}
}
}
});
});
},
});
// Fields (Private)
// Defaults
// default options
var _defaults = {
// the offset to be applied to the left and top positions of the container
buffer: 0,
// the element to apply the 'scrolling' event to (default window)
container: window,
// the maximum value of the X or Y coordinate, depending on mode the selected
max: 0,
// the maximum value of the X or Y coordinate, depending on mode the selected
min: 0,
// whether to listen to the X (horizontal) or Y (vertical) scrolling
mode: 'vertical',
// namespace to append to the 'scroll' event
namespace: 'scrollspy',
// call the following callback function every time the user enters the min / max zone
onEnter: null,
// call the following callback function every time the user leaves the min / max zone
onLeave: null,
// call the following callback function every time the user leaves the top zone
onLeaveTop: null,
// call the following callback function every time the user leaves the bottom zone
onLeaveBottom: null,
// call the following callback function on each scroll event within the min and max parameters
onTick: null,
// call the following callback function on each scroll event when the element is inside the viewable view port
onView: null,
};
// Methods (Private)
// check if a value is an object datatype
function _isObject(value) {
return $.type(value) === 'object';
}
// check if a value is a string datatype with a length greater than zero when whitespace is stripped
function _isString(value) {
return $.type(value) === 'string' && $.trim(value).length > 0;
}
// check if an option is correctly formatted using a predicate; otherwise, return the default value
function _sanitizeOption(options, defaults, property, predicate) {
// set the property to the default value if the predicate returned false
if (!predicate(options[property])) {
options[property] = defaults[property];
}
}
}(window, window.jQuery));
|