31 | */
32 | (function($) {
33 | $.fn.hoverIntent = function(handlerIn,handlerOut,selector) {
34 |
35 | // default configuration values
36 | var cfg = {
37 | interval: 100,
38 | sensitivity: 7,
39 | timeout: 0
40 | };
41 |
42 | if ( typeof handlerIn === "object" ) {
43 | cfg = $.extend(cfg, handlerIn );
44 | } else if ($.isFunction(handlerOut)) {
45 | cfg = $.extend(cfg, { over: handlerIn, out: handlerOut, selector: selector } );
46 | } else {
47 | cfg = $.extend(cfg, { over: handlerIn, out: handlerIn, selector: handlerOut } );
48 | }
49 |
50 | // instantiate variables
51 | // cX, cY = current X and Y position of mouse, updated by mousemove event
52 | // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
53 | var cX, cY, pX, pY;
54 |
55 | // A private function for getting mouse position
56 | var track = function(ev) {
57 | cX = ev.pageX;
58 | cY = ev.pageY;
59 | };
60 |
61 | // A private function for comparing current and previous mouse position
62 | var compare = function(ev,ob) {
63 | ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
64 | // compare mouse positions to see if they've crossed the threshold
65 | if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
66 | $(ob).off("mousemove.hoverIntent",track);
67 | // set hoverIntent state to true (so mouseOut can be called)
68 | ob.hoverIntent_s = 1;
69 | return cfg.over.apply(ob,[ev]);
70 | } else {
71 | // set previous coordinates for next time
72 | pX = cX; pY = cY;
73 | // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
74 | ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
75 | }
76 | };
77 |
78 | // A private function for delaying the mouseOut function
79 | var delay = function(ev,ob) {
80 | ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
81 | ob.hoverIntent_s = 0;
82 | return cfg.out.apply(ob,[ev]);
83 | };
84 |
85 | // A private function for handling mouse 'hovering'
86 | var handleHover = function(e) {
87 | // copy objects to be passed into t (required for event object to be passed in IE)
88 | var ev = jQuery.extend({},e);
89 | var ob = this;
90 |
91 | // cancel hoverIntent timer if it exists
92 | if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
93 |
94 | // if e.type == "mouseenter"
95 | if (e.type == "mouseenter") {
96 | // set "previous" X and Y position based on initial entry point
97 | pX = ev.pageX; pY = ev.pageY;
98 | // update "current" X and Y position based on mousemove
99 | $(ob).on("mousemove.hoverIntent",track);
100 | // start polling interval (self-calling timeout) to compare mouse coordinates over time
101 | if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
102 |
103 | // else e.type == "mouseleave"
104 | } else {
105 | // unbind expensive mousemove event
106 | $(ob).off("mousemove.hoverIntent",track);
107 | // if hoverIntent state is true, then call the mouseOut function after the specified delay
108 | if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
109 | }
110 | };
111 |
112 | // listen for mouseenter and mouseleave
113 | return this.on({'mouseenter.hoverIntent':handleHover,'mouseleave.hoverIntent':handleHover}, cfg.selector);
114 | };
115 | })(jQuery);
116 |
--------------------------------------------------------------------------------
/jquery.jqpagination.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jqPagination, a jQuery pagination plugin (obviously)
3 | * Version: 1.4 (26th July 2013)
4 | *
5 | * Copyright (C) 2013 Ben Everard
6 | *
7 | * http://beneverard.github.com/jqPagination
8 | *
9 | * This program is free software: you can redistribute it and/or modify
10 | * it under the terms of the GNU General Public License as published by
11 | * the Free Software Foundation, either version 3 of the License, or
12 | * (at your option) any later version.
13 | *
14 | * This program is distributed in the hope that it will be useful,
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | * GNU General Public License for more details.
18 | *
19 | * You should have received a copy of the GNU General Public License
20 | * along with this program. If not, see .
21 | *
22 | */
23 |
24 | (function ($) {
25 | "use strict";
26 |
27 | $.jqPagination = function (el, options) {
28 |
29 | // To avoid scope issues, use 'base' instead of 'this'
30 | // to reference this class from internal events and functions.
31 |
32 | var base = this;
33 |
34 | // Access to jQuery and DOM versions of element
35 | base.$el = $(el);
36 | base.el = el;
37 |
38 | // get input jQuery object
39 | base.$input = base.$el.find('input');
40 |
41 | // Add a reverse reference to the DOM object
42 | base.$el.data("jqPagination", base);
43 |
44 | base.init = function () {
45 |
46 | base.options = $.extend({}, $.jqPagination.defaultOptions, options);
47 |
48 | // if the user hasn't provided a max page number in the options try and find
49 | // the data attribute for it, if that cannot be found, use one as a max page number
50 |
51 | if (base.options.max_page === null) {
52 |
53 | if (base.$input.data('max-page') !== undefined) {
54 | base.options.max_page = base.$input.data('max-page');
55 | } else {
56 | base.options.max_page = 1;
57 | }
58 |
59 | }
60 |
61 | // if the current-page data attribute is specified this takes priority
62 | // over the options passed in, so long as it's a number
63 |
64 | if (base.$input.data('current-page') !== undefined && base.isNumber(base.$input.data('current-page'))) {
65 | base.options.current_page = base.$input.data('current-page');
66 | }
67 |
68 | // remove the readonly attribute as JavaScript must be working by now ;-)
69 | base.$input.removeAttr('readonly');
70 |
71 | // set the initial input value
72 | // pass true to prevent paged callback form being fired
73 |
74 | base.updateInput(true);
75 |
76 |
77 | //***************
78 | // BIND EVENTS
79 |
80 | base.$input.on('focus.jqPagination mouseup.jqPagination', function (event) {
81 |
82 | // if event === focus, select all text...
83 | if (event.type === 'focus') {
84 |
85 | var current_page = parseInt(base.options.current_page, 10);
86 |
87 | $(this).val(current_page).select();
88 |
89 | }
90 |
91 | // if event === mouse up, return false. Fixes Chrome bug
92 | if (event.type === 'mouseup') {
93 | return false;
94 | }
95 |
96 | });
97 |
98 | base.$input.on('blur.jqPagination keydown.jqPagination', function (event) {
99 |
100 | var $self = $(this),
101 | current_page = parseInt(base.options.current_page, 10);
102 |
103 | // if the user hits escape revert the input back to the original value
104 | if (event.keyCode === 27) {
105 | $self.val(current_page);
106 | $self.blur();
107 | }
108 |
109 | // if the user hits enter, trigger blur event but DO NOT set the page value
110 | if (event.keyCode === 13) {
111 | $self.blur();
112 | }
113 |
114 | // only set the page is the event is focusout.. aka blur
115 | if (event.type === 'blur') {
116 | base.setPage($self.val());
117 | }
118 |
119 | });
120 |
121 | base.$el.on('click.jqPagination', 'a', function (event) {
122 |
123 | var $self = $(this);
124 |
125 | // we don't want to do anything if we've clicked a disabled link
126 | // return false so we stop normal link action btu also drop out of this event
127 |
128 | if ($self.hasClass('disabled')) {
129 | return false;
130 | }
131 |
132 | // for mac + windows (read: other), maintain the cmd + ctrl click for new tab
133 | if (!event.metaKey && !event.ctrlKey) {
134 | event.preventDefault();
135 | base.setPage($self.data('action'));
136 | }
137 |
138 | });
139 |
140 | };
141 |
142 | base.setPage = function (page, prevent_paged) {
143 |
144 | // return current_page value if getting instead of setting
145 | if (page === undefined) {
146 | return base.options.current_page;
147 | }
148 |
149 | var current_page = parseInt(base.options.current_page, 10),
150 | max_page = parseInt(base.options.max_page, 10);
151 |
152 | if (isNaN(parseInt(page, 10))) {
153 |
154 | switch (page) {
155 |
156 | case 'first':
157 | page = 1;
158 | break;
159 |
160 | case 'prev':
161 | case 'previous':
162 | page = current_page - 1;
163 | break;
164 |
165 | case 'next':
166 | page = current_page + 1;
167 | break;
168 |
169 | case 'last':
170 | page = max_page;
171 | break;
172 |
173 | }
174 |
175 | }
176 |
177 | page = parseInt(page, 10);
178 |
179 | // reject any invalid page requests
180 | if (isNaN(page) || page < 1 || page > max_page) {
181 |
182 | // update the input element
183 | base.setInputValue(current_page);
184 |
185 | return false;
186 |
187 | }
188 |
189 | // update current page options
190 | base.options.current_page = page;
191 | base.$input.data('current-page', page);
192 |
193 | // update the input element
194 | base.updateInput( prevent_paged );
195 |
196 | };
197 |
198 | base.setMaxPage = function (max_page, prevent_paged) {
199 |
200 | // return the max_page value if getting instead of setting
201 | if (max_page === undefined) {
202 | return base.options.max_page;
203 | }
204 |
205 | // ignore if max_page is not a number
206 | if (!base.isNumber(max_page)) {
207 | console.error('jqPagination: max_page is not a number');
208 | return false;
209 | }
210 |
211 | // ignore if max_page is less than the current_page
212 | if (max_page < base.options.current_page) {
213 | console.error('jqPagination: max_page lower than current_page');
214 | return false;
215 | }
216 |
217 | // set max_page options
218 | base.options.max_page = max_page;
219 | base.$input.data('max-page', max_page);
220 |
221 | // update the input element
222 | base.updateInput( prevent_paged );
223 |
224 | };
225 |
226 | // ATTN this isn't really the correct name is it?
227 | base.updateInput = function (prevent_paged) {
228 |
229 | var current_page = parseInt(base.options.current_page, 10);
230 |
231 | // set the input value
232 | base.setInputValue(current_page);
233 |
234 | // set the link href attributes
235 | base.setLinks(current_page);
236 |
237 | // we may want to prevent the paged callback from being fired
238 | if (prevent_paged !== true) {
239 |
240 | // fire the callback function with the current page
241 | base.options.paged(current_page);
242 |
243 | }
244 |
245 | };
246 |
247 | base.setInputValue = function (page) {
248 |
249 | var page_string = base.options.page_string,
250 | max_page = base.options.max_page;
251 |
252 | // this looks horrible :-(
253 | page_string = page_string
254 | .replace("{current_page}", page)
255 | .replace("{max_page}", max_page);
256 |
257 | base.$input.val(page_string);
258 |
259 | };
260 |
261 | base.isNumber = function(n) {
262 | return !isNaN(parseFloat(n)) && isFinite(n);
263 | };
264 |
265 | base.setLinks = function (page) {
266 |
267 | var link_string = base.options.link_string,
268 | current_page = parseInt(base.options.current_page, 10),
269 | max_page = parseInt(base.options.max_page, 10);
270 |
271 | if (link_string !== '') {
272 |
273 | // set initial page numbers + make sure the page numbers aren't out of range
274 |
275 | var previous = current_page - 1;
276 | if (previous < 1) {
277 | previous = 1;
278 | }
279 |
280 | var next = current_page + 1;
281 | if (next > max_page) {
282 | next = max_page;
283 | }
284 |
285 | // apply each page number to the link string, set it back to the element href attribute
286 | base.$el.find('a.first').attr('href', link_string.replace('{page_number}', '1'));
287 | base.$el.find('a.prev, a.previous').attr('href', link_string.replace('{page_number}', previous));
288 | base.$el.find('a.next').attr('href', link_string.replace('{page_number}', next));
289 | base.$el.find('a.last').attr('href', link_string.replace('{page_number}', max_page));
290 |
291 | }
292 |
293 | // set disable class on appropriate links
294 | base.$el.find('a').removeClass('disabled');
295 |
296 | if (current_page === max_page) {
297 | base.$el.find('.next, .last').addClass('disabled');
298 | }
299 |
300 | if (current_page === 1) {
301 | base.$el.find('.previous, .first').addClass('disabled');
302 | }
303 |
304 | };
305 |
306 | base.callMethod = function (method, key, value) {
307 |
308 | switch (method.toLowerCase()) {
309 |
310 | case 'option':
311 |
312 | // if we're getting, immediately return the value
313 | if ( value === undefined && typeof key !== "object" ) {
314 | return base.options[key];
315 | }
316 |
317 | // set default object to trigger the paged event (legacy opperation)
318 | var options = {'trigger': true},
319 | result = false;
320 |
321 | // if the key passed in is an object
322 | if($.isPlainObject(key) && !value){
323 | $.extend(options, key)
324 | }
325 | else{ // make the key value pair part of the default object
326 | options[key] = value;
327 | }
328 |
329 | var prevent_paged = (options.trigger === false);
330 |
331 | // if current_page property is set call setPage
332 | if(options.current_page !== undefined){
333 | result = base.setPage(options.current_page, prevent_paged);
334 | }
335 |
336 | // if max_page property is set call setMaxPage
337 | if(options.max_page !== undefined){
338 | result = base.setMaxPage(options.max_page, prevent_paged);
339 | }
340 |
341 | // if we've not got a result fire an error and return false
342 | if( result === false ) console.error('jqPagination: cannot get / set option ' + key);
343 | return result;
344 |
345 | break;
346 |
347 | case 'destroy':
348 |
349 | base.$el
350 | .off('.jqPagination')
351 | .find('*')
352 | .off('.jqPagination');
353 |
354 | break;
355 |
356 | default:
357 |
358 | // the function name must not exist
359 | console.error('jqPagination: method "' + method + '" does not exist');
360 | return false;
361 |
362 | }
363 |
364 | };
365 |
366 | // Run initializer
367 | base.init();
368 |
369 | };
370 |
371 | $.jqPagination.defaultOptions = {
372 | current_page : 1,
373 | link_string : '',
374 | max_page : null,
375 | page_string : 'Page {current_page} of {max_page}',
376 | paged : function () {}
377 | };
378 |
379 | $.fn.jqPagination = function () {
380 |
381 | // get any function parameters
382 | var self = this,
383 | $self = $(self),
384 | args = Array.prototype.slice.call(arguments),
385 | result = false;
386 |
387 | // if the first argument is a string call the desired function
388 | // note: we can only do this to a single element, and not a collection of elements
389 |
390 | if (typeof args[0] === 'string') {
391 |
392 | // if we're getting, we can only get value for the first pagination element
393 | if (args[2] === undefined) {
394 |
395 | result = $self.first().data('jqPagination').callMethod(args[0], args[1]);
396 |
397 | } else {
398 |
399 | // if we're setting, set values for all pagination elements
400 | $self.each(function(){
401 | result = $(this).data('jqPagination').callMethod(args[0], args[1], args[2]);
402 | });
403 |
404 | }
405 |
406 | return result;
407 | }
408 |
409 | // if we're not dealing with a method, initialise plugin
410 | self.each(function () {
411 | (new $.jqPagination(this, args[0]));
412 | });
413 |
414 | };
415 |
416 | })(jQuery);
417 |
418 |
--------------------------------------------------------------------------------
/jquery.lazyloadxt.js:
--------------------------------------------------------------------------------
1 | /*jslint browser:true, plusplus:true, vars:true */
2 | /*jshint browser:true, jquery:true */
3 | /*jshint -W040:false */ /* to don't alert on "this" in triggerLoadOrError */
4 |
5 | (function ($, window, document, undefined) {
6 | 'use strict';
7 |
8 | // options
9 | var lazyLoadXT = 'lazyLoadXT',
10 | dataLazied = 'lazied',
11 | load_error = 'load error',
12 | classLazyHidden = 'lazy-hidden',
13 | docElement = document.documentElement || document.body,
14 | // force load all images in Opera Mini and some mobile browsers without scroll event or getBoundingClientRect()
15 | forceLoad = (window.onscroll === undefined || !!window.operamini || !docElement.getBoundingClientRect),
16 | options = {
17 | autoInit: true, // auto initialize in $.ready
18 | selector: 'img[data-src]', // selector for lazyloading elements
19 | blankImage: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
20 | throttle: 99, // interval (ms) for changes check
21 | forceLoad: forceLoad, // force auto load all images
22 |
23 | loadEvent: 'pageshow', // check AJAX-loaded content in jQueryMobile
24 | updateEvent: 'load orientationchange resize scroll touchmove focus', // page-modified events
25 | forceEvent: '', // force loading of all elements
26 |
27 | //onstart: null,
28 | oninit: {removeClass: 'lazy'}, // init handler
29 | onshow: {addClass: classLazyHidden}, // start loading handler
30 | onload: {removeClass: classLazyHidden, addClass: 'lazy-loaded'}, // load success handler
31 | onerror: {removeClass: classLazyHidden}, // error handler
32 | //oncomplete: null, // complete handler
33 |
34 | //scrollContainer: undefined,
35 | checkDuplicates: true
36 | },
37 | elementOptions = {
38 | srcAttr: 'data-src',
39 | edgeX: 0,
40 | edgeY: 0,
41 | visibleOnly: true
42 | },
43 | $window = $(window),
44 | $isFunction = $.isFunction,
45 | $extend = $.extend,
46 | $data = $.data || function (el, name) {
47 | return $(el).data(name);
48 | },
49 | elements = [],
50 | topLazy = 0,
51 | /*
52 | waitingMode=0 : no setTimeout
53 | waitingMode=1 : setTimeout, no deferred events
54 | waitingMode=2 : setTimeout, deferred events
55 | */
56 | waitingMode = 0;
57 |
58 | $[lazyLoadXT] = $extend(options, elementOptions, $[lazyLoadXT]);
59 |
60 | /**
61 | * Return options.prop if obj.prop is undefined, otherwise return obj.prop
62 | * @param {*} obj
63 | * @param {*} prop
64 | * @returns *
65 | */
66 | function getOrDef(obj, prop) {
67 | return obj[prop] === undefined ? options[prop] : obj[prop];
68 | }
69 |
70 | /**
71 | * @returns {number}
72 | */
73 | function scrollTop() {
74 | var scroll = window.pageYOffset;
75 | return (scroll === undefined) ? docElement.scrollTop : scroll;
76 | }
77 |
78 | /**
79 | * Add new elements to lazy-load list:
80 | * $(elements).lazyLoadXT() or $(window).lazyLoadXT()
81 | *
82 | * @param {object} [overrides] override global options
83 | */
84 | $.fn[lazyLoadXT] = function (overrides) {
85 | overrides = overrides || {};
86 |
87 | var blankImage = getOrDef(overrides, 'blankImage'),
88 | checkDuplicates = getOrDef(overrides, 'checkDuplicates'),
89 | scrollContainer = getOrDef(overrides, 'scrollContainer'),
90 | forceShow = getOrDef(overrides, 'show'),
91 | elementOptionsOverrides = {},
92 | prop;
93 |
94 | // empty overrides.scrollContainer is supported by both jQuery and Zepto
95 | $(scrollContainer).on('scroll', queueCheckLazyElements);
96 |
97 | for (prop in elementOptions) {
98 | elementOptionsOverrides[prop] = getOrDef(overrides, prop);
99 | }
100 |
101 | return this.each(function (index, el) {
102 | if (el === window) {
103 | $(options.selector).lazyLoadXT(overrides);
104 | } else {
105 | var duplicate = checkDuplicates && $data(el, dataLazied),
106 | $el = $(el).data(dataLazied, forceShow ? -1 : 1);
107 |
108 | // prevent duplicates
109 | if (duplicate) {
110 | queueCheckLazyElements();
111 | return;
112 | }
113 |
114 | if (blankImage && el.tagName === 'IMG' && !el.src) {
115 | el.src = blankImage;
116 | }
117 |
118 | // clone elementOptionsOverrides object
119 | $el[lazyLoadXT] = $extend({}, elementOptionsOverrides);
120 |
121 | triggerEvent('init', $el);
122 |
123 | elements.push($el);
124 | queueCheckLazyElements();
125 | }
126 | });
127 | };
128 |
129 |
130 | /**
131 | * Process function/object event handler
132 | * @param {string} event suffix
133 | * @param {jQuery} $el
134 | */
135 | function triggerEvent(event, $el) {
136 | var handler = options['on' + event];
137 | if (handler) {
138 | if ($isFunction(handler)) {
139 | handler.call($el[0]);
140 | } else {
141 | if (handler.addClass) {
142 | $el.addClass(handler.addClass);
143 | }
144 | if (handler.removeClass) {
145 | $el.removeClass(handler.removeClass);
146 | }
147 | }
148 | }
149 |
150 | $el.trigger('lazy' + event, [$el]);
151 |
152 | // queue next check as images may be resized after loading of actual file
153 | queueCheckLazyElements();
154 | }
155 |
156 |
157 | /**
158 | * Trigger onload/onerror handler
159 | * @param {Event} e
160 | */
161 | function triggerLoadOrError(e) {
162 | triggerEvent(e.type, $(this).off(load_error, triggerLoadOrError));
163 | }
164 |
165 |
166 | /**
167 | * Load visible elements
168 | * @param {bool} [force] loading of all elements
169 | */
170 | function checkLazyElements(force) {
171 | if (!elements.length) {
172 | return;
173 | }
174 |
175 | force = force || options.forceLoad;
176 |
177 | topLazy = Infinity;
178 |
179 | var viewportTop = scrollTop(),
180 | viewportHeight = window.innerHeight || docElement.clientHeight,
181 | viewportWidth = window.innerWidth || docElement.clientWidth,
182 | i,
183 | length;
184 |
185 | for (i = 0, length = elements.length; i < length; i++) {
186 | var $el = elements[i],
187 | el = $el[0],
188 | objData = $el[lazyLoadXT],
189 | removeNode = false,
190 | visible = force || $data(el, dataLazied) < 0,
191 | topEdge;
192 |
193 | // remove items that are not in DOM
194 | if (!$.contains(docElement, el)) {
195 | removeNode = true;
196 | } else if (force || !objData.visibleOnly || el.offsetWidth || el.offsetHeight) {
197 |
198 | if (!visible) {
199 | var elPos = el.getBoundingClientRect(),
200 | edgeX = objData.edgeX,
201 | edgeY = objData.edgeY;
202 |
203 | topEdge = (elPos.top + viewportTop - edgeY) - viewportHeight;
204 |
205 | visible = (topEdge <= viewportTop && elPos.bottom > -edgeY &&
206 | elPos.left <= viewportWidth + edgeX && elPos.right > -edgeX);
207 | }
208 |
209 | if (visible) {
210 | $el.on(load_error, triggerLoadOrError);
211 |
212 | triggerEvent('show', $el);
213 |
214 | var srcAttr = objData.srcAttr,
215 | src = $isFunction(srcAttr) ? srcAttr($el) : el.getAttribute(srcAttr);
216 |
217 | if (src) {
218 | el.src = src;
219 | }
220 |
221 | removeNode = true;
222 | } else {
223 | if (topEdge < topLazy) {
224 | topLazy = topEdge;
225 | }
226 | }
227 | }
228 |
229 | if (removeNode) {
230 | elements.splice(i--, 1);
231 | length--;
232 | }
233 | }
234 |
235 | if (!length) {
236 | triggerEvent('complete', $(docElement));
237 | }
238 | }
239 |
240 |
241 | /**
242 | * Run check of lazy elements after timeout
243 | */
244 | function timeoutLazyElements() {
245 | if (waitingMode > 1) {
246 | waitingMode = 1;
247 | checkLazyElements();
248 | setTimeout(timeoutLazyElements, options.throttle);
249 | } else {
250 | waitingMode = 0;
251 | }
252 | }
253 |
254 |
255 | /**
256 | * Queue check of lazy elements because of event e
257 | * @param {Event} [e]
258 | */
259 | function queueCheckLazyElements(e) {
260 | if (!elements.length) {
261 | return;
262 | }
263 |
264 | // fast check for scroll event without new visible elements
265 | if (e && e.type === 'scroll' && e.currentTarget === window) {
266 | if (topLazy >= scrollTop()) {
267 | return;
268 | }
269 | }
270 |
271 | if (!waitingMode) {
272 | setTimeout(timeoutLazyElements, 0);
273 | }
274 | waitingMode = 2;
275 | }
276 |
277 |
278 | /**
279 | * Initialize list of hidden elements
280 | */
281 | function initLazyElements() {
282 | $window.lazyLoadXT();
283 | }
284 |
285 |
286 | /**
287 | * Loading of all elements
288 | */
289 | function forceLoadAll() {
290 | checkLazyElements(true);
291 | }
292 |
293 |
294 | /**
295 | * Initialization
296 | */
297 | $(document).ready(function () {
298 | triggerEvent('start', $window);
299 |
300 | $window
301 | .on(options.loadEvent, initLazyElements)
302 | .on(options.updateEvent, queueCheckLazyElements)
303 | .on(options.forceEvent, forceLoadAll);
304 |
305 | $(document).on(options.updateEvent, queueCheckLazyElements);
306 |
307 | if (options.autoInit) {
308 | initLazyElements(); // standard initialization
309 | }
310 | });
311 |
312 | })(window.jQuery || window.Zepto || window.$, window, document);
--------------------------------------------------------------------------------
/jquery.localScroll.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery.LocalScroll
3 | * Copyright (c) 2007-2010 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
4 | * Dual licensed under MIT and GPL.
5 | * Date: 05/31/2010
6 | *
7 | * @projectDescription Animated scrolling navigation, using anchors.
8 | * http://flesler.blogspot.com/2007/10/jquerylocalscroll-10.html
9 | * @author Ariel Flesler
10 | * @version 1.2.8b
11 | *
12 | * @id jQuery.fn.localScroll
13 | * @param {Object} settings Hash of settings, it is passed in to jQuery.ScrollTo, none is required.
14 | * @return {jQuery} Returns the same jQuery object, for chaining.
15 | *
16 | * @example $('ul.links').localScroll();
17 | *
18 | * @example $('ul.links').localScroll({ filter:'.animated', duration:400, axis:'x' });
19 | *
20 | * @example $.localScroll({ target:'#pane', axis:'xy', queue:true, event:'mouseover' });
21 | *
22 | * Notes:
23 | * - The plugin requires jQuery.ScrollTo.
24 | * - The hash of settings, is passed to jQuery.ScrollTo, so the settings are valid for that plugin as well.
25 | * - jQuery.localScroll can be used if the desired links, are all over the document, it accepts the same settings.
26 | * - If the setting 'lazy' is set to true, then the binding will still work for later added anchors.
27 | * - If onBefore returns false, the event is ignored.
28 | */
29 | ;(function( $ ){
30 | var URI = location.href.replace(/#.*/,''); // local url without hash
31 |
32 | var $localScroll = $.localScroll = function( settings ){
33 | $('body').localScroll( settings );
34 | };
35 |
36 | // Many of these defaults, belong to jQuery.ScrollTo, check it's demo for an example of each option.
37 | // @see http://flesler.demos.com/jquery/scrollTo/
38 | // The defaults are public and can be overriden.
39 | $localScroll.defaults = {
40 | duration:1000, // How long to animate.
41 | axis:'y', // Which of top and left should be modified.
42 | event:'click', // On which event to react.
43 | stop:true, // Avoid queuing animations
44 | target: window, // What to scroll (selector or element). The whole window by default.
45 | reset: true // Used by $.localScroll.hash. If true, elements' scroll is resetted before actual scrolling
46 | /*
47 | lock:false, // ignore events if already animating
48 | lazy:false, // if true, links can be added later, and will still work.
49 | filter:null, // filter some anchors out of the matched elements.
50 | hash: false // if true, the hash of the selected link, will appear on the address bar.
51 | */
52 | };
53 |
54 | // If the URL contains a hash, it will scroll to the pointed element
55 | $localScroll.hash = function( settings ){
56 | if( location.hash ){
57 | settings = $.extend( {}, $localScroll.defaults, settings );
58 | settings.hash = false; // can't be true
59 |
60 | if( settings.reset ){
61 | var d = settings.duration;
62 | delete settings.duration;
63 | $(settings.target).scrollTo( 0, settings );
64 | settings.duration = d;
65 | }
66 | scroll( 0, location, settings );
67 | }
68 | };
69 |
70 | $.fn.localScroll = function( settings ){
71 | settings = $.extend( {}, $localScroll.defaults, settings );
72 |
73 | return settings.lazy ?
74 | // use event delegation, more links can be added later.
75 | this.bind( settings.event, function( e ){
76 | // Could use closest(), but that would leave out jQuery -1.3.x
77 | var a = $([e.target, e.target.parentNode]).filter(filter)[0];
78 | // if a valid link was clicked
79 | if( a )
80 | scroll( e, a, settings ); // do scroll.
81 | }) :
82 | // bind concretely, to each matching link
83 | this.find('a,area')
84 | .filter( filter ).bind( settings.event, function(e){
85 | scroll( e, this, settings );
86 | }).end()
87 | .end();
88 |
89 | function filter(){// is this a link that points to an anchor and passes a possible filter ? href is checked to avoid a bug in FF.
90 | return !!this.href && !!this.hash && this.href.replace(this.hash,'') == URI && (!settings.filter || $(this).is( settings.filter ));
91 | };
92 | };
93 |
94 | function scroll( e, link, settings ){
95 | var id = link.hash.slice(1),
96 | elem = document.getElementById(id) || document.getElementsByName(id)[0];
97 |
98 | if ( !elem )
99 | return;
100 |
101 | if( e )
102 | e.preventDefault();
103 |
104 | var $target = $( settings.target );
105 |
106 | if( settings.lock && $target.is(':animated') ||
107 | settings.onBefore && settings.onBefore(e, elem, $target) === false )
108 | return;
109 |
110 | if( settings.stop )
111 | $target._scrollable().stop(true); // remove all its animations
112 |
113 | if( settings.hash ){
114 | var attr = elem.id == id ? 'id' : 'name',
115 | $a = $(' ').attr(attr, id).css({
116 | position:'absolute',
117 | top: $(window).scrollTop(),
118 | left: $(window).scrollLeft()
119 | });
120 |
121 | elem[attr] = '';
122 | $('body').prepend($a);
123 | location = link.hash;
124 | $a.remove();
125 | elem[attr] = id;
126 | }
127 |
128 | $target
129 | .scrollTo( elem, settings ) // do scroll
130 | .trigger('notify.serialScroll',[elem]); // notify serialScroll about this change
131 | };
132 |
133 | })( jQuery );
--------------------------------------------------------------------------------
/jquery.spellchecker.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Spellchecker - v0.2.4 - 2012-12-19
3 | * https://github.com/badsyntax/jquery-spellchecker
4 | * Copyright (c) 2012 Richard Willis; Licensed MIT
5 | */
6 |
7 | (function(window, $) {
8 |
9 | /* Config
10 | *************************/
11 |
12 | var defaultConfig = {
13 | lang: 'en',
14 | webservice: {
15 | path: 'spellchecker.php',
16 | driver: 'PSpell'
17 | },
18 | local: {
19 | requestError: 'There was an error processing the request.',
20 | ignoreWord: 'Ignore word',
21 | ignoreAll: 'Ignore all',
22 | ignoreForever: 'Add to dictionary',
23 | loading: 'Loading...',
24 | noSuggestions: '(No suggestions)'
25 | },
26 | suggestBox: {
27 | numWords: 5,
28 | position: 'above',
29 | offset: 2,
30 | appendTo: null
31 | },
32 | incorrectWords: {
33 | container: 'body', //selector
34 | position: null //function
35 | }
36 | };
37 |
38 | var pluginName = 'spellchecker';
39 |
40 | /* Util
41 | *************************/
42 |
43 | if (!Function.prototype.bind) {
44 | Function.prototype.bind = function(scope) {
45 | return $.proxy(this, scope);
46 | };
47 | }
48 |
49 | var inherits = function(_sub, _super) {
50 | function F() {}
51 | F.prototype = _super.prototype;
52 | _sub.prototype = new F();
53 | _sub.prototype.constructor = _sub;
54 | };
55 |
56 | var decode = function(text) {
57 | return $('').html(text).html();
58 | };
59 |
60 | RegExp.escape = function(text) {
61 | return text.replace(/[\-\[\]{}()*+?.,\^$|#\s]/g, "\\$&");
62 | };
63 |
64 | /* Character sets
65 | *************************/
66 |
67 | var punctuationChars = '\\u0021-\\u0023\\u0025-\\u002A\\u002C-\\u002F\\u003A\\u003B\\u003F\\u0040\\u005B-\\u005D\\u005F\\u007B\\u007D\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65';
68 | var letterChars = '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC';
69 |
70 | /* Events
71 | *************************/
72 |
73 | var Events = function(){
74 | this._handlers = {};
75 | };
76 |
77 | Events.prototype = {
78 | on: function(name, handler) {
79 | if (!this._handlers[name]) {
80 | this._handlers[name] = $.Callbacks();
81 | }
82 | this._handlers[name].add(handler);
83 | },
84 | trigger: function(name) {
85 | var args = Array.prototype.slice.call(arguments, 1);
86 | if ($.isFunction(name)) {
87 | return name.apply(this, args);
88 | }
89 | if (this._handlers[name]) {
90 | this._handlers[name].fireWith(this, args);
91 | }
92 | },
93 | handler: function(name) {
94 | return function(e) {
95 | this.trigger(name, e);
96 | }.bind(this);
97 | }
98 | };
99 |
100 | /* Handlers
101 | *************************/
102 |
103 | var selectWordHandler = function(handlerName) {
104 |
105 | return function(e) {
106 |
107 | e.preventDefault();
108 | e.stopPropagation();
109 |
110 | var element = $(e.currentTarget);
111 | var word = $.trim(element.data('word') || element.text());
112 |
113 | this.trigger(handlerName, e, word, element, this);
114 |
115 | }.bind(this);
116 | };
117 |
118 | /* Collections
119 | *************************/
120 |
121 | var Collection = function(elements, instanceFactory) {
122 | this.instances = [];
123 | for(var i = 0; i < elements.length; i++) {
124 | this.instances.push( instanceFactory(elements[i]) );
125 | }
126 | this.methods([ 'on', 'destroy', 'trigger' ]);
127 | };
128 |
129 | Collection.prototype.methods = function(methods) {
130 | $.each(methods, function(i, method) {
131 | this[method] = function() {
132 | this.execute(method, arguments);
133 | }.bind(this);
134 | }.bind(this));
135 | };
136 |
137 | Collection.prototype.execute = function(method, args) {
138 | $.each(this.instances, function(i, instance) {
139 | instance[method].apply(instance, args);
140 | });
141 | };
142 |
143 | Collection.prototype.get = function(i) {
144 | return this.instances[i];
145 | };
146 |
147 | /* Base box
148 | *************************/
149 |
150 | var Box = function(config, parser, element) {
151 | Events.call(this);
152 | this.config = config;
153 | this.parser = parser;
154 | this.spellCheckerElement = $(element);
155 | this.createBox();
156 | this.bindEvents();
157 | };
158 | inherits(Box, Events);
159 |
160 | /* Incorrect words box
161 | *************************/
162 |
163 | var IncorrectWordsBox = function(config, parser, element) {
164 | Box.apply(this, arguments);
165 | };
166 | inherits(IncorrectWordsBox, Box);
167 |
168 | IncorrectWordsBox.prototype.bindEvents = function() {
169 | this.container.on('click', 'a', selectWordHandler.call(this, 'select.word'));
170 | this.on('addWords', this.addWords.bind(this));
171 | };
172 |
173 | IncorrectWordsBox.prototype.createBox = function() {
174 |
175 | this.container = $([
176 | '',
177 | '
'
178 | ].join(''))
179 | .hide();
180 |
181 | if ($.isFunction(this.config.incorrectWords.position)) {
182 | this.config.incorrectWords.position.call(this.spellCheckerElement, this.container);
183 | } else {
184 | this.container.appendTo(this.config.incorrectWords.container);
185 | }
186 | };
187 |
188 | IncorrectWordsBox.prototype.addWords = function(words) {
189 |
190 | // Make array values unique
191 | words = $.grep(words, function(el, index){
192 | return index === $.inArray(el, words);
193 | });
194 |
195 | var html = $.map(words, function(word) {
196 | return '' + word + '';
197 | }).join('');
198 |
199 | this.container.html(html).show();
200 | };
201 |
202 | IncorrectWordsBox.prototype.removeWord = function(elem) {
203 | if (elem) {
204 | elem.remove();
205 | }
206 | if (this.container.children().length === 0) {
207 | this.container.hide();
208 | }
209 | };
210 |
211 | IncorrectWordsBox.prototype.destroy = function() {
212 | this.container.empty().remove();
213 | };
214 |
215 | /* Incorrect words inline
216 | *************************/
217 |
218 | var IncorrectWordsInline = function(config, parser, element) {
219 | Events.call(this);
220 | this.config = config;
221 | this.parser = parser;
222 | this.spellCheckerElement = this.element = $(element);
223 | this.bindEvents();
224 | };
225 | inherits(IncorrectWordsInline, Events);
226 |
227 | IncorrectWordsInline.prototype.bindEvents = function() {
228 | this.element.on('click.' + pluginName, '.' + pluginName + '-word-highlight', selectWordHandler.call(this, 'select.word'));
229 | };
230 |
231 | IncorrectWordsInline.prototype.addWords = function(words) {
232 | var highlighted = this.parser.highlightWords(words, this.element);
233 | this.element.html(highlighted);
234 | };
235 |
236 | IncorrectWordsInline.prototype.removeWord = function(elem) {};
237 |
238 | IncorrectWordsInline.prototype.destroy = function() {
239 | this.element.off('.' + pluginName);
240 | try {
241 | window.findAndReplaceDOMText.revert();
242 | } catch(e) {}
243 | };
244 |
245 | /* Suggest box
246 | *************************/
247 |
248 | var SuggestBox = function(config, element) {
249 | this.element = element;
250 | if (config.suggestBox.appendTo) {
251 | this.body = $(config.suggestBox.appendTo);
252 | } else {
253 | this.body = (this.element.length && this.element[0].nodeName === 'BODY') ? this.element : 'body';
254 | }
255 | this.position = $.isFunction(config.suggestBox.position) ? config.suggestBox.position : this.position;
256 | Box.apply(this, arguments);
257 | };
258 | inherits(SuggestBox, Box);
259 |
260 | SuggestBox.prototype.bindEvents = function() {
261 | var click = 'click.' + pluginName;
262 | this.container.on(click, this.onContainerClick.bind(this));
263 | this.container.on(click, '.ignore-word', selectWordHandler.call(this, 'ignore.word'));
264 | this.container.on(click, '.ignore-all', this.handler('ignore.all'));
265 | this.container.on(click, '.ignore-forever', this.handler('ignore.forever'));
266 | this.container.on(click, '.words a', selectWordHandler.call(this, 'select.word'));
267 | $('html').on(click, this.onWindowClick.bind(this));
268 | if (this.element[0].nodeName === 'BODY') {
269 | this.element.parent().on(click, this.onWindowClick.bind(this));
270 | }
271 | };
272 |
273 | SuggestBox.prototype.createBox = function() {
274 |
275 | var local = this.config.local;
276 |
277 | this.container = $([
278 | '',
279 | ' ',
284 | '
'
285 | ].join('')).appendTo(this.body);
286 |
287 | this.words = $([
288 | '',
289 | '
'
290 | ].join('')).prependTo(this.container);
291 |
292 | this.loadingMsg = $([
293 | '',
294 | this.config.local.loading,
295 | '
'
296 | ].join(''));
297 |
298 | this.footer = this.container.find('.footer').hide();
299 | };
300 |
301 | SuggestBox.prototype.addWords = function(words) {
302 |
303 | var html;
304 |
305 | if (!words.length) {
306 | html = '' + this.config.local.noSuggestions + '';
307 | } else {
308 | html = $.map(words, function(word) {
309 | return '' + word + '';
310 | }).slice(0, this.config.suggestBox.numWords).join('');
311 | }
312 |
313 | this.words.html(html);
314 | };
315 |
316 | SuggestBox.prototype.showSuggestedWords = function(getWords, word, wordElement) {
317 | this.wordElement = $(wordElement);
318 | getWords(word, this.onGetWords.bind(this));
319 | };
320 |
321 | SuggestBox.prototype.loading = function(show) {
322 | this.footer.hide();
323 | this.words.html(show ? this.loadingMsg.clone() : '');
324 | this.position();
325 | this.open();
326 | };
327 |
328 | SuggestBox.prototype.position = function() {
329 |
330 | var win = $(window);
331 | var element = this.wordElement.data('firstElement') || this.wordElement;
332 | var offset = element.offset();
333 | var boxOffset = this.config.suggestBox.offset;
334 | var containerHeight = this.container.outerHeight();
335 |
336 | var positionAbove = (offset.top - containerHeight - boxOffset);
337 | var positionBelow = (offset.top + element.outerHeight() + boxOffset);
338 |
339 | var left = offset.left;
340 | var top;
341 |
342 | if (this.config.suggestBox.position === 'below') {
343 | top = positionBelow;
344 | if (win.height() + win.scrollTop() < positionBelow + containerHeight) {
345 | top = positionAbove;
346 | }
347 | } else {
348 | top = positionAbove;
349 | }
350 |
351 | this.container.css({ top: top, left: left });
352 | };
353 |
354 | SuggestBox.prototype.open = function() {
355 | this.position();
356 | this.container.fadeIn(180);
357 | };
358 |
359 | SuggestBox.prototype.close = function() {
360 | this.container.fadeOut(100, function(){
361 | this.footer.hide();
362 | }.bind(this));
363 | };
364 |
365 | SuggestBox.prototype.detach = function() {
366 | this.container = this.container.detach();
367 | };
368 |
369 | SuggestBox.prototype.reattach = function() {
370 | this.container.appendTo(this.body);
371 | };
372 |
373 | SuggestBox.prototype.onContainerClick = function(e) {
374 | e.stopPropagation();
375 | };
376 |
377 | SuggestBox.prototype.onWindowClick = function(e) {
378 | this.close();
379 | };
380 |
381 | SuggestBox.prototype.onGetWords = function(words) {
382 | this.addWords(words);
383 | this.footer.show();
384 | this.position();
385 | this.open();
386 | };
387 |
388 | SuggestBox.prototype.destroy = function() {
389 | this.container.empty().remove();
390 | };
391 |
392 | /* Spellchecker web service
393 | *************************/
394 |
395 | var WebService = function(config) {
396 |
397 | this.config = config;
398 |
399 | this.defaultConfig = {
400 | url: config.webservice.path,
401 | type: 'POST',
402 | dataType: 'json',
403 | cache: false,
404 | data: {
405 | lang: config.lang,
406 | driver: config.webservice.driver
407 | },
408 | error: function() {
409 | alert(config.local.requestError);
410 | }.bind(this)
411 | };
412 | };
413 |
414 | WebService.prototype.makeRequest = function(config) {
415 |
416 | var defaultConfig = $.extend(true, {}, this.defaultConfig);
417 |
418 | return $.ajax($.extend(true, defaultConfig, config));
419 | };
420 |
421 | WebService.prototype.checkWords = function(text, callback) {
422 | return this.makeRequest({
423 | data: {
424 | action: 'get_incorrect_words',
425 | text: text
426 | },
427 | success: callback
428 | });
429 | };
430 |
431 | WebService.prototype.getSuggestions = function(word, callback) {
432 | return this.makeRequest({
433 | data: {
434 | action: 'get_suggestions',
435 | word: word
436 | },
437 | success: callback
438 | });
439 | };
440 |
441 | /* Spellchecker base parser
442 | *************************/
443 |
444 | var Parser = function(elements) {
445 | this.elements = elements;
446 | };
447 |
448 | Parser.prototype.clean = function(text) {
449 |
450 | text = '' + text; // Typecast to string
451 | text = decode(text); // Decode HTML characters
452 | text = text.replace(/\xA0|\s+|( )/mg, ' '); // Convert whitespace
453 | text = text.replace(new RegExp('<[^>]+>', 'g'), ''); // Strip HTML tags
454 |
455 | var puncExpr = [
456 | '(^|\\s+)[' + punctuationChars + ']+', // punctuation(s) with leading whitespace(s)
457 | '[' + punctuationChars + ']+\\s+[' + punctuationChars + ']+', // punctuation(s) with leading and trailing whitespace(s)
458 | '[' + punctuationChars + ']+(\\s+|$)' // puncutation(s) with trailing whitespace(s)
459 | ].join('|');
460 |
461 | text = text.replace(new RegExp(puncExpr, 'g'), ' '); // strip any punctuation
462 | text = $.trim(text.replace(/\s{2,}/g, ' ')); // remove extra whitespace
463 |
464 | // Remove numbers
465 | text = $.map(text.split(' '), function(word) {
466 | return (/^\d+$/.test(word)) ? null : word;
467 | }).join(' ');
468 |
469 | return text;
470 | };
471 |
472 | /* Spellchecker text parser
473 | *************************/
474 |
475 | var TextParser = function() {
476 | Parser.apply(this, arguments);
477 | };
478 | inherits(TextParser, Parser);
479 |
480 | TextParser.prototype.getText = function(text, textGetter) {
481 | return $.map(this.elements, function(element) {
482 | return this.clean(textGetter ? textGetter(element) : $(element).val());
483 | }.bind(this));
484 | };
485 |
486 | TextParser.prototype.replaceWordInText = function(oldWord, newWord, text) {
487 | var regex = new RegExp('(^|[^' + letterChars + '])(' + RegExp.escape(oldWord) + ')(?=[^' + letterChars + ']|$)', 'g');
488 | return (text + '').replace(regex, '$1' + newWord);
489 | };
490 |
491 | TextParser.prototype.replaceWord = function(oldWord, replacement, element) {
492 | element = $(element);
493 | var newText = this.replaceWordInText(oldWord, replacement, element.val());
494 | element.val(newText);
495 | };
496 |
497 | /* Spellchecker html parser
498 | *************************/
499 |
500 | var HtmlParser = function() {
501 | Parser.apply(this, arguments);
502 | };
503 | inherits(HtmlParser, Parser);
504 |
505 | HtmlParser.prototype.getText = function(text, textGetter) {
506 | if (text && (text = $(text)).length) {
507 | return this.clean(text.text());
508 | }
509 | return $.map(this.elements, function(element) {
510 |
511 | if (textGetter) {
512 | text = textGetter(element);
513 | } else {
514 | text = $(element)
515 | .clone()
516 | .find('[class^="spellchecker-"]')
517 | .remove()
518 | .end()
519 | .text();
520 | }
521 |
522 | return this.clean(text);
523 |
524 | }.bind(this));
525 | };
526 |
527 | HtmlParser.prototype.replaceText = function(regExp, element, replaceText, captureGroup) {
528 | window.findAndReplaceDOMText(regExp, element, replaceText, captureGroup);
529 | };
530 |
531 | HtmlParser.prototype.replaceWord = function(oldWord, replacement, element) {
532 |
533 | try {
534 | window.findAndReplaceDOMText.revert();
535 | } catch(e) {}
536 |
537 | var regExp = new RegExp('(^|[^' + letterChars + '])(' + RegExp.escape(oldWord) + ')(?=[^' + letterChars + ']|$)', 'g');
538 |
539 | this.replaceText(regExp, element[0], this.replaceTextHandler(oldWord, replacement), 2);
540 |
541 | // Remove this word from the list of incorrect words
542 | this.incorrectWords = $.map(this.incorrectWords || [], function(word) {
543 | return word === oldWord ? null : word;
544 | });
545 |
546 | this.highlightWords(this.incorrectWords, element);
547 | };
548 |
549 | HtmlParser.prototype.replaceTextHandler = function(oldWord, replacement){
550 |
551 | var r = replacement;
552 | var replaced;
553 | var replaceFill;
554 | var c;
555 |
556 | return function(fill, i) {
557 |
558 | // Reset the replacement for each match
559 | if (i !== c) {
560 | c = i;
561 | replacement = r;
562 | replaced = '';
563 | }
564 |
565 | replaceFill = replacement.substring(0, fill.length);
566 | replacement = replacement.substr(fill.length);
567 | replaced += fill;
568 |
569 | // Add remaining text to last node
570 | if (replaced === oldWord) {
571 | replaceFill += replacement;
572 | }
573 |
574 | return document.createTextNode(replaceFill);
575 | };
576 | };
577 |
578 | HtmlParser.prototype.highlightWords = function(incorrectWords, element) {
579 | if (!incorrectWords.length) {
580 | return;
581 | }
582 |
583 | this.incorrectWords = incorrectWords;
584 | incorrectWords = $.map(incorrectWords, function(word) {
585 | return RegExp.escape(word);
586 | });
587 |
588 | var regExp = '';
589 | regExp += '([^' + letterChars + '])';
590 | regExp += '(' + incorrectWords.join('|') + ')';
591 | regExp += '(?=[^' + letterChars + '])';
592 |
593 | this.replaceText(new RegExp(regExp, 'g'), element[0], this.highlightWordsHandler(incorrectWords), 2);
594 | };
595 |
596 | HtmlParser.prototype.highlightWordsHandler = function(incorrectWords) {
597 |
598 | var c;
599 | var replaceElement;
600 |
601 | return function(fill, i, word) {
602 |
603 | // Replacement node
604 | var span = $('', {
605 | 'class': pluginName + '-word-highlight'
606 | });
607 |
608 | // If we have a new match
609 | if (i !== c) {
610 | c = i;
611 | replaceElement = span;
612 | }
613 |
614 | span
615 | .text(fill)
616 | .data({
617 | 'firstElement': replaceElement,
618 | 'word': word
619 | });
620 |
621 | return span[0];
622 | };
623 | };
624 |
625 | HtmlParser.prototype.ignoreWord = function(oldWord, replacement) {
626 | this.replaceWord(oldWord, replacement);
627 | };
628 |
629 | /* Spellchecker
630 | *************************/
631 |
632 | var SpellChecker = function(elements, config) {
633 |
634 | Events.call(this);
635 |
636 | this.elements = $(elements).attr('spellcheck', 'false');
637 | this.config = $.extend(true, defaultConfig, config);
638 |
639 | this.setupWebService();
640 | this.setupParser();
641 |
642 | if (this.elements.length) {
643 | this.setupSuggestBox();
644 | this.setupIncorrectWords();
645 | this.bindEvents();
646 | }
647 | };
648 | inherits(SpellChecker, Events);
649 |
650 | SpellChecker.prototype.setupWebService = function() {
651 | this.webservice = new WebService(this.config);
652 | };
653 |
654 | SpellChecker.prototype.setupSuggestBox = function() {
655 |
656 | this.suggestBox = new SuggestBox(this.config, this.elements);
657 |
658 | this.on('replace.word.before', function() {
659 | this.suggestBox.close();
660 | this.suggestBox.detach();
661 | }.bind(this));
662 |
663 | this.on('replace.word', function() {
664 | this.suggestBox.reattach();
665 | }.bind(this));
666 |
667 | this.on('destroy', function() {
668 | this.suggestBox.destroy();
669 | }.bind(this));
670 | };
671 |
672 | SpellChecker.prototype.setupIncorrectWords = function() {
673 |
674 | this.incorrectWords = new Collection(this.elements, function(element) {
675 | return this.config.parser === 'html' ?
676 | new IncorrectWordsInline(this.config, this.parser, element) :
677 | new IncorrectWordsBox(this.config, this.parser, element);
678 | }.bind(this));
679 |
680 | this.on('replace.word', function(index) {
681 | this.incorrectWords.get(index).removeWord(this.incorrectWordElement);
682 | }.bind(this));
683 |
684 | this.on('destroy', function() {
685 | this.incorrectWords.destroy();
686 | }, this);
687 | };
688 |
689 | SpellChecker.prototype.setupParser = function() {
690 | this.parser = this.config.parser === 'html' ?
691 | new HtmlParser(this.elements) :
692 | new TextParser(this.elements);
693 | };
694 |
695 | SpellChecker.prototype.bindEvents = function() {
696 | this.on('check.fail', this.onCheckFail.bind(this));
697 | this.suggestBox.on('ignore.word', this.onIgnoreWord.bind(this));
698 | this.suggestBox.on('select.word', this.onSelectWord.bind(this));
699 | this.incorrectWords.on('select.word', this.onIncorrectWordSelect.bind(this));
700 | };
701 |
702 | /* Pubic API methods */
703 |
704 | SpellChecker.prototype.check = function(text, callback) {
705 | this.trigger('check.start');
706 | text = typeof text === 'string' ? this.parser.clean(text) : this.parser.getText(text || '', this.config.getText);
707 | this.webservice.checkWords(text, this.onCheckWords(callback));
708 | };
709 |
710 | SpellChecker.prototype.getSuggestions = function(word, callback) {
711 | if (this.suggestBox) {
712 | this.suggestBox.loading(true);
713 | }
714 | this.webservice.getSuggestions(word, callback);
715 | };
716 |
717 | SpellChecker.prototype.replaceWord = function(oldWord, replacement, elementOrText) {
718 |
719 | if (typeof elementOrText === 'string') {
720 | return this.parser.replaceWordInText(oldWord, replacement, elementOrText);
721 | }
722 |
723 | var element = elementOrText || this.spellCheckerElement;
724 | var index = this.elements.index(element);
725 |
726 | this.trigger('replace.word.before');
727 | this.parser.replaceWord(oldWord, replacement, element);
728 | this.trigger('replace.word', index);
729 | };
730 |
731 | SpellChecker.prototype.destroy = function() {
732 | this.trigger('destroy');
733 | };
734 |
735 | /* Event handlers */
736 |
737 | SpellChecker.prototype.onCheckWords = function(callback) {
738 |
739 | return function(data) {
740 |
741 | var incorrectWords = data.data;
742 | var outcome = 'success';
743 |
744 | $.each(incorrectWords, function(i, words) {
745 | if (words.length) {
746 | outcome = 'fail';
747 | return false;
748 | }
749 | });
750 |
751 | this.trigger('check.complete');
752 | this.trigger('check.' + outcome, incorrectWords);
753 | this.trigger(callback, incorrectWords);
754 |
755 | }.bind(this);
756 | };
757 |
758 | SpellChecker.prototype.onCheckFail = function(badWords) {
759 | this.suggestBox.detach();
760 | $.each(badWords, function(i, words) {
761 | if (words.length) {
762 | // Make array unique
763 | words = $.grep(words, function(el, index){
764 | return index === $.inArray(el, words);
765 | });
766 | this.incorrectWords.get(i).addWords(words);
767 | }
768 | }.bind(this));
769 | this.suggestBox.reattach();
770 | };
771 |
772 | SpellChecker.prototype.onSelectWord = function(e, word, element) {
773 | e.preventDefault();
774 | this.replaceWord(this.incorrectWord, word);
775 | };
776 |
777 | SpellChecker.prototype.onIgnoreWord = function(e, word, element) {
778 | e.preventDefault();
779 | this.replaceWord(this.incorrectWord, this.incorrectWord);
780 | };
781 |
782 | SpellChecker.prototype.onIncorrectWordSelect = function(e, word, element, incorrectWords) {
783 | e.preventDefault();
784 | this.incorrectWord = word;
785 | this.incorrectWordElement = element;
786 | this.spellCheckerElement = incorrectWords.spellCheckerElement;
787 | this.spellCheckerIndex = this.elements.index(this.spellCheckerElement);
788 | this.suggestBox.showSuggestedWords(this.getSuggestions.bind(this), word, element);
789 | this.trigger('select.word', e);
790 | };
791 |
792 | $.SpellChecker = SpellChecker;
793 |
794 | }(this, jQuery));
795 |
796 | /**
797 | * Some small changes were made by Richard Willis to allow this
798 | * code to pass the project-configured jshint
799 | *
800 | * findAndReplaceDOMText v 0.2
801 | * @author James Padolsey http://james.padolsey.com
802 | * @license http://unlicense.org/UNLICENSE
803 | *
804 | * Matches the text of a DOM node against a regular expression
805 | * and replaces each match (or node-separated portions of the match)
806 | * in the specified element.
807 | *
808 | * Example: Wrap 'test' in :
809 | * This is a test
810 | *
817 | */
818 | window.findAndReplaceDOMText = (function() {
819 |
820 | /**
821 | * findAndReplaceDOMText
822 | *
823 | * Locates matches and replaces with replacementNode
824 | *
825 | * @param {RegExp} regex The regular expression to match
826 | * @param {Node} node Element or Text node to search within
827 | * @param {String|Element|Function} replacementNode A NodeName,
828 | * Node to clone, or a function which returns a node to use
829 | * as the replacement node.
830 | * @param {Number} captureGroup A number specifiying which capture
831 | * group to use in the match. (optional)
832 | */
833 | function findAndReplaceDOMText(regex, node, replacementNode, captureGroup) {
834 |
835 | var m, matches = [], text = _getText(node);
836 | var replaceFn = _genReplacer(replacementNode);
837 |
838 | if (!text) { return; }
839 |
840 | if (regex.global) {
841 | while (!!(m = regex.exec(text))) {
842 | matches.push(_getMatchIndexes(m, captureGroup));
843 | }
844 | } else {
845 | m = text.match(regex);
846 | matches.push(_getMatchIndexes(m, captureGroup));
847 | }
848 |
849 | if (matches.length) {
850 | _stepThroughMatches(node, matches, replaceFn);
851 | }
852 | }
853 |
854 | /**
855 | * Gets the start and end indexes of a match
856 | */
857 | function _getMatchIndexes(m, captureGroup) {
858 |
859 | captureGroup = captureGroup || 0;
860 |
861 | if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches';
862 |
863 | var index = m.index;
864 |
865 | if (captureGroup > 0) {
866 | var cg = m[captureGroup];
867 | if (!cg) throw 'Invalid capture group';
868 | index += m[0].indexOf(cg);
869 | m[0] = cg;
870 | }
871 |
872 | return [ index, index + m[0].length, [ m[0] ] ];
873 | }
874 |
875 | /**
876 | * Gets aggregate text of a node without resorting
877 | * to broken innerText/textContent
878 | */
879 | function _getText(node) {
880 |
881 | if (node.nodeType === 3) {
882 | return node.data;
883 | }
884 |
885 | var txt = '';
886 |
887 | if (!!(node = node.firstChild)) do {
888 | txt += _getText(node);
889 | } while (!!(node = node.nextSibling));
890 |
891 | return txt;
892 |
893 | }
894 |
895 | /**
896 | * Steps through the target node, looking for matches, and
897 | * calling replaceFn when a match is found.
898 | */
899 | function _stepThroughMatches(node, matches, replaceFn) {
900 |
901 | var after, before,
902 | startNode,
903 | endNode,
904 | startNodeIndex,
905 | endNodeIndex,
906 | innerNodes = [],
907 | atIndex = 0,
908 | curNode = node,
909 | matchLocation = matches.shift(),
910 | matchIndex = 0;
911 |
912 | out: while (true) {
913 |
914 | if (curNode.nodeType === 3) {
915 | if (!endNode && curNode.length + atIndex >= matchLocation[1]) {
916 | // We've found the ending
917 | endNode = curNode;
918 | endNodeIndex = matchLocation[1] - atIndex;
919 | } else if (startNode) {
920 | // Intersecting node
921 | innerNodes.push(curNode);
922 | }
923 | if (!startNode && curNode.length + atIndex > matchLocation[0]) {
924 | // We've found the match start
925 | startNode = curNode;
926 | startNodeIndex = matchLocation[0] - atIndex;
927 | }
928 | atIndex += curNode.length;
929 | }
930 |
931 | if (startNode && endNode) {
932 | curNode = replaceFn({
933 | startNode: startNode,
934 | startNodeIndex: startNodeIndex,
935 | endNode: endNode,
936 | endNodeIndex: endNodeIndex,
937 | innerNodes: innerNodes,
938 | match: matchLocation[2],
939 | matchIndex: matchIndex
940 | });
941 | // replaceFn has to return the node that replaced the endNode
942 | // and then we step back so we can continue from the end of the
943 | // match:
944 | atIndex -= (endNode.length - endNodeIndex);
945 | startNode = null;
946 | endNode = null;
947 | innerNodes = [];
948 | matchLocation = matches.shift();
949 | matchIndex++;
950 | if (!matchLocation) {
951 | break; // no more matches
952 | }
953 | } else if (curNode.firstChild || curNode.nextSibling) {
954 | // Move down or forward:
955 | curNode = curNode.firstChild || curNode.nextSibling;
956 | continue;
957 | }
958 |
959 | // Move forward or up:
960 | while (true) {
961 | if (curNode.nextSibling) {
962 | curNode = curNode.nextSibling;
963 | break;
964 | } else if (curNode.parentNode !== node) {
965 | curNode = curNode.parentNode;
966 | } else {
967 | break out;
968 | }
969 | }
970 |
971 | }
972 |
973 | }
974 |
975 | var reverts;
976 | /**
977 | * Reverts the last findAndReplaceDOMText process
978 | */
979 | findAndReplaceDOMText.revert = function revert() {
980 | for (var i = 0, l = reverts.length; i < l; ++i) {
981 | reverts[i]();
982 | }
983 | reverts = [];
984 | };
985 |
986 | /**
987 | * Generates the actual replaceFn which splits up text nodes
988 | * and inserts the replacement element.
989 | */
990 | function _genReplacer(nodeName) {
991 |
992 | reverts = [];
993 |
994 | var makeReplacementNode;
995 |
996 | if (typeof nodeName !== 'function') {
997 | var stencilNode = nodeName.nodeType ? nodeName : document.createElement(nodeName);
998 | makeReplacementNode = function(fill) {
999 | var clone = document.createElement('div'),
1000 | el;
1001 | clone.innerHTML = stencilNode.outerHTML || new window.XMLSerializer().serializeToString(stencilNode);
1002 | el = clone.firstChild;
1003 | if (fill) {
1004 | el.appendChild(document.createTextNode(fill));
1005 | }
1006 | return el;
1007 | };
1008 | } else {
1009 | makeReplacementNode = nodeName;
1010 | }
1011 |
1012 | return function replace(range) {
1013 |
1014 | var startNode = range.startNode,
1015 | endNode = range.endNode,
1016 | matchIndex = range.matchIndex,
1017 | before, after;
1018 |
1019 | if (startNode === endNode) {
1020 | var node = startNode;
1021 | if (range.startNodeIndex > 0) {
1022 | // Add `before` text node (before the match)
1023 | before = document.createTextNode(node.data.substring(0, range.startNodeIndex));
1024 | node.parentNode.insertBefore(before, node);
1025 | }
1026 |
1027 | // Create the replacement node:
1028 | var el = makeReplacementNode(range.match[0], matchIndex, range.match[0]);
1029 | node.parentNode.insertBefore(el, node);
1030 | if (range.endNodeIndex < node.length) {
1031 | // Add `after` text node (after the match)
1032 | after = document.createTextNode(node.data.substring(range.endNodeIndex));
1033 | node.parentNode.insertBefore(after, node);
1034 | }
1035 | node.parentNode.removeChild(node);
1036 | reverts.push(function() {
1037 | var pnode = el.parentNode;
1038 | pnode.insertBefore(el.firstChild, el);
1039 | pnode.removeChild(el);
1040 | pnode.normalize();
1041 | });
1042 | return el;
1043 | } else {
1044 | // Replace startNode -> [innerNodes...] -> endNode (in that order)
1045 | before = document.createTextNode(startNode.data.substring(0, range.startNodeIndex));
1046 | after = document.createTextNode(endNode.data.substring(range.endNodeIndex));
1047 | var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex, range.match[0]);
1048 | var innerEls = [];
1049 | for (var i = 0, l = range.innerNodes.length; i < l; ++i) {
1050 | var innerNode = range.innerNodes[i];
1051 | var innerEl = makeReplacementNode(innerNode.data, matchIndex, range.match[0]);
1052 | innerNode.parentNode.replaceChild(innerEl, innerNode);
1053 | innerEls.push(innerEl);
1054 | }
1055 | var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex, range.match[0]);
1056 | startNode.parentNode.insertBefore(before, startNode);
1057 | startNode.parentNode.insertBefore(elA, startNode);
1058 | startNode.parentNode.removeChild(startNode);
1059 | endNode.parentNode.insertBefore(elB, endNode);
1060 | endNode.parentNode.insertBefore(after, endNode);
1061 | endNode.parentNode.removeChild(endNode);
1062 | reverts.push(function() {
1063 | innerEls.unshift(elA);
1064 | innerEls.push(elB);
1065 | for (var i = 0, l = innerEls.length; i < l; ++i) {
1066 | var el = innerEls[i];
1067 | var pnode = el.parentNode;
1068 | pnode.insertBefore(el.firstChild, el);
1069 | pnode.removeChild(el);
1070 | pnode.normalize();
1071 | }
1072 | });
1073 | return elB;
1074 | }
1075 | };
1076 |
1077 | }
1078 |
1079 | return findAndReplaceDOMText;
1080 |
1081 | }());
--------------------------------------------------------------------------------
/tooltipster-sideTip-shadow.min.css:
--------------------------------------------------------------------------------
1 | .tooltipster-sidetip.tooltipster-shadow .tooltipster-box{border:none;border-radius:5px;background:#fff;box-shadow:0 0 10px 6px rgba(0,0,0,.1)}.tooltipster-sidetip.tooltipster-shadow.tooltipster-bottom .tooltipster-box{margin-top:6px}.tooltipster-sidetip.tooltipster-shadow.tooltipster-left .tooltipster-box{margin-right:6px}.tooltipster-sidetip.tooltipster-shadow.tooltipster-right .tooltipster-box{margin-left:6px}.tooltipster-sidetip.tooltipster-shadow.tooltipster-top .tooltipster-box{margin-bottom:6px}.tooltipster-sidetip.tooltipster-shadow .tooltipster-content{color:#8d8d8d}.tooltipster-sidetip.tooltipster-shadow .tooltipster-arrow{height:6px;margin-left:-6px;width:12px}.tooltipster-sidetip.tooltipster-shadow.tooltipster-left .tooltipster-arrow,.tooltipster-sidetip.tooltipster-shadow.tooltipster-right .tooltipster-arrow{height:12px;margin-left:0;margin-top:-6px;width:6px}.tooltipster-sidetip.tooltipster-shadow .tooltipster-arrow-background{display:none}.tooltipster-sidetip.tooltipster-shadow .tooltipster-arrow-border{border:6px solid transparent}.tooltipster-sidetip.tooltipster-shadow.tooltipster-bottom .tooltipster-arrow-border{border-bottom-color:#fff}.tooltipster-sidetip.tooltipster-shadow.tooltipster-left .tooltipster-arrow-border{border-left-color:#fff}.tooltipster-sidetip.tooltipster-shadow.tooltipster-right .tooltipster-arrow-border{border-right-color:#fff}.tooltipster-sidetip.tooltipster-shadow.tooltipster-top .tooltipster-arrow-border{border-top-color:#fff}.tooltipster-sidetip.tooltipster-shadow.tooltipster-bottom .tooltipster-arrow-uncropped{top:-6px}.tooltipster-sidetip.tooltipster-shadow.tooltipster-right .tooltipster-arrow-uncropped{left:-6px}
--------------------------------------------------------------------------------
/tooltipster.bundle.css:
--------------------------------------------------------------------------------
1 | /* This is the core CSS of Tooltipster */
2 |
3 | /* GENERAL STRUCTURE RULES (do not edit this section) */
4 |
5 | .tooltipster-base {
6 | /* this ensures that a constrained height set by functionPosition,
7 | if greater that the natural height of the tooltip, will be enforced
8 | in browsers that support display:flex */
9 | display: flex;
10 | pointer-events: none;
11 | /* this may be overriden in JS for fixed position origins */
12 | position: absolute;
13 | }
14 |
15 | .tooltipster-box {
16 | /* see .tooltipster-base. flex-shrink 1 is only necessary for IE10-
17 | and flex-basis auto for IE11- (at least) */
18 | flex: 1 1 auto;
19 | }
20 |
21 | .tooltipster-content {
22 | /* prevents an overflow if the user adds padding to the div */
23 | box-sizing: border-box;
24 | /* these make sure we'll be able to detect any overflow */
25 | max-height: 100%;
26 | max-width: 100%;
27 | overflow: auto;
28 | }
29 |
30 | .tooltipster-ruler {
31 | /* these let us test the size of the tooltip without overflowing the window */
32 | bottom: 0;
33 | left: 0;
34 | overflow: hidden;
35 | position: fixed;
36 | right: 0;
37 | top: 0;
38 | visibility: hidden;
39 | }
40 |
41 | /* ANIMATIONS */
42 |
43 | /* Open/close animations */
44 |
45 | /* fade */
46 |
47 | .tooltipster-fade {
48 | opacity: 0;
49 | -webkit-transition-property: opacity;
50 | -moz-transition-property: opacity;
51 | -o-transition-property: opacity;
52 | -ms-transition-property: opacity;
53 | transition-property: opacity;
54 | }
55 | .tooltipster-fade.tooltipster-show {
56 | opacity: 1;
57 | }
58 |
59 | /* grow */
60 |
61 | .tooltipster-grow {
62 | -webkit-transform: scale(0,0);
63 | -moz-transform: scale(0,0);
64 | -o-transform: scale(0,0);
65 | -ms-transform: scale(0,0);
66 | transform: scale(0,0);
67 | -webkit-transition-property: -webkit-transform;
68 | -moz-transition-property: -moz-transform;
69 | -o-transition-property: -o-transform;
70 | -ms-transition-property: -ms-transform;
71 | transition-property: transform;
72 | -webkit-backface-visibility: hidden;
73 | }
74 | .tooltipster-grow.tooltipster-show {
75 | -webkit-transform: scale(1,1);
76 | -moz-transform: scale(1,1);
77 | -o-transform: scale(1,1);
78 | -ms-transform: scale(1,1);
79 | transform: scale(1,1);
80 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
81 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
82 | -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
83 | -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
84 | -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
85 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
86 | }
87 |
88 | /* swing */
89 |
90 | .tooltipster-swing {
91 | opacity: 0;
92 | -webkit-transform: rotateZ(4deg);
93 | -moz-transform: rotateZ(4deg);
94 | -o-transform: rotateZ(4deg);
95 | -ms-transform: rotateZ(4deg);
96 | transform: rotateZ(4deg);
97 | -webkit-transition-property: -webkit-transform, opacity;
98 | -moz-transition-property: -moz-transform;
99 | -o-transition-property: -o-transform;
100 | -ms-transition-property: -ms-transform;
101 | transition-property: transform;
102 | }
103 | .tooltipster-swing.tooltipster-show {
104 | opacity: 1;
105 | -webkit-transform: rotateZ(0deg);
106 | -moz-transform: rotateZ(0deg);
107 | -o-transform: rotateZ(0deg);
108 | -ms-transform: rotateZ(0deg);
109 | transform: rotateZ(0deg);
110 | -webkit-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 1);
111 | -webkit-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
112 | -moz-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
113 | -ms-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
114 | -o-transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
115 | transition-timing-function: cubic-bezier(0.230, 0.635, 0.495, 2.4);
116 | }
117 |
118 | /* fall */
119 |
120 | .tooltipster-fall {
121 | -webkit-transition-property: top;
122 | -moz-transition-property: top;
123 | -o-transition-property: top;
124 | -ms-transition-property: top;
125 | transition-property: top;
126 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
127 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
128 | -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
129 | -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
130 | -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
131 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
132 | }
133 | .tooltipster-fall.tooltipster-initial {
134 | top: 0 !important;
135 | }
136 | .tooltipster-fall.tooltipster-show {
137 | }
138 | .tooltipster-fall.tooltipster-dying {
139 | -webkit-transition-property: all;
140 | -moz-transition-property: all;
141 | -o-transition-property: all;
142 | -ms-transition-property: all;
143 | transition-property: all;
144 | top: 0 !important;
145 | opacity: 0;
146 | }
147 |
148 | /* slide */
149 |
150 | .tooltipster-slide {
151 | -webkit-transition-property: left;
152 | -moz-transition-property: left;
153 | -o-transition-property: left;
154 | -ms-transition-property: left;
155 | transition-property: left;
156 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
157 | -webkit-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
158 | -moz-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
159 | -ms-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
160 | -o-transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
161 | transition-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.15);
162 | }
163 | .tooltipster-slide.tooltipster-initial {
164 | left: -40px !important;
165 | }
166 | .tooltipster-slide.tooltipster-show {
167 | }
168 | .tooltipster-slide.tooltipster-dying {
169 | -webkit-transition-property: all;
170 | -moz-transition-property: all;
171 | -o-transition-property: all;
172 | -ms-transition-property: all;
173 | transition-property: all;
174 | left: 0 !important;
175 | opacity: 0;
176 | }
177 |
178 | /* Update animations */
179 |
180 | /* We use animations rather than transitions here because
181 | transition durations may be specified in the style tag due to
182 | animationDuration, and we try to avoid collisions and the use
183 | of !important */
184 |
185 | /* fade */
186 |
187 | @keyframes tooltipster-fading {
188 | 0% {
189 | opacity: 0;
190 | }
191 | 100% {
192 | opacity: 1;
193 | }
194 | }
195 |
196 | .tooltipster-update-fade {
197 | animation: tooltipster-fading 400ms;
198 | }
199 |
200 | /* rotate */
201 |
202 | @keyframes tooltipster-rotating {
203 | 25% {
204 | transform: rotate(-2deg);
205 | }
206 | 75% {
207 | transform: rotate(2deg);
208 | }
209 | 100% {
210 | transform: rotate(0);
211 | }
212 | }
213 |
214 | .tooltipster-update-rotate {
215 | animation: tooltipster-rotating 600ms;
216 | }
217 |
218 | /* scale */
219 |
220 | @keyframes tooltipster-scaling {
221 | 50% {
222 | transform: scale(1.1);
223 | }
224 | 100% {
225 | transform: scale(1);
226 | }
227 | }
228 |
229 | .tooltipster-update-scale {
230 | animation: tooltipster-scaling 600ms;
231 | }
232 |
233 | /**
234 | * DEFAULT STYLE OF THE SIDETIP PLUGIN
235 | *
236 | * All styles are "namespaced" with .tooltipster-sidetip to prevent
237 | * conflicts between plugins.
238 | */
239 |
240 | /* .tooltipster-box */
241 |
242 | .tooltipster-sidetip .tooltipster-box {
243 | background: #565656;
244 | border: 2px solid black;
245 | border-radius: 4px;
246 | }
247 |
248 | .tooltipster-sidetip.tooltipster-bottom .tooltipster-box {
249 | margin-top: 8px;
250 | }
251 |
252 | .tooltipster-sidetip.tooltipster-left .tooltipster-box {
253 | margin-right: 8px;
254 | }
255 |
256 | .tooltipster-sidetip.tooltipster-right .tooltipster-box {
257 | margin-left: 8px;
258 | }
259 |
260 | .tooltipster-sidetip.tooltipster-top .tooltipster-box {
261 | margin-bottom: 8px;
262 | }
263 |
264 | /* .tooltipster-content */
265 |
266 | .tooltipster-sidetip .tooltipster-content {
267 | color: white;
268 | line-height: 18px;
269 | padding: 6px 14px;
270 | }
271 |
272 | /* .tooltipster-arrow : will keep only the zone of .tooltipster-arrow-uncropped that
273 | corresponds to the arrow we want to display */
274 |
275 | .tooltipster-sidetip .tooltipster-arrow {
276 | overflow: hidden;
277 | position: absolute;
278 | }
279 |
280 | .tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow {
281 | height: 10px;
282 | /* half the width, for centering */
283 | margin-left: -10px;
284 | top: 0;
285 | width: 20px;
286 | }
287 |
288 | .tooltipster-sidetip.tooltipster-left .tooltipster-arrow {
289 | height: 20px;
290 | margin-top: -10px;
291 | right: 0;
292 | /* top 0 to keep the arrow from overflowing .tooltipster-base when it has not
293 | been positioned yet */
294 | top: 0;
295 | width: 10px;
296 | }
297 |
298 | .tooltipster-sidetip.tooltipster-right .tooltipster-arrow {
299 | height: 20px;
300 | margin-top: -10px;
301 | left: 0;
302 | /* same as .tooltipster-left .tooltipster-arrow */
303 | top: 0;
304 | width: 10px;
305 | }
306 |
307 | .tooltipster-sidetip.tooltipster-top .tooltipster-arrow {
308 | bottom: 0;
309 | height: 10px;
310 | margin-left: -10px;
311 | width: 20px;
312 | }
313 |
314 | /* common rules between .tooltipster-arrow-background and .tooltipster-arrow-border */
315 |
316 | .tooltipster-sidetip .tooltipster-arrow-background, .tooltipster-sidetip .tooltipster-arrow-border {
317 | height: 0;
318 | position: absolute;
319 | width: 0;
320 | }
321 |
322 | /* .tooltipster-arrow-background */
323 |
324 | .tooltipster-sidetip .tooltipster-arrow-background {
325 | border: 10px solid transparent;
326 | }
327 |
328 | .tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-background {
329 | border-bottom-color: #565656;
330 | left: 0px;
331 | top: 3px;
332 | }
333 |
334 | .tooltipster-sidetip.tooltipster-left .tooltipster-arrow-background {
335 | border-left-color: #565656;
336 | left: -3px;
337 | top: 0px;
338 | }
339 |
340 | .tooltipster-sidetip.tooltipster-right .tooltipster-arrow-background {
341 | border-right-color: #565656;
342 | left: 3px;
343 | top: 0px;
344 | }
345 |
346 | .tooltipster-sidetip.tooltipster-top .tooltipster-arrow-background {
347 | border-top-color: #565656;
348 | left: 0px;
349 | top: -3px;
350 | }
351 |
352 | /* .tooltipster-arrow-border */
353 |
354 | .tooltipster-sidetip .tooltipster-arrow-border {
355 | border: 10px solid transparent;
356 | left: 0;
357 | top: 0;
358 | }
359 |
360 | .tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-border {
361 | border-bottom-color: black;
362 | }
363 |
364 | .tooltipster-sidetip.tooltipster-left .tooltipster-arrow-border {
365 | border-left-color: black;
366 | }
367 |
368 | .tooltipster-sidetip.tooltipster-right .tooltipster-arrow-border {
369 | border-right-color: black;
370 | }
371 |
372 | .tooltipster-sidetip.tooltipster-top .tooltipster-arrow-border {
373 | border-top-color: black;
374 | }
375 |
376 | /* tooltipster-arrow-uncropped */
377 |
378 | .tooltipster-sidetip .tooltipster-arrow-uncropped {
379 | position: relative;
380 | }
381 |
382 | .tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-uncropped {
383 | top: -10px;
384 | }
385 |
386 | .tooltipster-sidetip.tooltipster-right .tooltipster-arrow-uncropped {
387 | left: -10px;
388 | }
389 |
--------------------------------------------------------------------------------