0/*!
1 * kimbo v1.0.4 - 2013-12-11
2 * http://kimbojs.com
3 * Copyright (c) 2013 Denis Ciccale (@tdecs)
4 * Released under the MIT license
5 * https://github.com/dciccale/kimbo.js/blob/master/LICENSE.txt
6 */
7(function (window, document) {
8
9 'use strict';
10
11 /*\
12 * $
13 [ object ]
14 * Global namespace for using Kimbo functions
15 \*/
16
17 // Kimbo modules
18 var modules = {};
19
20 // Common helpers
21 var _ = {
22
23 // Array methods
24 push: Array.prototype.push,
25 slice: Array.prototype.slice,
26
27 // Reference to the current document
28 document: document,
29 rootContext: document,
30
31 // Creates and returns a new Kimbo object
32 kimbo: function (element) {
33 return new Kimbo(element);
34 }
35 };
36
37 /*\
38 * $(…)
39 * Kimbo object collection.
40 * All methods called from a Kimbo collection affects all elements in it.
41 \*/
42 var Kimbo = function (selector, context) {
43 var match;
44
45 // Auto create a new instance of Kimbo if needed
46 if (!(this instanceof Kimbo)) {
47 return new Kimbo(selector, context);
48 }
49
50 // No selector, return empty Kimbo object
51 if (!selector) {
52 return this;
53 }
54
55 // Asume a css selector, query the dom
56 if (typeof selector === 'string') {
57
58 // Handle faster $('#id');
59 match = /^#([\w\-]+)$/.exec(selector);
60 if (match && match[1]) {
61 match = document.getElementById(match[1]);
62
63 if (match) {
64 this[0] = match;
65 this.length = 1;
66 }
67
68 return this;
69 }
70
71 // All other selectors
72 context = context ? _.kimbo(context) : _.rootContext;
73
74 return context.find(selector);
75 }
76
77 // Already a dom element
78 if (selector.nodeType) {
79 this[0] = selector;
80 this.length = 1;
81 return this;
82 }
83
84 // Is a function, call it when DOM is ready
85 if (Kimbo.isFunction(selector)) {
86 return _.rootContext.ready(selector);
87 }
88
89 // Handle kimbo object, plain objects or other objects
90 return Kimbo.makeArray(selector, this);
91 };
92
93 Kimbo.require = function (module) {
94 return modules[module];
95 };
96
97 Kimbo.define = function (module, fn) {
98 modules[module] = fn(_);
99 };
100
101 /*
102 * Kimbo prototype aliased as fn
103 */
104 Kimbo.prototype = Kimbo.fn = {
105
106 /*\
107 * $(…).length
108 [ property ]
109 * The length of the current Kimbo collection.
110 = (number) Integer representing the lenght of the current collection.
111 > Usage
112 * Having these paragraphs:
113 | <p>one</p>
114 | <p>two</p>
115 | <p>three</p>
116 * Grab them and check for the length property:
117 | $('p').length; // 3
118 \*/
119 length: 0,
120
121 /*\
122 * $(…).ready
123 [ method ]
124 * Execute a callback after de DOM is completely loaded
125 > Parameters
126 - callback (function) A function to call after de DOM is ready
127 = (object) The original element
128 > Usage
129 | $(document).ready(function () {
130 | console.log('the DOM is loaded!');
131 | });
132 * Or using the shortcut (recommended)
133 | $(function () {
134 | console.log('the DOM is loaded!);
135 | });
136 \*/
137 ready: function (callback) {
138 var completed;
139
140 // First check if already loaded, interactive or complete state so the t is enough
141 if (/t/.test(document.readyState)) {
142
143 // Execute the callback
144 callback.call(document);
145
146 // If not listen for when it loads
147 } else {
148 completed = function () {
149
150 // When completed remove the listener
151 document.removeEventListener('DOMContentLoaded', completed, false);
152
153 // Execute the callback
154 callback.call(document);
155 };
156
157 // Register the event
158 document.addEventListener('DOMContentLoaded', completed, false);
159 }
160
161 // Return the Kimbo wrapped document
162 return _.rootContext;
163 },
164
165 /*\
166 * $(…).get
167 [ method ]
168 * Retrieve native DOM elements from the current collection
169 > Parameters
170 - index (number) #optional A zero-based integer indicating which element to retrieve, supports going backwards with negative index.
171 = (array|object) An array of the native DOM elements or the specified element by index matched by Kimbo.
172 > Usage
173 | <ul>
174 | <li id="foo"></li>
175 | <li id="bar"></li>
176 | </ul>
177 * Get standard array of elements
178 | $('li').get(); // [<li id="foo">, <li id="bar">]
179 * Get the first element
180 | $('li').get(0); // <li id="foo">
181 * Get the last element
182 | $('li').get(-1); // <li id="bar">
183 \*/
184 get: function (index) {
185 if (!this.length) {
186 return;
187 }
188
189 // If no index specified return a new set
190 // Else return the element in the specified positive index or backwards if negative
191 return (!arguments.length) ? _.slice.call(this) :
192 (index < 0 ? this[this.length + index] : this[index]);
193 },
194
195 // Needed to have an array-like object
196 splice: Array.prototype.splice
197 };
198
199 /*\
200 * $.forEach
201 [ method ]
202 * Iterator function that can be used to seamlessly iterate over both objects and arrays.
203 > Parameters
204 - obj (object) Object or array to iterate
205 - callback (function) Function that will be executed on each iteration
206 = (object) The original array or object
207 > Usage
208 | // Iterating array
209 | $.forEach(['a', 'b', 'c'], function (index, value) {
210 | alert(index + ': ' + value);
211 | });
212 |
213 | // Iterating object
214 | $.forEach({name: 'Denis', surname: 'Ciccale'}, function (key, value) {
215 | alert(key + ': ' + value);
216 | });
217 \*/
218 Kimbo.forEach = function (obj, callback) {
219 var l = obj.length;
220 var isObj = l === undefined || typeof obj === 'function';
221 var i;
222
223 if (isObj) {
224 for (i in obj) {
225 if (obj.hasOwnProperty(i) && callback.call(obj[i], i, obj[i], obj) === false) {
226 break;
227 }
228 }
229 } else {
230 for (i = 0; i < l; i++) {
231 if (callback.call(obj[i], obj[i], i, obj) === false) {
232 break;
233 }
234 }
235 }
236
237 // Return original obj
238 return obj;
239 };
240
241
242 /*\
243 * $.extend
244 [ method ]
245 * Merge the contents of two or more objects together into the first object.
246 > Parameters
247 - target (object|boolean) Object that will receive the merged properties if objects are passed, or target will extend Kimbo object. If target is `true` the passed objects will be recursively merged.
248 - objectN (object) #optional One or more objects with additional properties.
249 = (object) The extended target or a new copy if target is an empty object.
250 > Usage
251 * When passing two or more objects, all properties will be merged into the target object.
252 | var obj1 = { msg: 'hi', info: { from: 'Denis' }};
253 | var obj2 = { msg: 'Hi!', info: { time: '22:00PM' }};
254 |
255 | // Merge obj1 into obj2
256 | $.extend(obj1, obj2);
257 |
258 | // Now obj1 is equal to:
259 | { msg: 'Hi!', info: { time: '22:00PM' }}
260 * If an empty target object is passed, none of the other objects will be directly modified
261 | // Pass an empty target
262 | var obj3 = $.extend({}, obj1, obj);
263 * To do a recursive merge, pass true as first argument, then the objects to merge
264 | $.extend(true, obj1, obj2);
265 | // Obj1 will be:
266 | { msg: 'Hi!', info: { from: 'Denis', time: '22:00PM' }}
267 \*/
268 Kimbo.extend = Kimbo.fn.extend = function () {
269 var objs = arguments;
270 var target = objs[0] || {};
271 var deep = (target === true);
272 var cut = 1;
273
274 // Check for deep copy
275 if (deep) {
276 target = objs[1] || {};
277 cut = 2;
278
279 // Extend Kimbo itself if only one argument is passed
280 } else if (objs.length === 1) {
281 target = this;
282 cut = 0;
283 }
284
285 // Make an array from the arguments removing the target and/or the deep boolean
286 objs = _.slice.call(objs, cut);
287
288 // Loop through the objects
289 Kimbo.forEach(objs, function (source) {
290
291 // Populate target from source
292 Kimbo.forEach(source, function (key, value) {
293 var src;
294
295 if (deep && (Kimbo.isObject(value) || Kimbo.isArray(value))) {
296 src = target[key];
297 target[key] = Kimbo.extend(deep, src, value);
298
299 } else if (value !== undefined) {
300 target[key] = value;
301 }
302 });
303 });
304
305 return target;
306 };
307
308 // Unique reference for the current instance of Kimbo
309 Kimbo.ref = 'kimbo' + ('1' + Math.random()).replace(/\D/g, '');
310
311 // Expose Kimbo as an AMD module
312 if (typeof window.define === 'function' && window.define.amd) {
313 window.define('kimbo', [], function () {
314 return Kimbo;
315 });
316 }
317
318 // Expose Kimbo to global object
319 window.Kimbo = window.$ = Kimbo;
320
321}(window, window.document));
322
323
324Kimbo.define('query', function (_) {
325
326 'use strict';
327
328 // Selector regexes
329 var ID_RE = /^#([\w\-]+)$/;
330 var CLASS_RE = /^\.([\w\-]+)$/;
331 var TAG_RE = /^[\w\-]+$/;
332 var NAME_RE = /^\[name=["']?([\w\-]+)["']?\]$/;
333
334 // Use querySelectoAll but optimize for id, class, tagName and name
335 var _find = function (element, selector) {
336 var els = [], sel;
337
338 // #id
339 if (element === _.document && (sel = ID_RE.exec(selector))) {
340 els = element.getElementById(sel[1]);
341
342 // .class
343 } else if ((sel = CLASS_RE.exec(selector))) {
344 els = element.getElementsByClassName(sel[1]);
345
346 // tag
347 } else if (TAG_RE.test(selector)) {
348 els = element.getElementsByTagName(selector);
349
350 // [name=val]
351 } else if ((sel = NAME_RE.exec(selector))) {
352 els = element.getElementsByName(sel[1]);
353
354 // Other CSS selectors
355 } else {
356 els = element.querySelectorAll(selector);
357 }
358
359 // Return NodeList/Node as an array
360 return _.slice.call(els);
361 };
362
363 // DOM Level 4 contains improved
364 var _contains = function (el1, el2) {
365 return (el1 === window && (el2 && (el2 === window || el2.nodeType))) ||
366 ((el1 && el1.nodeType) &&
367 (el2 && el2.nodeType)) ? (el1.contains ? el1.contains(el2) : true) : false;
368 };
369
370 return {
371 find: _find,
372 contains: _contains
373 };
374});
375
376
377Kimbo.define('data', function () {
378
379 'use strict';
380
381 var cache = {};
382 var dataId = 1;
383
384 var data = {
385 get: function (el, key) {
386 var dataCache = cache[el._dataId];
387 var value;
388
389 // Look first in cached data
390 if (dataCache) {
391 value = dataCache[key];
392
393 // If none, try dataset
394 } else {
395 value = el.dataset[key];
396
397 // Cache the value
398 if (value) {
399 this.set(el, key, value);
400 }
401 }
402
403 return value;
404 },
405
406 set: function (el, key, value) {
407 var elementId = el._dataId || (el._dataId = dataId++);
408 var dataCache = cache[elementId];
409
410 // Create data cache for the current element if necessary
411 if (!dataCache) {
412 dataCache = cache[elementId] = {};
413 }
414
415 dataCache[key] = value;
416 },
417
418 remove: function (el, key) {
419 if (key === undefined) {
420 cache[el._dataId] = {};
421 } else {
422 delete cache[el._dataId][key];
423 }
424 }
425 };
426
427 Kimbo.fn.extend({
428 /*\
429 * $(…).data
430 [ method ]
431 * Store or retrieve elements dataset.
432 > Parameters
433 - key (string) Key of the data attribute to to set.
434 - value (string) #optional Value to store in dataset.
435 = (object) Original matched collection.
436 > Usage
437 | <div id="panel"></div>
438 * Set some data to the panel:
439 | $('#panel').data('isOpen', true);
440 * No a data-* attribute was added
441 | <div id="panel" data-isOpen="true"></div>
442 * We can retrieve the data
443 | $('#panel').data('isOpen'); // 'true'
444 \*/
445 data: function (key, value) {
446 if (!this.length || !Kimbo.isString(key)) {
447 return this;
448 }
449
450 key = Kimbo.camelCase(key);
451
452 // Get
453 if (value === undefined) {
454 return data.get(this[0], key);
455
456 // Set
457 } else {
458 return this.each(function (el) {
459 data.set(el, key, value);
460 });
461 }
462 },
463
464 /*\
465 * $(…).removeData
466 [ method ]
467 * Remove data from the element dataset.
468 > Parameters
469 - key (string) Key of the data attribute to to remove.
470 = (object) Original matched collection.
471 > Usage
472 | <div id="panel" data-isOpen="true"></div>
473 * Remove data associated to the panel div:
474 | $('#panel').removeData('isOpen');
475 * Data attribute and value was removed:
476 | <div id="panel"></div>
477 * data-isOpen is undefined
478 | $('#panel').data('isOpen'); // Undefined
479 \*/
480 removeData: function (key) {
481 if (!this.length || !Kimbo.isString(key)) {
482 return this;
483 }
484
485 key = Kimbo.camelCase(key);
486
487 return this.each(function (el) {
488 data.remove(el, key);
489 });
490 }
491 });
492
493 return data;
494});
495
496
497Kimbo.define('css', function (_) {
498
499 'use strict';
500
501 // Properties without 'px' at the end
502 var CSS_NO_PX = {
503 fontWeight: true,
504 lineHeight: true,
505 opacity: true,
506 zIndex: true
507 };
508
509 // Wrap native to extend behavoiur
510 var _getComputedStyle = function (element, property) {
511
512 // Support both camelCase and dashed property names
513 property = property.replace(/([A-Z])/g, '-$1').toLowerCase();
514
515 return window.getComputedStyle(element, null).getPropertyValue(property);
516 };
517
518 var iframe = null;
519
520 var createIframe = function () {
521 iframe = _.document.createElement('iframe');
522 _.document.documentElement.appendChild(iframe);
523 return iframe;
524 };
525
526 var getActualDisplay = function (nodeName, doc) {
527 doc = doc || _.document;
528
529 var elem, display;
530
531 // Create and append the node
532 elem = doc.createElement(nodeName);
533 doc.body.appendChild(elem);
534
535 // Get display
536 display = _getComputedStyle(elem, 'display');
537
538 // Remove it from the dom
539 elem.parentNode.removeChild(elem);
540
541 return display;
542 };
543
544 var elementsDisplay = {};
545
546 Kimbo.fn.extend({
547 /*\
548 * $(…).show
549 [ method ]
550 * Display all matched elements.
551 = (object) Original matched collection.
552 > Usage
553 | <p style="display: none;">Lorem</p>
554 * Show it
555 | $('p').show();
556 * Now it's visible
557 | <p style="display: block;">Lorem</p>
558 \*/
559 show: function () {
560 return this.each(function (el) {
561 var nodeName = el.nodeName;
562 var display = elementsDisplay[nodeName];
563 var doc;
564
565 if (!display) {
566 display = getActualDisplay(nodeName);
567
568 // If still fails for some css rule try creating the element in an isolated iframe
569 if (display === 'none' || !display) {
570
571 // Use the already-created iframe if possible
572 iframe = (iframe || createIframe());
573
574 doc = (iframe.contentWindow || iframe.contentDocument).document;
575 doc.write('<!doctype html><html><body>');
576 doc.close();
577 display = getActualDisplay(nodeName, doc);
578 iframe.parentNode.removeChild(iframe);
579 }
580
581 // Save the default display for this element
582 elementsDisplay[nodeName] = display;
583 }
584
585 el.style.display = display || 'block';
586 });
587 },
588
589 /*\
590 * $(…).hide
591 [ method ]
592 * Hide all matched elements.
593 = (object) Original matched collection.
594 > Usage
595 | <p>Lorem</p>
596 * Hide it
597 | $('p').hide();
598 * Now it's hidden
599 | <p style="display: none;">Lorem</p>
600 \*/
601 hide: function () {
602 return this.each(function (el) {
603 var nodeName = el.nodeName;
604 var display = elementsDisplay[nodeName];
605
606 if (!display) {
607 display = _getComputedStyle(el, 'display');
608 elementsDisplay[nodeName] = display;
609 } else {
610 display = el.style.display;
611 }
612
613 // Only hide if not already hidden
614 if (display !== 'none') {
615 el.style.display = 'none';
616 }
617 });
618 },
619
620 /*\
621 * $(…).css
622 [ method ]
623 * Get the css value of a property from one element or set a css property to all matched elements.
624 > Parameters
625 - property (string|object) Name of the css property.
626 - value (string|number) #optional Value for the property.
627 = (object) Original matched collection.
628 > Usage
629 | <p>Hello dude!</p>
630 * Modify some styles
631 | $('p').css('color', 'red'); // Now the text is red
632 * The HTML will look like this:
633 | <p style="color: red;">Hello dude!</p>
634 * You can also pass an object for setting multiple properties at the same time
635 | $('p').css({
636 | color: 'red',
637 | 'font-size': '30px',
638 | backgroundColor: 'blue'
639 | });
640 * Properties can be dash-separated (add quotes) or camelCase.
641 | <p style="color: red; font-size: 30px; background-color: blue">Hello dude!</p>
642 \*/
643 css: function (property, value) {
644 var that = this;
645 var setCss;
646
647 if (!this.length || (!Kimbo.isString(property) && !Kimbo.isObject(property))) {
648 return this;
649 }
650
651 setCss = function (name, value) {
652
653 // If it's a number add 'px' except for some properties
654 if (Kimbo.isNumeric(value) && !CSS_NO_PX[Kimbo.camelCase(name)]) {
655 value += 'px';
656 }
657
658 // Apply styles to all elements in the set
659 that.each(function (el) {
660 el.style[name] = value;
661 });
662 };
663
664 // Setting one property
665 if (Kimbo.isString(property)) {
666
667 // Get
668 if (value === undefined) {
669 return _getComputedStyle(this[0], property);
670
671 // Set
672 } else {
673 setCss(property, value);
674 }
675
676 // Multiple properties with an object
677 } else if (Kimbo.isObject(property)) {
678 Kimbo.forEach(property, setCss);
679 }
680
681 return this;
682 }
683 });
684});
685
686
687Kimbo.define('manipulation', function (_) {
688
689 'use strict';
690
691 var SPACE_RE = /\s+/;
692
693 // Browser native classList
694 var _hasClass = function (el, name) {
695 return (el.nodeType === 1 && el.classList.contains(name));
696 };
697
698 /*\
699 * $(…).text
700 [ method ]
701 * Get the text of the first element in the set or set the text of all the matched elements.
702 > Parameters
703 - value (string) #optional A string of text to set as the text content of all matched elements.
704 = (string) string value of the text content of the element if no parameter passed.
705 = (object) current Kimbo object.
706 > Usage
707 * Get the text content of an element
708 | <p>Demo text</p>
709 * without passing any parameter to the function:
710 | $('p').text(); // 'Demo text'
711 * To replace the text of the paragraph pass a string parameter to the function:
712 | $('p').text('Another text');
713 * Now the text content was replaced:
714 | <p>Another text</p>
715 \*/
716
717 /*\
718 * $(…).html
719 [ method ]
720 * Get the HTML content of the first element in the set or set the HTML content of all the matched elements.
721 > Parameters
722 - value (string) #optional A string of HTML to set as the HTML content of all matched elements.
723 = (string) a string value of the HTML content of the element if no parameter passed.
724 = (object) current Kimbo object.
725 > Usage
726 * Get the HTML content of an element
727 | <p><span>Demo text<span></p>
728 * without passing any parameter to the function:
729 | $('p').html(); // '<span>Demo tetxt</span>'
730 * To replace the HTML content of the paragraph pass a string parameter to the function:
731 | $('p').html('<strong>Another content</strong>');
732 * Now the text content was replaced:
733 | <p><strong>Another content</strong></p>
734 \*/
735
736 /*\
737 * $(…).val
738 [ method ]
739 * Get the current value of the first element in the set or set the value of all the matched elements.
740 > Parameters
741 - value (string) #optional A string of text to set as the value of all matched elements.
742 = (string) the current value of the first element if no parameter passed
743 = (object) current Kimbo object
744 > Usage
745 * Get the value of a form element:
746 | <input type="text" name="color" value="red" />
747 * without passing any parameter to the function:
748 | $('input').val(); // 'red'
749 * To change the value of the input pass a string parameter to the function:
750 | $('input').val('blue');
751 * Now the value was changed:
752 | $('input').val(); // 'blue'
753 \*/
754 Kimbo.forEach({
755 text: 'textContent',
756 html: 'innerHTML',
757 val: 'value'
758 }, function (method, prop) {
759 Kimbo.fn[method] = function (value) {
760
761 // No element
762 if (!this.length) {
763 return undefined;
764 }
765
766 // Get
767 if (value === undefined) {
768 return this[0][prop];
769
770 // Set
771 } else {
772 return this.each(function (el) {
773 el[prop] = value;
774 });
775 }
776 };
777 });
778
779 /*\
780 * $(…).addClass
781 [ method ]
782 * Adds a class to all matched elements.
783 > Parameters
784 - name (string) Name of the class to add.
785 = (object) Original matched collection.
786 > Usage
787 | <p>I want to be green</p>
788 | <script>
789 | $('p').addClass('green');
790 | </script>
791 * Now it's green
792 | <p class="green">I want to be green</p>
793 * You can add multiple classes separated by a space
794 | <p>Add classes to me</p>
795 | <script>
796 | $('p').addClass('green big width100');
797 | </script>
798 * All classes added and they won't be repetead if you try to add an existing one
799 | <p class="green big width100">Add classes to me</p>
800 \*/
801
802 /*\
803 * $(…).removeClass
804 [ method ]
805 * Removes a class to all matched elements.
806 > Parameters
807 - name (string) Name of the class to remove.
808 = (object) Original matched collection.
809 > Usage
810 | <p class="big green title float-rigth">Lorem ipsum</p>
811 * Remove a specific class from the paragraph:
812 | $('p').removeClass('green');
813 * The specified class was removed:
814 | <p class="big title float-right">Lorem ipsum</p>
815 * You can remove multiple classes separating them by a space when calling the function:
816 | $('p').removeClass('big title');
817 * Now only one class left:
818 | <p class="float-right">Lorem ipsum</p>
819 * You can remove all classes just calling .removeClass() without parameters
820 | $('p').removeClass();
821 * All classes were removed including the class attribute:
822 | <p>Lorem ipsum</p>
823 \*/
824
825 // Generate addClass and removeClass methods
826 // Use native classList
827 // Mdn: https://developer.mozilla.org/en-US/docs/DOM/element.classList
828 // Spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#dom-classlist
829 Kimbo.forEach(['add', 'remove'], function (method, i) {
830 var isRemove = i > 0;
831
832 Kimbo.fn[method + 'Class'] = function (name) {
833 var classNames;
834
835 if (name && Kimbo.isString(name)) {
836 classNames = name.split(SPACE_RE);
837 this.each(function (el) {
838
839 // Skip comments, text, etc
840 if (el.nodeType === 1) {
841
842 // Iterate through all class names passed
843 Kimbo.forEach(classNames, function (className) {
844 el.classList[method](className);
845 });
846 }
847 });
848
849 // Remove all element classes if no classname specified
850 } else if (!name && isRemove) {
851 this.removeAttr('class');
852 }
853
854 return this;
855 };
856 });
857
858 /*\
859 * $(…).append
860 [ method ]
861 * Insert content to the end of all elements matched by Kimbo.
862 > Parameters
863 - value (string|object) HTML string, DOM element, or Kimbo object to insert.
864 = (object) Original matched collection.
865 > Usage
866 | <div class="container">
867 | <p class="lorem">Lorem </p>
868 | <p class="lorem">Lorem </p>
869 | </div>
870 * Insert content
871 | $('.lorem').append('<em>ipsum</em>')
872 * Each element gets the content
873 | <div class="container">
874 | <p class="lorem">Lorem <em>ipsum</em></p>
875 | <p class="lorem">Lorem <em>ipsum</em></p>
876 | </div>
877 * You can also get an element and insert it elsewhere
878 | $('.container').append($('.ipsum'))
879 * The selected element will be moved, not cloned.
880 | <div class="container">
881 | <p class="lorem">Lorem</p>
882 | <p class="lorem">Lorem</p>
883 | <p class="ipsum">Ipsum</p>
884 | </div>
885 \*/
886
887 /*\
888 * $(…).prepend
889 [ method ]
890 * Insert content to the beginning of all elements matched by Kimbo.
891 > Parameters
892 - value (string|object) HTML string, DOM element, or Kimbo object to insert.
893 = (object) Original matched collection.
894 > Usage
895 | <div class="container">
896 | <p class="lorem"> Lorem</p>
897 | <p class="lorem"> Lorem</p>
898 | </div>
899 * Insert content
900 | $('.lorem').prepend('<em>ipsum</em>')
901 * Each element gets the content
902 | <div class="container">
903 | <p class="lorem"><em>ipsum</em> Lorem</p>
904 | <p class="lorem"><em>ipsum</em> Lorem</p>
905 | </div>
906 * You can also get an element and insert it elsewhere
907 | $('.container').prepend($('.ipsum'))
908 * The selected element will be moved, not cloned.
909 | <div class="container">
910 | <p class="ipsum">Ipsum</p>
911 | <p class="lorem">Lorem</p>
912 | <p class="lorem">Lorem</p>
913 | </div>
914 \*/
915
916 // Generate append and prepend methods
917 Kimbo.forEach(['append', 'prepend'], function (method, i) {
918 var isPrepend = i > 0;
919
920 Kimbo.fn[method] = function (value) {
921 var div;
922
923 // Exit if no value passed
924 if (!this.length || !value) {
925 return this;
926 }
927
928 // Handle html string
929 if (Kimbo.isString(value)) {
930
931 // Placeholder element
932 div = document.createElement('div');
933 div.innerHTML = value;
934 value = div.firstChild;
935 }
936
937 // Already a dom node or kimbo collection, just insert it
938 if (value.nodeType || (value instanceof Kimbo)) {
939 return this.each(function (el) {
940
941 // Be sure we can append/prepend to the element
942 if (el.nodeType === 1 || el.nodeType === 11) {
943 _.kimbo(value).each(function (_el) {
944 el.insertBefore(_el, isPrepend ? el.firstChild : null);
945 });
946 }
947 });
948 }
949 };
950 });
951
952 Kimbo.fn.extend({
953 /*\
954 * $(…).empty
955 [ method ]
956 * Remove all child nodes from the DOM of the elements in the collection.
957 = (object) Original matched collection.
958 > Usage
959 | <div class="container">
960 | <p class="lorem">Lorem</p>
961 | <p class="lorem">Lorem</p>
962 | </div>
963 * Empty .container
964 | $('.container').empty();
965 * All elements inside ".container" are removed from the DOM
966 | <div class="container"></div>
967 \*/
968 empty: function () {
969 return this.each(function (el) {
970 while (el.hasChildNodes()) {
971 el.removeChild(el.childNodes[0]);
972 }
973 });
974 },
975
976 /*\
977 * $(…).remove
978 [ method ]
979 * Remove all matched elements from the DOM.
980 * Similar to @$(…).empty but .remove() removes the element itself
981 = (object) Original matched collection.
982 > Usage
983 | <div class="container">
984 | <p class="lorem">Lorem</p>
985 | <p>Lorem</p>
986 | </div>
987 * Remove one element
988 | $('.lorem').remove();
989 * The result element is:
990 | <div class="container">
991 | <p>Lorem</p>
992 | </div>
993 \*/
994 remove: function () {
995 return this.each(function (el) {
996 if (el.parentNode) {
997 el.parentNode.removeChild(el);
998 }
999 });
1000 },
1001
1002 // Todo: extend to accept objects and functions to set values
1003 /*\
1004 * $(…).attr
1005 [ method ]
1006 * Get an attribute value from one element or set attributes to all matched elements.
1007 > Parameters
1008 - name (string) Name of the attribute.
1009 - value (string) #optional Value for the attribute.
1010 = (string) Attribute value, only if name was passed.
1011 = (object) Original matched collection when setting a value.
1012 > Usage
1013 | <a href="http://kimbojs.com">Go to Kimbojs.com</a>
1014 * Get href attribute
1015 | $('a').attr('href'); // Http://kimbojs.com
1016 * Set a new attribute
1017 | $('a').attr('title', 'Go to Kimbojs.com');
1018 * Now element has a title attribute
1019 | <a href="http://kimbojs.com" title="Go to Kimbojs.com">Go to Kimbojs.com</a>
1020 \*/
1021 attr: function (name, value) {
1022 if (!this.length) {
1023 return this;
1024 }
1025
1026 if (Kimbo.isString(name) && value === undefined) {
1027 return this[0].getAttribute(name);
1028 } else {
1029 return this.each(function (el) {
1030 el.setAttribute(name, value);
1031 });
1032 }
1033 },
1034
1035 /*\
1036 * $(…).removeAttr
1037 [ method ]
1038 * Removes an attribute from all matched elements.
1039 > Parameters
1040 - name (string) Name of the attribute to remove.
1041 = (object) Original matched collection.
1042 > Usage
1043 | <a href="http://kimbojs.com" title="Go to Kimbojs.com">Go to Kimbojs.com</a>
1044 * Remove the title attribute
1045 | $('a').removeAttr('title');
1046 * Now the element has no title
1047 | <a href="http://kimbojs.com">Go to Kimbojs.com</a>
1048 \*/
1049 removeAttr: function (name) {
1050 return this.each(function (el) {
1051 el.removeAttribute(name);
1052 });
1053 },
1054
1055 /*\
1056 * $(…).toggleClass
1057 [ method ]
1058 * Removes a class to all matched elements.
1059 > Parameters
1060 - name (string) Name of the class to toggle.
1061 - state (boolean) #optional If state is true the class will be added, if false, removed.
1062 = (object) Original matched collection.
1063 > Usage
1064 | <p class="foo">Lorem ipsum.</p>
1065 | <script>
1066 | $('p').toggleClass('foo');
1067 | </script>
1068 * The `p` element has the class foo soo it will be removed
1069 | <p>Lorem ipsum.</p>
1070 \*/
1071 toggleClass: function (name, state) {
1072 var classNames;
1073
1074 if (this.length && name && Kimbo.isString(name)) {
1075
1076 classNames = name.split(SPACE_RE);
1077
1078 this.each(function (el) {
1079 Kimbo.forEach(classNames, function (name) {
1080
1081 // Use custom toggle (anyway it uses classList.add/remove)
1082 state = Kimbo.isBoolean(state) ? state : !_hasClass(el, name);
1083 _.kimbo(el)[state ? 'addClass' : 'removeClass'](name);
1084 });
1085 });
1086 }
1087
1088 return this;
1089 },
1090
1091 /*\
1092 * $(…).hasClass
1093 [ method ]
1094 * Determine whether any matched elements has the given class.
1095 > Parameters
1096 - name (string) Name of the class to search for.
1097 = (object) Original matched collection.
1098 > Usage
1099 | <p class="asd foo qwe">Lorem ipsum.</p>
1100 * Check if the element has the class 'foo'
1101 | $('p').hasClass('foo'); // True
1102 * You could also check if it has multiple classes
1103 | $('p').hasClass('qwe asd'); // True
1104 \*/
1105 hasClass: function (name) {
1106 var has = false;
1107 var classNames;
1108
1109 if (this.length && name && Kimbo.isString(name)) {
1110
1111 classNames = name.trim().split(SPACE_RE);
1112
1113 this.each(function (el) {
1114
1115 // Classlist.contains only accepts one class parameter
1116 Kimbo.forEach(classNames, function (name) {
1117 has = _hasClass(el, name);
1118
1119 // When only one is missing break the loop
1120 if (!has) {
1121 return false;
1122 }
1123 });
1124 });
1125 }
1126
1127 return has;
1128 },
1129
1130 /*\
1131 * $(…).clone
1132 [ method ]
1133 * Clones a DOM node.
1134 > Parameters
1135 = (object) Original matched collection.
1136 > Usage
1137 | <p class="asd foo qwe">Lorem ipsum.</p>
1138 | var p1 = $('p'); // Grab the p element
1139 | var p2 = p1.clone(); // Clone p1 into p2
1140 | console.log(p2 === p1); // False
1141 \*/
1142 clone: function () {
1143 return this.each(function (el) {
1144 return el.cloneNode(true);
1145 });
1146 }
1147 });
1148
1149 // Generate get/set .width() and .height() methods
1150 /*\
1151 * $(…).width
1152 [ method ]
1153 * Get the current width of the first element or set the width of all matched elements.
1154 > Parameters
1155 - value (number|string) #optional An integer indicating the width of the element or a string width a unit of measure.
1156 = (number) the actual width of the element if no parameter passed.
1157 = (object) Kimbo object.
1158 > Usage
1159 | <style>
1160 | div { width: 100px; }
1161 | </style>
1162 | <div>Actual width is 100px</div>
1163 * Get the width:
1164 | $('div').width(); // 100
1165 * Change the width:
1166 | $('div').width(200); // Now its width is 200
1167 * Or passing a specific unit:
1168 | $('div').width('50%'); // Now its width is 50%
1169 \*/
1170
1171 /*\
1172 * $(…).height
1173 [ method ]
1174 * Get the current height of the first element or set the height of all matched elements.
1175 > Parameters
1176 - value (number|string) #optional An integer indicating the height of the element or a string height a unit of measure.
1177 = (number) the actual height of the element if no parameter passed.
1178 = (object) Kimbo object.
1179 > Usage
1180 | <style>
1181 | div { height: 100px; }
1182 | </style>
1183 | <div>Actual height is 100px</div>
1184 * Get the height:
1185 | $('div').height(); // 100
1186 * Change the height:
1187 | $('div').height(200); // Now its height is 200
1188 * Or passing a specific unit:
1189 | $('div').height('50%'); // Now its height is 50%
1190 \*/
1191 Kimbo.forEach(['width', 'height'], function (dimension) {
1192 Kimbo.fn[dimension] = function (value) {
1193 if (!value) {
1194 return parseInt(this.css(dimension), 10);
1195 }
1196
1197 return this.css(dimension, value);
1198 };
1199 });
1200});
1201
1202
1203Kimbo.define('traversing', function (_) {
1204
1205 'use strict';
1206
1207 var query = Kimbo.require('query');
1208
1209 var _filter = Array.prototype.filter;
1210
1211 var IS_UNIQUE = {
1212 children: true,
1213 contents: true,
1214 next: true,
1215 prev: true
1216 };
1217
1218 // Use native matchesSelector
1219 var _matchesSelector = _.document.documentElement.webkitMatchesSelector ||
1220 _.document.documentElement.mozMatchesSelector ||
1221 _.document.documentElement.oMatchesSelector ||
1222 _.document.documentElement.matchesSelector;
1223
1224 var _matches = function (elem, selector) {
1225 return (!elem || elem.nodeType !== 1) ? false : _matchesSelector.call(elem, selector);
1226 };
1227
1228 // Remove duplicates from an array
1229 var _unique = function (array) {
1230 return array.filter(function (item, index) {
1231 return array.indexOf(item) === index;
1232 });
1233 };
1234
1235 var _sibling = function (node, elem) {
1236 var result = [];
1237 for (; node; node = node.nextSibling) {
1238 if (node.nodeType === 1 && node !== elem ) {
1239 result.push(node);
1240 }
1241 }
1242 return result;
1243 };
1244
1245 var _singleSibling = function (node, prop) {
1246 do {
1247 node = node[prop];
1248 } while (node && node.nodeType !== 1);
1249
1250 return node;
1251 };
1252
1253 Kimbo.fn.extend({
1254 /*\
1255 * $(…).filter
1256 [ method ]
1257 * Filter element collection by the passed argument.
1258 > Parameters
1259 - selector (string|object|function) The argument by which the collection will be filtered.
1260 = (object) Filtered elements collection.
1261 > Usage
1262 | <ul>
1263 | <li>One</li>
1264 | <li>Two</li>
1265 | <li>Three</li>
1266 | <li>Four</li>
1267 | </ul>
1268 * Get even items.
1269 | $('li').filter(':nth-child(even)').addClass('even');
1270 * Only even items were affected.
1271 | <ul>
1272 | <li>One</li>
1273 | <li class="even">Two</li>
1274 | <li>Three</li>
1275 | <li class="even">Four</li>
1276 | </ul>
1277 > Using a function(index, element)
1278 * You can also filter the collection using a function, receiving the current index and element in the collection.
1279 | $('li').filter(function (index, element) {
1280 | return index % 3 == 2;
1281 | }).addClass('red');
1282 * This will add a 'red' class to the third, sixth, ninth elements and so on...
1283 > Filter by DOM or Kimbo object
1284 * You can also filter by a DOM or Kimbo object.
1285 | $('li').filter(document.getElementById('id'));
1286 | // Or a Kimbo object
1287 | $('li').filter($('#id'));
1288 \*/
1289 filter: function (selector) {
1290 var result;
1291
1292 // Filter collection
1293 result = _filter.call(this, function (elem, i) {
1294 var ret;
1295
1296 if (Kimbo.isFunction(selector)) {
1297 ret = !!selector.call(elem, i, elem);
1298 } else if (Kimbo.isString(selector)) {
1299 ret = _matches(elem, selector);
1300 } else if (selector.nodeType) {
1301 ret = elem === selector;
1302 } else if (selector instanceof Kimbo) {
1303 ret = elem === selector[0];
1304 }
1305
1306 return ret;
1307 });
1308
1309 return _.kimbo(result);
1310 },
1311
1312 /*\
1313 * $(…).eq
1314 [ method ]
1315 * Reduce the matched elements collection to the one at the specified index.
1316 > Parameters
1317 - index (number) An integer indicating the position of the element. Use a negative number to go backwards in the collection.
1318 = (object) Kimbo object at specified index.
1319 > Usage
1320 | <ul>
1321 | <li>Item 1</li>
1322 | <li>Item 2</li>
1323 | <li>Item 3</li>
1324 | <li>Item 4</li>
1325 | </ul>
1326 * Get 3rd element, index always start at 0, so to get the 3rd we need to pass the number 2.
1327 | $('li').eq(2); // Item 3
1328 \*/
1329 eq: function (i) {
1330 return this.length && i === -1 ? this.slice(i) : this.slice(i, i + 1);
1331 },
1332
1333 /*\
1334 * $(…).first
1335 [ method ]
1336 * Reduce the matched elements collection to the first in the set.
1337 = (object) First Kimbo object of the collection
1338 > Usage
1339 | <ul>
1340 | <li>Item 1</li>
1341 | <li>Item 2</li>
1342 | <li>Item 3</li>
1343 | </ul>
1344 * Get only the first element
1345 | $('li').first(); // Item 1
1346 \*/
1347 first: function () {
1348 return this.eq(0);
1349 },
1350
1351 /*\
1352 * $(…).last
1353 [ method ]
1354 * Reduce the matched elements collection to the last in the set.
1355 = (object) Last Kimbo object of the collection
1356 > Usage
1357 | <ul>
1358 | <li>Item 1</li>
1359 | <li>Item 2</li>
1360 | <li>Item 3</li>
1361 | </ul>
1362 * Get only the last element
1363 | $('li').last(); // Item 3
1364 \*/
1365 last: function () {
1366 return this.eq(-1);
1367 },
1368
1369 /*\
1370 * $(…).slice
1371 [ method ]
1372 * Reduce the matched elements collection to a subset specified by a range of indices
1373 > Parameters
1374 - start (number) An integer indicating the position at which the elements begin to be selected. Use a negative number to go backwards in the collection.
1375 - end (number) #optional An integer indicating the position at which the elements stop being selected. Use a negative number to start at the end of the collection. If ommited, the range continues to the end.
1376 = (object) Reduced Kimbo object collection in the range specified
1377 > Usage
1378 | <ul>
1379 | <li>Item 1</li>
1380 | <li>Item 2</li>
1381 | <li>Item 3</li>
1382 | <li>Item 4</li>
1383 | <li>Item 5</li>
1384 | </ul>
1385 * This will add the class selected to Item 3, 4 and 5, as the index starts at 0
1386 | $('li').slice(2).addClass('selected');
1387 * This will select only Items 3 and 4
1388 | $('li').slice(2, 4).addClass('selected');
1389 > Negative index
1390 * Here only Item 4 will be selected since is the only between -2 (Item 3) and -1 (Item 5)
1391 | $('li').slice(-2, -1).addClass('selected');
1392 \*/
1393 slice: function () {
1394 return this.length && _.kimbo(_.slice.apply(this, arguments));
1395 },
1396
1397 /*\
1398 * $(…).each
1399 [ method ]
1400 * Iterate over a Kimbo objct, executing a function for each element.
1401 > Parameters
1402 - callback (function) A function to call for each element in the collection.
1403 = (object) Kimbo object
1404 > Usage
1405 * Here we have an unordered list:
1406 | <ul>
1407 | <li>Item 1</li>
1408 | <li>Item 2</li>
1409 | <li>Item 3</li>
1410 | </ul>
1411 * You can iterate over all the list items and execute a function
1412 | $('li').each(function (el, index, collection) {
1413 | console.log('index of ' + $(this).text() + ' is: ' + index);
1414 | });
1415 * This will log the following message
1416 *
1417 * index of Item 1 is: 0
1418 *
1419 * index of Item 2 is: 1
1420 *
1421 * index of Item 3 is: 2
1422 \*/
1423 each: function (callback) {
1424 return Kimbo.forEach(this, callback);
1425 },
1426
1427 /*\
1428 * $(…).map
1429 [ method ]
1430 * Execute a function for each element in the collection, producing a new Kimbo set with the returned values
1431 > Parameters
1432 - callback (function) A function to call for each element in the collection.
1433 = (object) Kimbo object
1434 > Usage
1435 * Here we have an unordered list:
1436 | <ul>
1437 | <li id="item1">Item 1</li>
1438 | <li id="item2">Item 2</li>
1439 | <li id="item3">Item 3</li>
1440 | </ul>
1441 * You can call a function for each element to create a new Kimbo object
1442 | $('li').map(function (el, index) {
1443 | return this.id;
1444 | }).get().join();
1445 * This will produce a list of the item ids.
1446 | "item1,item2,item3"
1447 \*/
1448 map: function (callback) {
1449 return _.kimbo(Kimbo.map(this, function (elem, i) {
1450 return callback.call(elem, elem, i);
1451 }));
1452 },
1453
1454 /*\
1455 * $(…).find
1456 [ method ]
1457 * Find descendant elements for each element in the current collection.
1458 > Parameters
1459 - selector (string) A string selector to match elements.
1460 = (object) Kimbo object
1461 > Usage
1462 * Here we have some HTML
1463 | <div id="container">
1464 | <p>Demo</p>
1465 | <div class="article">
1466 | <p>This is an article</p>
1467 | <p>with some paragraphs</p>
1468 | </div>
1469 | </div>
1470 * You can find all paragraph elements inside the article:
1471 | $('.article').find('p');
1472 \*/
1473 find: function (selector) {
1474 var i, l, length, n, r, result, elems;
1475
1476 // Make new empty kimbo collection
1477 result = _.kimbo();
1478
1479 // Could use Kimbo.forEach, but this is a bit faster..
1480 for (i = 0, l = this.length; i < l; i++) {
1481 length = result.length;
1482
1483 // Get elements
1484 elems = query.find(this[i], selector);
1485
1486 // Push them to current kimbo collection
1487 _.push.apply(result, elems);
1488
1489 if (i) {
1490
1491 // Make results unique
1492 for (n = length; n < result.length; n++) {
1493 for (r = 0; r < length; r++) {
1494 if (result[r] === result[n]) {
1495 result.splice(n--, 1);
1496 break;
1497 }
1498 }
1499 }
1500 }
1501 }
1502
1503 return result;
1504 },
1505
1506 /*\
1507 * $(…).closest
1508 [ method ]
1509 * Get a Kimbo collection that matches the closest selector
1510 > Parameters
1511 - selector (string) A string selector to match elements.
1512 - context (string) #optional A DOM element within which matching elements may be found.
1513 = (object) Kimbo object
1514 > Usage
1515 * Here we have a nested unordered lists:
1516 | <ul>
1517 | <li>
1518 | Item 1
1519 | <ul class="ul-level-2">
1520 | <li class="item-1-1">Item 1.1</li>
1521 | <li class="item-1-2">Item 1.2</li>
1522 | </ul>
1523 | </li>
1524 | <li>Item 2</li>
1525 | </ul>
1526 * You can find the containing ul of the items in the nested ul:
1527 | $('.item-1-1').closest('ul');
1528 * This will return `ul.level-2` element
1529 \*/
1530 closest: function (selector, context) {
1531 var l = this.length;
1532 var result = [];
1533 var closest = function (node) {
1534
1535 // Check selector match and grab the element
1536 while (node && !_matches(node, selector)) {
1537 node = node !== context && node !== _.document && node.parentNode;
1538 }
1539 return node;
1540 };
1541
1542 if (!l) {
1543 return this;
1544
1545 // Get closest only for one element
1546 } else if (l === 1) {
1547 result = closest(this[0]);
1548
1549 // Get closest from all elements in the set
1550 } else {
1551 Kimbo.forEach(this, function (node) {
1552 node = closest(node);
1553 if (node) {
1554 result.push(node);
1555 }
1556 });
1557
1558 // Only unique results
1559 result = result.length > 1 ? _unique(result) : result;
1560 }
1561
1562 return _.kimbo(result);
1563 },
1564
1565 /*\
1566 * $(…).contains
1567 [ method ]
1568 * Determine whether an element is contained by the current matched element.
1569 > Parameters
1570 - element (string|object) Selector of the element or the actual DOM or Kimbo object.
1571 = (boolean) true if it is contained, false if not.
1572 > Usage
1573 | <div id="container">
1574 | <p id="inside">Inside paragraph</p>
1575 | </div>
1576 | <p id="outside">Outside paragraph</p>
1577 * The paragraph with id "inside" is actually contained by "#container"
1578 | $('#container').contains('#inside'); // True
1579 * The paragraph ourside is not contained
1580 | var outside_p = $('#outside');
1581 | $('#container').contains(outside_p); // False
1582 \*/
1583 contains: function (element) {
1584 element = (element instanceof Kimbo) ? element[0] :
1585 (Kimbo.isString(element) ? this.find(element)[0] : element);
1586
1587 return query.contains(this[0], element);
1588 },
1589
1590 /*\
1591 * $(…).add
1592 [ method ]
1593 * Add elements to the current Kimbo collection.
1594 > Parameters
1595 - selector (string|object) Selector of the element or the actual DOM or Kimbo object.
1596 - context (string|object) #optional Selector of the context element or the actual DOM or Kimbo object.
1597 = (object) The merged Kimbo collection.
1598 > Usage
1599 | <ul id="menu1">
1600 | <li>Apple</li>
1601 | <li>Orange</li>
1602 | </ul>
1603 |
1604 | <ul id="menu2">
1605 | <li>Lemon</li>
1606 | <li>Banana</li>
1607 | </ul>
1608 * Get the items from the #menu1 and add the ones from #menu2, all the following ways will produce the same collection
1609 | $('#menu1 li').add('#menu2 li');
1610 * or
1611 | $('#menu1 li').add('li', '#menu2');
1612 * or
1613 | $('#menu1 li').add($('#menu2 li'));
1614 \*/
1615 add: function (selector, context) {
1616 var set = Kimbo.isString(selector) ? _.kimbo(selector, context) :
1617 Kimbo.makeArray(selector && selector.nodeType ? [selector] : selector);
1618
1619 var all = Kimbo.merge(this, set);
1620
1621 return _.kimbo(all);
1622 },
1623
1624 /*\
1625 * $(…).is
1626 [ method ]
1627 * Check the current elements collection against a selector, object or function.
1628 - selector (string|object|function) The argument by which the collection will be matched against.
1629 = (boolean)
1630 > Usage
1631 | <ul>
1632 | <li>Click the <em>Apple</em></li>
1633 | <li><span>Click the Orange</span></li>
1634 | <li>Or the Banana</li>
1635 | </ul>
1636 * Test if the current clicked element is an `<li>` element.
1637 | $('ul').click(function (event) {
1638 | var $target = $(event.target);
1639 | if ($target.is('li')) {
1640 | $target.css('background-color', 'red');
1641 | }
1642 | });
1643 \*/
1644 is: function (selector) {
1645 return !!(this.length && this.filter(selector).length);
1646 }
1647 });
1648
1649 Kimbo.forEach({
1650 /*\
1651 * $(…).parent
1652 [ method ]
1653 * Get the parent of each element matched in the current collection.
1654 * If a selector is specified, it will return the parent element only if it matches that selector.
1655 - selector (string) #optional A string containing a selector expression to match elements against
1656 = (object) Kimbo object
1657 > Usage
1658 * Suppose a page with this HTML:
1659 | <ul>
1660 | <li class="item-a">Item 1</li>
1661 | <li class="item-b">Item 2</li>
1662 | </ul>
1663 * Get the parent element of `.item-a`
1664 | $('.item-a').parent(); // Ul
1665 \*/
1666 parent: function (elem) {
1667 var parent = elem.parentNode;
1668
1669 return parent && parent.nodeType !== 11 ? parent : null;
1670 },
1671
1672 /*\
1673 * $(…).next
1674 [ method ]
1675 * Get the immedeately following sibling of each element in the current collection.
1676 * If a selector is specified, it will return the element only if it matches that selector.
1677 - selector (string) #optional A string containing a selector expression to match elements against
1678 = (object) Kimbo object
1679 > Usage
1680 * Suppose a page with this HTML:
1681 | <ul>
1682 | <li class="item-a">Item 1</li>
1683 | <li class="item-b">Item 2</li>
1684 | </ul>
1685 * Get the parent element of `.item-a`
1686 | $('.item-a').next(); // .item-b
1687 \*/
1688 next: function (elem) {
1689 return _singleSibling(elem, 'nextSibling');
1690 },
1691
1692 /*\
1693 * $(…).prev
1694 [ method ]
1695 * Get the immedeately previous sibling of each element in the current collection.
1696 * If a selector is specified, it will return the element only if it matches that selector.
1697 - selector (string) #optional A string containing a selector expression to match elements against
1698 = (object) Kimbo object
1699 > Usage
1700 * Suppose a page with this HTML:
1701 | <ul>
1702 | <li class="item-a">Item 1</li>
1703 | <li class="item-b">Item 2</li>
1704 | </ul>
1705 * Get the parent element of `.item-a`
1706 | $('.item-b').prev(); // .item-a
1707 \*/
1708 prev: function (elem) {
1709 return _singleSibling(elem, 'previousSibling');
1710 },
1711
1712 /*\
1713 * $(…).sibling
1714 [ method ]
1715 * Get the immedeately previous sibling of each element in the current collection.
1716 * If a selector is specified, it will return the element only if it matches that selector.
1717 - selector (string) #optional A string containing a selector expression to match elements against
1718 = (object) Kimbo object
1719 > Usage
1720 * Suppose a page with this HTML:
1721 | <ul>
1722 | <li class="item-a">Item 1</li>
1723 | <li class="item-b">Item 2</li>
1724 | </ul>
1725 * Get the parent element of `.item-a`
1726 | $('.item-b').prev(); // .item-a
1727 \*/
1728 siblings: function (elem) {
1729 return _sibling((elem.parentNode || {}).firstChild, elem);
1730 },
1731
1732 /*\
1733 * $(…).children
1734 [ method ]
1735 * Get the children of all matched elements, optionally filtered by a selector.
1736 - selector (string) #optional A string selector to match elements against.
1737 = (object) Kimbo object
1738 > Usage
1739 * Suppose a page with the following HTML:
1740 | <div class="demo">
1741 | <p>This</p>
1742 | <p>is</p>
1743 | <p>a</p>
1744 | <p>demo.</p>
1745 | </div>
1746 * Get all children of `.demo`:
1747 | $('.demo').children(); // Al <p> tags inside .demo div
1748 * Another example passing an specific selector:
1749 | <form>
1750 | <input type="text" name="name" />
1751 | <input type="text" name="last" />
1752 | <input type="submit" value="Send" />
1753 | </form>
1754 * Get only the children that are text type elements:
1755 | $('form').children('input[type="text"]'); // Only name and last inputs
1756 \*/
1757 children: function (elem) {
1758 return _sibling(elem.firstChild);
1759 },
1760
1761 /*\
1762 * $(…).contents
1763 [ method ]
1764 * Get the HTML content of an iframe
1765 = (object) Kimbo object
1766 > Usage
1767 * Suppose an iframe loading an external page:
1768 | <iframe src="http://api.kimbojs.com"></iframe>
1769 * Find the body element of the contents of that iframe:
1770 | $('iframe').contents().find('body');
1771 \*/
1772 contents: function (elem) {
1773 return elem.nodeName.toLowerCase() === 'iframe' ? elem.contentDocument || elem.contentWindow[_.document] : Kimbo.makeArray(elem.childNodes);
1774 }
1775 }, function (name, fn) {
1776 Kimbo.fn[name] = function (selector) {
1777 var ret;
1778
1779 if (!this.length) {
1780 return this;
1781 }
1782
1783 ret = Kimbo.map(this, fn);
1784
1785 // Clean collection
1786 ret = this.length > 1 && !IS_UNIQUE[name] ? _unique(ret) : ret;
1787
1788 if (Kimbo.isString(selector)) {
1789 ret = _.kimbo(ret).filter(selector);
1790 }
1791
1792 return _.kimbo(ret);
1793 };
1794 });
1795});
1796
1797
1798Kimbo.define('utilities', function (_) {
1799
1800 'use strict';
1801
1802 // Mobile userAgent escaped regexes
1803 var ANDROID_RE = '(Android)\\s+([\\d.]+)';
1804 var BLACKBERRY_RE = '(BlackBerry|BB10|Playbook).*Version\/([\\d.]+)';
1805 var FIREFOXOS_RE = '(Mozilla).*Mobile[^\/]*\/([\\d.]*)';
1806 var IPAD_RE = '(iPad).*OS\\s([\\d_]+)';
1807 var IPHONE_RE = '(iPhone\\sOS)\\s([\\d_]+)';
1808 var WEBOS = '(web|hpw)OS[\\s\/]([\\d.]+)';
1809
1810 // Full regexp to test the userAgent
1811 var MOBILE_OS_RE = new RegExp(
1812 ANDROID_RE + '|' +
1813 BLACKBERRY_RE + '|' +
1814 FIREFOXOS_RE + '|' +
1815 IPHONE_RE + '|' +
1816 IPAD_RE + '|' +
1817 WEBOS
1818 );
1819
1820 var isMobile = null;
1821
1822 var objectTypes = {};
1823
1824 // Map object types
1825 Kimbo.forEach([ 'Array', 'Boolean', 'Date', 'Error', 'Function',
1826 'Number', 'Object', 'RegExp', 'String'
1827 ], function (type) {
1828 objectTypes['[object ' + type + ']'] = type.toLowerCase();
1829 }
1830 );
1831
1832 Kimbo.extend({
1833 /*\
1834 * $.typeOf
1835 [ method ]
1836 * Determine the internal JavaScript [[Class]] of an object.
1837 > Parameters
1838 - obj (object) Object to get its [[Class]] type.
1839 = (string) Type of the object.
1840 > Usage
1841 | $.typeOf('i am a string'); // 'string'
1842 | $.typeOf(/(\.regexp?)/); // 'regexp'
1843 | $.typeOf(null); // 'null'
1844 | $.typeOf(undefined); // 'undefined'
1845 | $.typeOf(window.notDefined); // 'undefined'
1846 | $.typeOf(function () {}); // 'function'
1847 | $.typeOf({key: 'value'}); // 'object'
1848 | $.typeOf(true); // 'boolean'
1849 | $.typeOf([]); // 'array'
1850 | $.typeOf(new Date()); // 'date'
1851 | $.typeOf(3); // 'number'
1852 \*/
1853 typeOf: function (obj) {
1854 var type;
1855
1856 if (obj === null || obj === undefined) {
1857 type = String(obj);
1858
1859 } else {
1860 type = (objectTypes[Object.prototype.toString.call(obj)] || 'object');
1861 }
1862
1863 return type;
1864 },
1865
1866 /*\
1867 * $.isArray
1868 [ method ]
1869 * Determine if the parameter passed is an array object.
1870 > Parameters
1871 - obj (object) Object to test if its an array.
1872 = (boolean) According wether or not it is an array object.
1873 > Usage
1874 | $.isArray([]); // True
1875 | $.isArray({}); // False
1876 | $.isArray('test'); // False
1877 \*/
1878 isArray: Array.isArray,
1879
1880 /*\
1881 * $.isNumeric
1882 [ method ]
1883 * Determine if the parameter passed is an number.
1884 > Parameters
1885 - obj (object) Object to test if its a number.
1886 = (boolean) According wether or not it is a number.
1887 > Usage
1888 | $.isNumeric(3); // True
1889 | $.isNumeric('3'); // False
1890 \*/
1891 isNumeric: function (obj) {
1892 return !isNaN(parseFloat(obj)) && isFinite(obj);
1893 },
1894
1895 /*\
1896 * $.isWindow
1897 [ method ]
1898 * Determine if the parameter passed is the window object.
1899 > Parameters
1900 - obj (object) Object to test if its the window object.
1901 = (boolean) According wether or not it is the window object.
1902 > Usage
1903 | $.isWindow(window); // True
1904 | $.isWindow({ window: window }); // False
1905 \*/
1906 isWindow: function (obj) {
1907 return obj && obj === obj.window;
1908 },
1909
1910 /*\
1911 * $.isEmptyObject
1912 [ method ]
1913 * Determine if the parameter passed is an empty object.
1914 > Parameters
1915 - obj (object) Object to test if its an empty object.
1916 = (boolean) According wether or not it is an empty object.
1917 > Usage
1918 | $.isEmptyObject({}); // True
1919 | $.isEmptyObject([]); // True
1920 | $.isEmptyObject([1, 2]); // False
1921 \*/
1922 isEmptyObject: function (obj) {
1923 var key;
1924
1925 for (key in obj) {
1926 if (obj.hasOwnProperty(key)) {
1927 return false;
1928 }
1929 }
1930
1931 return true;
1932 },
1933
1934 /*\
1935 * $.isMobile
1936 [ method ]
1937 * Determine if the current platform is a mobile device, (otherwise is a desktop browser).
1938 > Parameters
1939 = (boolean) According wether or not is a mobile device.
1940 > Usage
1941 | $.isMobile(); // False
1942 \*/
1943 isMobile: function () {
1944
1945 // Check only once for the current browser
1946 if (isMobile === null) {
1947 isMobile = MOBILE_OS_RE.test(navigator.userAgent);
1948 }
1949
1950 return isMobile;
1951 },
1952
1953 /*\
1954 * $.parseJSON
1955 [ method ]
1956 * Parses a well-formed JSON string and returns the resulting JavaScript object.
1957 > Parameters
1958 - data (string) The JSON string to parse.
1959 = (object) A JavaScript object.
1960 > Usage
1961 | var obj = $.parseJSON('{"name":"Denis"}');
1962 | console.log(obj.name === 'Denis'); // True
1963 \*/
1964 parseJSON: function (data) {
1965
1966 // Use native JSON parser
1967 if (data && Kimbo.isString(data)) {
1968 return window.JSON.parse(data);
1969 }
1970 },
1971
1972 /*\
1973 * $.parseXML
1974 [ method ]
1975 * Parses a string into an XML document.
1976 > Parameters
1977 - data (string) The JSON string to parse.
1978 = (object) A JavaScript object.
1979 > Usage
1980 | var xml = "<rss version='2.0'><channel><title>RSS Title</title></channel></rss>"
1981 | var xmlDoc = $.parseXML(xml);
1982 | $(xmlDoc).find('title'); // 'RSS Title'
1983 \*/
1984 parseXML: function (data) {
1985
1986 // Use native XML (DOM) parser
1987 var domparser;
1988 var xml;
1989
1990 if (data && Kimbo.isString(data)) {
1991 domparser = new window.DOMParser();
1992 xml = domparser.parseFromString(data, 'text/xml');
1993
1994 if (xml.getElementsByTagName('parsererror').length) {
1995 throw new Error('Invalid XML: ' + data);
1996 }
1997
1998 return xml;
1999 }
2000 },
2001
2002 /*\
2003 * $.map
2004 [ method ]
2005 * Translate all items in an array or object to new array of items.
2006 > Parameters
2007 - obj (array|object) The Array or Object to translate.
2008 = (array) A new array.
2009 > Usage
2010 | var arr = ['a', 'b', 'c'];
2011 | arr = $.map(arr, function (element, index) {
2012 | return element.toUpperCase() + index;
2013 | });
2014 | console.log(arr); // ['A0', 'B1', 'C2']
2015 * Or wit an object
2016 | var obj = {a: 'a', b: 'b', c: 'c'};
2017 | obj = $.map(arr, function (key, value) {
2018 | return key + ': ' + value.toUpperCase();
2019 | });
2020 | console.log(obj); // ['a: A', 'b: B', 'c: C']
2021 \*/
2022 map: function (obj, callback) {
2023 var values = [];
2024
2025 if (obj) {
2026 Kimbo.forEach(obj, function (key, val) {
2027 var value = callback(key, val);
2028
2029 if (value !== null && value !== undefined) {
2030 values.push(value);
2031 }
2032 });
2033 }
2034
2035 // Flatten any nested arrays
2036 return values.concat.apply([], values);
2037 },
2038
2039 /*\
2040 * $.makeArray
2041 [ method ]
2042 * Create an Array from the given object
2043 > Parameters
2044 - obj (array|object) The Array or Object to make an array from.
2045 = (array) A new array.
2046 > Usage
2047 | var lis = $('li'); // Kimbo object
2048 | var arr = $.makeArray(lis);
2049 |
2050 | console.log($.isArray(lis)); // False
2051 | console.log($.isArray(arr)); // True
2052 \*/
2053 makeArray: function (obj, results) {
2054 results = results || [];
2055
2056 if (obj) {
2057 if (Kimbo.isArray(obj) || (obj instanceof Kimbo) || obj instanceof window.NodeList) {
2058 results = Kimbo.merge(results, obj);
2059 } else {
2060 _.push.call(results, obj);
2061 }
2062 }
2063
2064 return results;
2065 },
2066
2067 /*\
2068 * $.merge
2069 [ method ]
2070 * Merge the contents of two arrays into the first array passed.
2071 > Parameters
2072 - first (array) The first array to merge the contents of the second.
2073 - second (array|string) The second array to merge into the first.
2074 = (array) The first array merged with the second.
2075 > Usage
2076 | $.merge(['a', 'b'], ['c', 'd']);
2077 * Result:
2078 | ['a', 'b', 'c', 'd']
2079 \*/
2080 merge: function (first, second) {
2081
2082 // Concat is very fast, use it if we can
2083 if (Kimbo.isArray(first)) {
2084 first = first.concat(second);
2085
2086 // Kimbo object, do a consecutive push
2087 } else {
2088 _.push.apply(first, _.slice.call(second));
2089 }
2090
2091 return first;
2092 },
2093
2094 /*\
2095 * $.camelCase
2096 [ method ]
2097 * Camelize any dashed separated string
2098 > Parameters
2099 - str (string) A dashed separated string value to transform into camelCase.
2100 = (string) camelCase string
2101 > Usage
2102 | $.camelCase('background-color');
2103 * Result:
2104 | 'backgroundColor'
2105 \*/
2106 camelCase: function (str) {
2107 return str.replace(/-+(.)?/g, function (wholeMatch, character) {
2108 return character.toUpperCase();
2109 });
2110 },
2111
2112 /*\
2113 * $.isFunction
2114 [ method ]
2115 * Determine if the parameter passed is a Javascript function object.
2116 > Parameters
2117 - obj (object) Object to test if its a function.
2118 = (boolean) According wether or not it is a function.
2119 > Usage
2120 | var myFunction = function () {};
2121 | $.isFunction(myFunction); // True
2122 | var something = ['lala', 'jojo'];
2123 | $.isFunction(something); // False
2124 \*/
2125 isFunction: function (obj) {
2126 return Kimbo.typeOf(obj) === 'function';
2127 },
2128
2129 /*\
2130 * $.isObject
2131 [ method ]
2132 * Determine if the parameter passed is a Javascript plain object.
2133 > Parameters
2134 - obj (object) Object to test if its a plain object.
2135 = (boolean) According wether or not it is a plain object.
2136 > Usage
2137 | $.isObject({}); // True
2138 | $.isObject([]); // False
2139 | $.isObject('test'); // False
2140 \*/
2141 isObject: function (obj) {
2142 return Kimbo.typeOf(obj) === 'object';
2143 },
2144
2145 /*\
2146 * $.isString
2147 [ method ]
2148 * Determine if the parameter passed is a string.
2149 > Parameters
2150 - obj (object) Object to test if its a string.
2151 = (boolean) According wether or not it is a string.
2152 > Usage
2153 | $.isString('test'); // True
2154 | $.isString({ name: 'asd' }); // False
2155 \*/
2156 isString: function (obj) {
2157 return Kimbo.typeOf(obj) === 'string';
2158 },
2159
2160 /*\
2161 * $.isBoolean
2162 [ method ]
2163 * Determine if the parameter passed is boolean.
2164 > Parameters
2165 - obj (object) Object to test if its boolean..
2166 = (boolean) According wether or not it is boolean.
2167 > Usage
2168 | $.isBoolean(false); // True
2169 | $.isBoolean(3); // False
2170 \*/
2171 isBoolean: function (obj) {
2172 return Kimbo.typeOf(obj) === 'boolean';
2173 }
2174 });
2175
2176 // Save reference to Kimbo wrapped document as the default context
2177 _.rootContext = _.kimbo(_.rootContext);
2178});
2179
2180
2181Kimbo.define('events', function (_) {
2182
2183 'use strict';
2184
2185 var query = Kimbo.require('query');
2186
2187 var _guid = 1;
2188
2189 var MOUSE_EVENT_RE = /^(?:mouse|menu)|click/;
2190
2191 var KEY_EVENT_RE = /^key/;
2192
2193 var DEFAULT_EVENT_PROPS = [
2194 'altKey', 'bubbles', 'cancelable', 'ctrlKey', 'currentTarget', 'defaultPrevented', 'eventPhase',
2195 'metaKey', 'relatedTarget', 'shiftKey', 'target', 'timeStamp', 'type', 'view', 'which'
2196 ];
2197
2198 var MOUSE_EVENT_PROPS = [
2199 'button', 'buttons', 'clientX', 'clientY', 'fromElement',
2200 'offsetX', 'offsetY', 'screenX', 'screenY', 'toElement'
2201 ];
2202
2203 var KEY_EVENT_PROPS = ['char', 'charCode', 'key', 'keyCode'];
2204
2205 // Gestures fallback for not mobile environment
2206 var GESTURES_FALLBACK = Kimbo.isMobile() ? {} : {
2207 touchstart: 'mousedown',
2208 touchmove: 'mousemove',
2209 touchend: 'mouseup',
2210 touch: 'click',
2211 doubletap: 'dblclick',
2212 orientationchange: 'resize'
2213 };
2214
2215 var handlersHash = {};
2216 var fixEventProps = {};
2217 var specialEvents = {};
2218
2219 var _fixEvent = function (event) {
2220 var originalEvent, eventProps, props;
2221
2222 // Already fixed
2223 if (event[Kimbo.ref]) {
2224 return event;
2225 }
2226
2227 // Get event properties
2228 originalEvent = event;
2229 eventProps = fixEventProps[event.type] || [];
2230 props = DEFAULT_EVENT_PROPS.concat(eventProps);
2231
2232 // Create a new event writable custom event object
2233 event = new Kimbo.Event(originalEvent);
2234
2235 // Set event props to Kimbo.Event object
2236 Kimbo.forEach(props, function (prop) {
2237 event[prop] = originalEvent[prop];
2238 });
2239
2240 return event;
2241 };
2242
2243 // Return element id
2244 var _getElementId = function (element) {
2245 return element._guid || (element._guid = _guid++);
2246 };
2247
2248 // Get element handlers for the specified type
2249 var _getHandlers = function (elementId, type) {
2250 var events = ((handlersHash[elementId] || {}).events || {});
2251
2252 return (type ? events[type] : events) || [];
2253 };
2254
2255 // Quick is() to check if event target matches when events are delegated
2256 var _is = function (target, selector, element) {
2257 return (target.nodeName.toLowerCase() === selector && _.kimbo(target).closest(selector, element)[0]);
2258 };
2259
2260 var _returnFalse = function () {
2261 return false;
2262 };
2263
2264 var _returnTrue = function () {
2265 return true;
2266 };
2267
2268 // Register events to dom elements
2269 var _addEvent = function (element, type, callback, data, selector) {
2270
2271 // TODO: element should use Kimbo.ref and the handler the _guid
2272 var elementId = _getElementId(element);
2273 var elementHandlers = handlersHash[elementId];
2274 var origType = type;
2275 var events, handlers, handleObj, handler;
2276
2277 // Could be a special type like mouseenter/mouseleave
2278 type = specialEvents[type] ? specialEvents[type].origType : type;
2279
2280 // Create hash for this element if first init
2281 if (!elementHandlers) {
2282 handlersHash[elementId] = elementHandlers = {};
2283 }
2284
2285 // Create events object if first init
2286 events = elementHandlers.events;
2287 if (!events) {
2288 elementHandlers.events = events = {};
2289 }
2290
2291 // Create the handler for this element if first init
2292 handler = elementHandlers.handler;
2293 if (!handler) {
2294 elementHandlers.handler = handler = function () {
2295 return _dispatchEvent.apply(element, arguments);
2296 };
2297 }
2298
2299 // Create handler object
2300 handleObj = {
2301 type: type,
2302 origType: origType,
2303 data: data,
2304 callback: callback,
2305 selector: selector
2306 };
2307
2308 // Only add an event listener one time for each type of event
2309 handlers = events[type];
2310 if (!handlers) {
2311
2312 // Array of events for the current type
2313 handlers = events[type] = [];
2314 handlers.delegateCount = 0;
2315
2316 // Add event
2317 if (element.addEventListener) {
2318 element.addEventListener(type, handler, false);
2319 }
2320 }
2321
2322 // Add to handlers hash, delegates first
2323 if (selector) {
2324 handlers.splice(handlers.delegateCount++, 0, handleObj);
2325
2326 } else {
2327 handlers.push(handleObj);
2328 }
2329 };
2330
2331 // Unregister events from dom elements
2332 var _removeEvent = function (element, type, callback, selector) {
2333 var elementId = _getElementId(element);
2334 var handleObj, handlers, name, i;
2335
2336 if (!elementId) {
2337 return;
2338 }
2339
2340 handlers = _getHandlers(elementId, type);
2341
2342 // Return if no handlers for the current event type
2343 if (type && !handlers.length) {
2344 return;
2345 }
2346
2347 // Remove all handlers if no type provided
2348 if (!type) {
2349 for (name in handlers) {
2350 if (handlers.hasOwnProperty(name)) {
2351 _removeEvent(element, name, callback, selector);
2352 }
2353 }
2354 }
2355
2356 // Remove handlers that match
2357 for (i = 0; i < handlers.length; i++) {
2358 handleObj = handlers[i];
2359 if ((!callback || callback === handleObj.callback) && (!selector || selector === handleObj.selector)) {
2360
2361 // Remove current handler from stack
2362 handlers.splice(i--, 1);
2363
2364 // Decrement delegate count
2365 if (handleObj.selector) {
2366 handlers.delegateCount--;
2367 }
2368 }
2369 }
2370
2371 // If no more events for the current type delete its hash
2372 if (!handlers.length) {
2373
2374 // Remove event handler
2375 element.removeEventListener(type, handlersHash[elementId].handler, false);
2376 delete handlersHash[elementId].events[type];
2377 }
2378
2379 // Remove kimbo reference if element have no more events
2380 // If (Kimbo.isEmptyObject(handlersHash[elementId].events)) {
2381 // delete handlersHash[elementId];
2382 // delete element._guid;
2383 // }
2384 };
2385
2386 // Triggers a provided event type
2387 var _triggerEvent = function (element, type, data) {
2388
2389 /* jshint validthis: true */
2390 var currentElement, lastElement, eventTree, elementId, event;
2391
2392 // Don't do events if element is text or comment node
2393 // Or if there is no event type at all or type is not a string
2394 if ((element && (element.nodeType === 3 || element.nodeType === 8)) || !type || !Kimbo.isString(type)) {
2395 return this;
2396 }
2397
2398 // Try triggering native focus and blur events
2399 if (type === 'focus' || type === 'blur') {
2400 try {
2401 return element[type]();
2402 } catch (e) {}
2403 }
2404
2405 // Create a new writable custom event object
2406 event = new Kimbo.Event(type);
2407
2408 // Triggered programatically
2409 event.isTrigger = true;
2410
2411 // Set the target
2412 if (!event.target) {
2413 event.target = element;
2414 }
2415
2416 // Include data if any
2417 data = data ? Kimbo.makeArray(data) : [];
2418 // Event goes first
2419 data.unshift(event);
2420
2421 // Generate a stack of [element, event] to be triggered
2422 eventTree = [[element, type]];
2423 if (!Kimbo.isWindow(element)) {
2424
2425 // Get all parent elements to bubble event later
2426 for (currentElement = element.parentNode; currentElement; currentElement = currentElement.parentNode) {
2427 eventTree.push([currentElement, type]);
2428 lastElement = currentElement;
2429 }
2430
2431 // Only add window object if we got to document (e.g., not plain obj or detached DOM)
2432 if (lastElement && lastElement === element.ownerDocument) {
2433 eventTree.push([window, type]);
2434 }
2435 }
2436
2437 // Fire handlers up to the document (or the last element)
2438 Kimbo.forEach(eventTree, function (branch) {
2439
2440 // Element
2441 currentElement = branch[0];
2442
2443 // Type
2444 event.type = branch[1];
2445
2446 // Get element id
2447 elementId = currentElement._guid;
2448
2449 // If the current element has events of the specified type, dispatch them
2450 if (elementId && _getHandlers(elementId, type)) {
2451 handlersHash[elementId].handler.apply(currentElement, data);
2452 }
2453 });
2454 };
2455
2456 // Own defined dispatchEvent()
2457 var _dispatchEvent = function (event) {
2458 /* jshint -W040 */
2459
2460 // Use own event object
2461 event = _fixEvent(event);
2462
2463 var elementId = _getElementId(this);
2464 var handlers = _getHandlers(elementId, event.type);
2465 var delegateCount = handlers.delegateCount;
2466 var args = _.slice.call(arguments);
2467 var handlerQueue = [];
2468 var currentElement, ret, selMatch, matches, handleObj, selector, i;
2469
2470 // Set the native event to be the fixed event
2471 args[0] = event;
2472
2473 // Save the delegate target element
2474 event.delegateTarget = this;
2475
2476 // Get delegated handlers if any
2477 if (delegateCount) {
2478
2479 // Go up to the dom finding the elements that matches the current selector from delegated event
2480 for (currentElement = event.target; currentElement !== this; currentElement = currentElement.parentNode || this) {
2481
2482 // Don't do events on disabled elements
2483 if (currentElement.disabled !== true || event.type !== 'click') {
2484 selMatch = {};
2485 matches = [];
2486
2487 // Loop throgh delegated events
2488 for (i = 0; i < delegateCount; i++) {
2489
2490 // Get its handler
2491 handleObj = handlers[i];
2492
2493 // Get its selector
2494 selector = handleObj.selector;
2495
2496 if (!selMatch[selector]) {
2497 selMatch[selector] = _is(currentElement, selector, this);
2498 }
2499
2500 if (selMatch[selector]) {
2501 matches.push(handleObj);
2502 }
2503 }
2504
2505 if (matches.length) {
2506 handlerQueue.push({elem: currentElement, matches: matches});
2507 }
2508 }
2509 }
2510 }
2511
2512 // Add the remaining not delegated handlers
2513 if (handlers.length > delegateCount) {
2514 handlerQueue.push({elem: this, matches: handlers.slice(delegateCount)});
2515 }
2516
2517 // Fire callbacks queue
2518 Kimbo.forEach(handlerQueue, function (handler) {
2519
2520 // Only fire handler if event wasnt stopped
2521 if (!event.isPropagationStopped()) {
2522 event.currentTarget = handler.elem;
2523
2524 Kimbo.forEach(handler.matches, function (handleObj) {
2525
2526 // Only fire bubble if not stopped
2527 if (!event.isImmediatePropagationStopped()) {
2528 event.data = handleObj.data;
2529 event.handleObj = handleObj;
2530
2531 // Call original callback, check if its an special event first
2532 ret = ((specialEvents[handleObj.origType] || {}).handle || handleObj.callback).apply(handler.elem, args);
2533
2534 // If callback returns false, stop the event
2535 if (ret === false) {
2536 event.preventDefault();
2537 event.stopPropagation();
2538 }
2539 }
2540 });
2541 }
2542 });
2543
2544 /* jshint +W040 */
2545 };
2546
2547 Kimbo.Event = function (event) {
2548
2549 // Is event object
2550 if (event && event.type) {
2551 this.originalEvent = event;
2552 this.type = event.type;
2553
2554 // The event may have been prevented
2555 // Check dom level 3 new attribute and set proper value
2556 if (event.defaultPrevented) {
2557 this.isDefaultPrevented = _returnTrue;
2558 } else {
2559 this.isDefaultPrevented = _returnFalse;
2560 }
2561
2562 // Is event type
2563 } else {
2564 this.type = event;
2565 }
2566
2567 // Create a timestamp if doesn't have one
2568 this.timeStamp = (event && event.timeStamp) || Date.now();
2569
2570 // Made by kimbo, yeah
2571 this[Kimbo.ref] = true;
2572 };
2573
2574 // Dom-Level-3-Events compliant
2575 Kimbo.Event.prototype = {
2576 isDefaultPrevented: _returnFalse,
2577 isPropagationStopped: _returnFalse,
2578 isImmediatePropagationStopped: _returnFalse,
2579
2580 preventDefault: function () {
2581 this.isDefaultPrevented = _returnTrue;
2582
2583 // Originalevent is not present when trigger is called
2584 if (!this.isTrigger) {
2585 this.originalEvent.preventDefault();
2586 }
2587 },
2588
2589 stopPropagation: function () {
2590 this.isPropagationStopped = _returnTrue;
2591
2592 if (!this.isTrigger) {
2593 this.originalEvent.stopPropagation();
2594 }
2595 },
2596
2597 stopImmediatePropagation: function () {
2598 this.isImmediatePropagationStopped = _returnTrue;
2599
2600 if (!this.isTrigger) {
2601 this.originalEvent.stopImmediatePropagation();
2602 }
2603 }
2604 };
2605
2606 Kimbo.fn.extend({
2607 /*\
2608 * $(…).on
2609 [ method ]
2610 * Add an event handler to the selected elements.
2611 > Parameters
2612 - type (string) A string name for the event to register.
2613 - selector (string) #optional Delegate an event providing a string selector.
2614 - data (any) #optional Data to be passed to the handler in `event.data` when an event is triggered.
2615 - callback (function) A callback function to execute when the event is triggered.
2616 > Usage
2617 * Suppose a button element:
2618 | <button id='btn'>click me</button>
2619 * Register a click event handler
2620 | $('#btn').on('click', function (event) {
2621 | console.log('clicked!', event);
2622 | });
2623 * There are shorthands for all events for example:
2624 | $('#btn').click(function (event) {
2625 | console.log('clicked!', event);
2626 | });
2627 * Passing some data when registering the handler:
2628 | $('#btn').on('click', { name: 'denis' }, function (event) {
2629 | // Data passed is inside event.data
2630 | console.log('name:', event.data.name);
2631 | });
2632 * Here is a list for all the shorthand methods available:
2633 | 'blur', 'change', 'click', 'contextmenu', 'dblclick', 'error',
2634 | 'focus', 'keydown', 'keypress', 'keyup', 'load', 'mousedown', 'mouseenter', 'mouseleave',
2635 | 'mousemove', 'mouseout', 'mouseup', 'mouseover', 'resize', 'scroll', 'select', 'submit', 'unload'
2636 \*/
2637 on: function (type, selector, data, callback) {
2638 // Prepare the arguments
2639
2640 // (type, callback)
2641 if (!data && !callback) {
2642 callback = selector;
2643 data = selector = undefined;
2644
2645 // (type, selector, callback)
2646 } else if (!callback) {
2647 if (Kimbo.isString(selector)) {
2648 callback = data;
2649 data = undefined;
2650
2651 // (type, data, callback)
2652 } else {
2653 callback = data;
2654 data = selector;
2655 selector = undefined;
2656 }
2657 }
2658
2659 // Don't add events if no callback
2660 if (!callback) {
2661 return this;
2662 }
2663
2664 type = GESTURES_FALLBACK[type] || type;
2665
2666 // Add the event
2667 return this.each(function (el) {
2668 _addEvent(el, type, callback, data, selector);
2669 });
2670 },
2671
2672 /*\
2673 * $(…).off
2674 [ method ]
2675 * Remove an event handler to the selected elements for the specified type, or all of them if no type defined.
2676 > Parameters
2677 - type (string) #optional A string name for the event to remove, or All if none specified.
2678 - selector (string) #optional A string selector to undelegate an event from that element.
2679 - callback (function) #optional A specific callback function if there are multiple registered under the same event type
2680 > Usage
2681 * Suppose a button element:
2682 | <button id='btn'>click me</button>
2683 * Register a click event handler
2684 | $('#btn').on('click', function (event) {
2685 | console.log('clicked!', event);
2686 | });
2687 * Remove the handler
2688 | $('#btn').off('click');
2689 * Also you could specify the handler for example:
2690 | var firstFunction = function () { console.log('first fn'); };
2691 | var secondFunction = function () { console.log('second fn'); };
2692 | var btn = $('#btn');
2693 | btn.click(firstFunction);
2694 | btn.click(secondFunction);
2695 * If you want to remove the click event only for the second function, do this:
2696 | btn.off('click', secondFunction);
2697 * Or if you want to remove All handlers (click and any other attached):
2698 | btn.off();
2699 \*/
2700 off: function (type, selector, callback) {
2701
2702 // Prepare the arguments
2703
2704 // (type, callback)
2705 if (Kimbo.isFunction(selector)) {
2706 callback = selector;
2707 selector = undefined;
2708 }
2709
2710 // Remove the event
2711 return this.each(function (el) {
2712 _removeEvent(el, type, callback, selector);
2713 });
2714 },
2715
2716 /*\
2717 * $(…).trigger
2718 [ method ]
2719 * Execute all handlers attached to the matched elements for the fiven event type.
2720 > Parameters
2721 - type (string) #optional A string name for the event to remove, or All if none specified.
2722 - data (any) #optional Additional parameters to be passed to the handler in `event.data` when an event is triggered.
2723 > Usage
2724 * Suppose a button element:
2725 | <button id='btn'>click me</button>
2726 * Register a click event handler
2727 | $('#btn').on('click', function (event, data) {
2728 | console.log('name', data.name);
2729 | });
2730 * Trigger the event programatically passing some data:
2731 | $('#btn').trigger('click', { name: 'denis' });
2732 | // 'name denis'
2733 * Allow the handler to recieve multiple data:
2734 | $('#btn').on('click', function (event, name, last) {
2735 | console.log('name', name);
2736 | console.log('last', last);
2737 | });
2738 | $('#btn').trigger('click', ['denis', 'ciccale']);
2739 | // Name denis
2740 | // Last ciccale
2741 \*/
2742 trigger: function (type, data) {
2743 return this.each(function (el) {
2744 _triggerEvent(el, type, data);
2745 });
2746 },
2747
2748 /*\
2749 * $(…).hover
2750 [ method ]
2751 * A shortcut method to register moseenter and/or mouseleave event handlers to the matched elements.
2752 > Parameters
2753 - fnOver (function) A function to execute when the cursor enters the element.
2754 - fnOut (function) #optional A function to execute when the cursor leaves the element.
2755 > Usage
2756 * Suppose a div element:
2757 | <div id='box'></div>
2758 * Register hover event
2759 | var fnOver = function () { console.log('enter'); };
2760 | var fnOut = function () { console.log('leave'); };
2761 |
2762 | $('.box').hover(fnOver, fnOut);
2763 * When the cursor enters the `.box` element the console will log:
2764 | 'enter'
2765 * When the cursor leaves the `.box` element the console will log:
2766 | 'leave'
2767 \*/
2768 hover: function (fnOver, fnOut) {
2769 return this.mouseenter(fnOver).mouseleave(fnOut || fnOver);
2770 }
2771 });
2772
2773 // Shortcut methods for each event type
2774 Kimbo.forEach(['blur', 'change', 'click', 'contextmenu', 'dblclick', 'error',
2775 'focus', 'keydown', 'keypress', 'keyup', 'load', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove',
2776 'mouseout', 'mouseup', 'mouseover', 'resize', 'scroll', 'select', 'submit', 'unload'], function (type) {
2777
2778 Kimbo.fn[type] = function (data, callback) {
2779 return arguments.length > 0 ? this.on(type, null, data, callback) : this.trigger(type);
2780 };
2781
2782 // Set event props for the specific type
2783 fixEventProps[type] = KEY_EVENT_RE.test(type) ? KEY_EVENT_PROPS : MOUSE_EVENT_RE.test(type) ? MOUSE_EVENT_PROPS : null;
2784 });
2785
2786 // Fix mouseover and mouseout events to use mouseenter mouseleave
2787 Kimbo.forEach({
2788 mouseenter: 'mouseover',
2789 mouseleave: 'mouseout'
2790 }, function (orig, fix) {
2791 specialEvents[orig] = {
2792 origType: fix,
2793
2794 handle: function (event) {
2795 var target = this;
2796 var related = event.relatedTarget;
2797 var handleObj = event.handleObj;
2798 var ret;
2799
2800 if (!related || (related !== target && !query.contains(target, related))) {
2801 event.type = handleObj.origType;
2802 ret = handleObj.callback.apply(this, arguments);
2803 event.type = fix;
2804 }
2805
2806 return ret;
2807 }
2808 };
2809 });
2810
2811});
2812
2813
2814Kimbo.define('ajax', function (_) {
2815
2816 'use strict';
2817
2818 var JSONP_RE = /(\=)\?(&|$)|\?\?/i;
2819
2820 var MIME_TYPES = {
2821 html: 'text/html',
2822 json: 'application/json',
2823 script: 'text/javascript, application/javascript',
2824 text: 'text/plain',
2825 xml: 'application/xml, text/xml'
2826 };
2827
2828 var dataParse = {
2829 json: Kimbo.parseJSON,
2830 xml: Kimbo.parseXML
2831 };
2832
2833 var xhrCallbacks = {};
2834
2835 // Success and error callbacks
2836 Kimbo.forEach(['success', 'error'], function (type) {
2837 xhrCallbacks[type] = function (res, msg, xhr, settings) {
2838 settings = settings || xhr;
2839 if (Kimbo.isFunction(settings[type])) {
2840 settings[type].apply(settings.context, arguments);
2841 }
2842 };
2843 });
2844
2845 var _getResponse = function (response, type) {
2846 return (dataParse[type] ? dataParse[type](response) : response);
2847 };
2848
2849 var _handleResponse = function (xhr, settings) {
2850 var response, contentType;
2851
2852 // Set dataType if missing
2853 if (!settings.dataType) {
2854 contentType = xhr.getResponseHeader('Content-Type');
2855
2856 Kimbo.forEach(MIME_TYPES, function (name, type) {
2857 if (type.match(contentType)) {
2858 settings.dataType = name;
2859 return false;
2860 }
2861 });
2862
2863 // Fix settings headers
2864 _setHeaders(settings);
2865 }
2866
2867 try {
2868 response = _getResponse(xhr.responseText, settings.dataType);
2869 } catch (e) {
2870 response = false;
2871 xhrCallbacks.error('parseerror', e, xhr, settings);
2872 }
2873
2874 return response;
2875 };
2876
2877 var _setHeaders = function (settings) {
2878 if (!settings.crossDomain && !settings.headers['X-Requested-With']) {
2879 settings.headers['X-Requested-With'] = 'XMLHttpRequest';
2880 }
2881
2882 if (settings.contentType) {
2883 settings.headers['Content-Type'] = settings.contentType;
2884 }
2885
2886 settings.headers.Accept = MIME_TYPES[settings.dataType] || '*/*';
2887 };
2888
2889 var _timeout = function (xhr, settings) {
2890 xhr.onreadystatechange = null;
2891 xhr.abort();
2892 xhrCallbacks.error('error', 'timeout', xhr, settings);
2893 };
2894
2895 var _createAbortTimeout = function (xhr, settings) {
2896 return window.setTimeout(function () {
2897 _timeout(xhr, settings);
2898 }, settings.timeout);
2899 };
2900
2901 /*\
2902 * $.ajaxSettings
2903 [ property ]
2904 * Default ajax settings object.
2905 > Usage
2906 * If you want to change the global and default ajax settings, change this object properties:
2907 | $.ajaxSettings.error = function () {
2908 | // Handle any failed ajax request in your app
2909 | };
2910 | $.ajaxSettings.timeout = 1000; // 1 second
2911 \*/
2912 Kimbo.ajaxSettings = {
2913 type: 'GET',
2914 async: true,
2915 success: {},
2916 error: {},
2917 context: null,
2918 headers: {},
2919 data: null,
2920 crossDomain: false,
2921 timeout: 0,
2922 contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
2923 xhr: function () {
2924 return new window.XMLHttpRequest();
2925 }
2926 };
2927
2928 /*\
2929 * $.ajax
2930 [ method ]
2931 * Perform an asynchronous HTTP (Ajax) request.
2932 > Parameters
2933 - options (object) #optional An object with options
2934 o {
2935 o url (string) Url to make the request.
2936 o type (string) #optional Type of request. Could be `'GET'` or `'POST'`. Default value is `'GET'`.
2937 o async (boolean) #optional Default value is `true` if you want synchronous requests set option to `false`.
2938 o success (function) #optional A function that will be called if the request succeeds. Recieving (response, responseMessage, xhr, settings).
2939 o error (function) #optional A function that will be called if the request fails. Recieving (response, responseMessage, xhr, settings).
2940 o context (object) #optional The context in which all ajax request are made. By default the context are the settings object. Could be any DOM element.
2941 o headers (object) #optional An object with additional header key/value pairs to send along with the request.
2942 o data (string|object) #optional Additional data to send with the request, if it is an object is converted to a query string.
2943 o xhr (function) #optional A function that returns a `new XMLHttpRequest()` object by default.
2944 o crossDomain (boolean) #optional Indicate wether you want to force crossDomain requests. `false` by defualt.
2945 o timeout (number) #optional Set a default timeout in milliseconds for the request.
2946 o contentType (string) #optional The default and finest contentType for most cases is `'application/x-www-form-urlencoded; charset=UTF-8'`.
2947 o }
2948 = (object) The native xhr object.
2949 > Usage
2950 * Get a username passing an id to the /users url
2951 | $.ajax({
2952 | url '/users',
2953 | data: {
2954 | id: 3
2955 | },
2956 | success: function (response, responseMessage, xhr, settings) {
2957 | // Success...
2958 | },
2959 | error: function (response, responseMessage, xhr, settings) {
2960 | // Error...
2961 | }
2962 | });
2963 \*/
2964 Kimbo.ajax = function (options) {
2965 var settings = Kimbo.extend({}, Kimbo.ajaxSettings, options);
2966 var xhr, abortTimeout, callback;
2967
2968 // Add data to url
2969 if (settings.data) {
2970 settings.url += (/\?/.test(settings.url) ? '&' : '?') +
2971 Kimbo.param(settings.data);
2972 delete settings.data;
2973 }
2974
2975 // Set default context
2976 if (!settings.context) {
2977 settings.context = settings;
2978 }
2979
2980 // Check if its jsonp
2981 if (JSONP_RE.test(settings.url)) {
2982 return _getJSONP(settings);
2983 }
2984
2985 // Create new instance
2986 xhr = settings.xhr();
2987
2988 // User specified timeout
2989 if (settings.timeout > 0) {
2990 abortTimeout = _createAbortTimeout(xhr, settings);
2991 }
2992
2993 // On complete callback
2994 callback = function () {
2995 var response, status;
2996
2997 // Request complete
2998 if (xhr.readyState === 4) {
2999 status = xhr.status;
3000
3001 // Clear timeout
3002 window.clearTimeout(abortTimeout);
3003
3004 // Scuccess
3005 if ((status >= 200 && status < 300) || status === 304) {
3006 if (settings.async) {
3007 response = _handleResponse(xhr, settings);
3008 if (response !== false) {
3009 xhrCallbacks.success(response, xhr, settings);
3010 }
3011 }
3012
3013 // Fail
3014 } else {
3015 xhrCallbacks.error('error', xhr.statusText, xhr, settings);
3016 }
3017 }
3018 };
3019
3020 // Listen for response
3021 xhr.onreadystatechange = callback;
3022
3023 // Init request
3024 xhr.open(settings.type, settings.url, settings.async);
3025
3026 // Set settings headers
3027 _setHeaders(settings);
3028
3029 // Set xhr headers
3030 Kimbo.forEach(settings.headers, function (header, value) {
3031 xhr.setRequestHeader(header, value);
3032 });
3033
3034 // Try to send request
3035 xhr.send(settings.data);
3036
3037 return (settings.async) ? xhr : callback();
3038 };
3039
3040 /*\
3041 * $.get
3042 [ method ]
3043 * Load data from the server using HTTP GET request.
3044 > Parameters
3045 - url (string) A string containing the URL to which the request is sent.
3046 - data (string|object) #optional An option string or object with data params to send to the server.
3047 - callback (function) A callback function to execute if the request succeeds.
3048 - type (string) #optional String with the type of the data to send (intelligent guess by default).
3049 > Usage
3050 | $.get('url/users.php', { id: '123' }, function (data) {
3051 | // Success
3052 | console.log('response:', data);
3053 | });
3054 * This method is a shorthand for the $.ajax
3055 | $.ajax({
3056 | url: url,
3057 | data: data,
3058 | success: success,
3059 | dataType: dataType
3060 | });
3061 \*/
3062
3063 /*\
3064 * $.post
3065 [ method ]
3066 * Load data from the server using HTTP POST request.
3067 > Parameters
3068 - url (string) A string containing the URL to which the request is sent.
3069 - data (string|object) #optional An option string or object with data params to send to the server.
3070 - callback (function) A callback function to execute if the request succeeds.
3071 - type (string) #optional String with the type of the data to send (intelligent guess by default).
3072 > Usage
3073 | $.post('url/users.php', { user: 'denis', pass: '123' }, function (data) {
3074 | // Success
3075 | console.log('response:', data);
3076 | });
3077 * This method is a shorthand for the $.ajax
3078 | $.ajax({
3079 | type: 'POST',
3080 | url: url,
3081 | data: data,
3082 | success: success,
3083 | dataType: dataType
3084 | });
3085 \*/
3086 Kimbo.forEach(['get', 'post'], function (method) {
3087 Kimbo[method] = function (url, data, callback, type) {
3088
3089 // Prepare arguments
3090 if (Kimbo.isFunction(data)) {
3091 type = type || callback;
3092 callback = data;
3093 data = null;
3094 }
3095
3096 // Call ajax
3097 return Kimbo.ajax({
3098 type: method.toUpperCase(),
3099 url: url,
3100 data: data,
3101 success: callback,
3102 dataType: type
3103 });
3104 };
3105 });
3106
3107 Kimbo.extend({
3108 /*\
3109 * $.getScript
3110 [ method ]
3111 * Load a JavaScript file from the server using a GET HTTP request, then execute it.
3112 > Parameters
3113 - url (string) A string containing the URL to which the request is sent.
3114 - callback (function) A callback function to execute if the request succeeds.
3115 > Usage
3116 | $.getScript('url/script.js', function (data) {
3117 | // Success
3118 | console.log('response:', data);
3119 | });
3120 * This method is a shorthand for the $.ajax
3121 | $.ajax({
3122 | url: url,
3123 | dataType: 'script',
3124 | success: success
3125 | });
3126 \*/
3127 getScript: function (url, callback) {
3128 return Kimbo.get(url, callback, 'script');
3129 },
3130
3131 /*\
3132 * $.getJSON
3133 [ method ]
3134 * Load data from the server using HTTP POST request.
3135 > Parameters
3136 - url (string) A string containing the URL to which the request is sent.
3137 - data (string|object) #optional An option string or object with data params to send to the server.
3138 - callback (function) A callback function to execute if the request succeeds.
3139 - type (string) #optional String with the type of the data to send (intelligent guess by default).
3140 > Usage
3141 | $.getJSON('url/test.json', { id: '2' }, function (data) {
3142 | // Success
3143 | console.log('response:', data);
3144 | });
3145 * This method is a shorthand for the $.ajax
3146 | $.ajax({
3147 | url: url,
3148 | dataType: 'json',
3149 | success: success
3150 | });
3151 * To get json data with jsonp:
3152 | $.getJSON('http://search.twitter.com/search.json?callback=?', 'q=#javascript', function (data) {
3153 | console.log(data);
3154 | });
3155 \*/
3156 getJSON: function (url, data, callback) {
3157 return Kimbo.get(url, data, callback, 'json');
3158 }
3159 });
3160
3161 // getJSONP internal use
3162 var _getJSONP = function (settings) {
3163 var jsonpCallback = Kimbo.ref + '_' + Date.now();
3164 var script = _.document.createElement('script');
3165 var head = _.document.head;
3166 var xhr = {
3167 abort: function () {
3168 window.clearTimeout(abortTimeout);
3169 head.removeChild(script);
3170 delete window[jsonpCallback];
3171 }
3172 };
3173 var abortTimeout;
3174
3175 // User specified timeout
3176 if (settings.timeout > 0) {
3177 abortTimeout = _createAbortTimeout(xhr, settings);
3178 }
3179
3180 // Set url
3181 script.src = settings.url.replace(JSONP_RE, '$1' + jsonpCallback + '$2');
3182
3183 // Jsonp callback
3184 window[jsonpCallback] = function (response) {
3185
3186 // Remove script
3187 xhr.abort();
3188
3189 // Fake xhr
3190 Kimbo.extend(xhr, {
3191 statusText: 'OK',
3192 status: 200,
3193 response: response,
3194 headers: settings.headers
3195 });
3196
3197 // Success
3198 xhrCallbacks.success(response, xhr, settings);
3199 };
3200
3201 // Set settings headers
3202 _setHeaders(settings);
3203
3204 // Apend script to head to make the request
3205 head.appendChild(script);
3206
3207 // Return fake xhr object to abort manually
3208 return xhr;
3209 };
3210
3211 /*\
3212 * $.param
3213 [ method ]
3214 * Create a serialized representation of an array or object, suitable for use in a URL query string or Ajax request.
3215 > Parameters
3216 - data (string|object) A string or object to serialize.
3217 > Usage
3218 | var obj = { name: 'Denis', last: 'Ciccale' };
3219 | var serialized = $.param(obj); // 'name=Denis&last=Ciccale'
3220 \*/
3221 Kimbo.param = function (data) {
3222 var params = '';
3223
3224 if (Kimbo.isObject(data)) {
3225 Kimbo.forEach(data, function (name, value) {
3226 params += name + '=' + value + '&';
3227 });
3228 } else {
3229 params = data;
3230 }
3231
3232 return window.encodeURIComponent(params)
3233 .replace(/%20/g, '+')
3234 .replace(/%\d[D6F]/g, window.unescape)
3235 .replace(/^\?|&$/g, '');
3236 };
3237});
3238