comparison default/node_modules/tablesaw/src/lib/shoestring-custom.js @ 0:1d038bc9b3d2 default tip

Up:default
author Liny <dev@neowd.com>
date Sat, 31 May 2025 09:21:51 +0800
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:1d038bc9b3d2
1 /*! Shoestring - v2.0.0 - 2017-02-14
2 * http://github.com/filamentgroup/shoestring/
3 * Copyright (c) 2017 Scott Jehl, Filament Group, Inc; Licensed MIT & GPLv2 */
4 (function( factory ) {
5 if( typeof define === 'function' && define.amd ) {
6 // AMD. Register as an anonymous module.
7 define( [ 'shoestring' ], factory );
8 } else if (typeof module === 'object' && module.exports) {
9 // Node/CommonJS
10 module.exports = factory();
11 } else {
12 // Browser globals
13 factory();
14 }
15 }(function () {
16 var win = typeof window !== "undefined" ? window : this;
17 var doc = win.document;
18
19
20 /**
21 * The shoestring object constructor.
22 *
23 * @param {string,object} prim The selector to find or element to wrap.
24 * @param {object} sec The context in which to match the `prim` selector.
25 * @returns shoestring
26 * @this window
27 */
28 function shoestring( prim, sec ){
29 var pType = typeof( prim ),
30 ret = [],
31 sel;
32
33 // return an empty shoestring object
34 if( !prim ){
35 return new Shoestring( ret );
36 }
37
38 // ready calls
39 if( prim.call ){
40 return shoestring.ready( prim );
41 }
42
43 // handle re-wrapping shoestring objects
44 if( prim.constructor === Shoestring && !sec ){
45 return prim;
46 }
47
48 // if string starting with <, make html
49 if( pType === "string" && prim.indexOf( "<" ) === 0 ){
50 var dfrag = doc.createElement( "div" );
51
52 dfrag.innerHTML = prim;
53
54 // TODO depends on children (circular)
55 return shoestring( dfrag ).children().each(function(){
56 dfrag.removeChild( this );
57 });
58 }
59
60 // if string, it's a selector, use qsa
61 if( pType === "string" ){
62 if( sec ){
63 return shoestring( sec ).find( prim );
64 }
65
66 sel = doc.querySelectorAll( prim );
67
68 return new Shoestring( sel, prim );
69 }
70
71 // array like objects or node lists
72 if( Object.prototype.toString.call( pType ) === '[object Array]' ||
73 (win.NodeList && prim instanceof win.NodeList) ){
74
75 return new Shoestring( prim, prim );
76 }
77
78 // if it's an array, use all the elements
79 if( prim.constructor === Array ){
80 return new Shoestring( prim, prim );
81 }
82
83 // otherwise assume it's an object the we want at an index
84 return new Shoestring( [prim], prim );
85 }
86
87 var Shoestring = function( ret, prim ) {
88 this.length = 0;
89 this.selector = prim;
90 shoestring.merge(this, ret);
91 };
92
93 // TODO only required for tests
94 Shoestring.prototype.reverse = [].reverse;
95
96 // For adding element set methods
97 shoestring.fn = Shoestring.prototype;
98
99 shoestring.Shoestring = Shoestring;
100
101 // For extending objects
102 // TODO move to separate module when we use prototypes
103 shoestring.extend = function( first, second ){
104 for( var i in second ){
105 if( second.hasOwnProperty( i ) ){
106 first[ i ] = second[ i ];
107 }
108 }
109
110 return first;
111 };
112
113 // taken directly from jQuery
114 shoestring.merge = function( first, second ) {
115 var len, j, i;
116
117 len = +second.length,
118 j = 0,
119 i = first.length;
120
121 for ( ; j < len; j++ ) {
122 first[ i++ ] = second[ j ];
123 }
124
125 first.length = i;
126
127 return first;
128 };
129
130 // expose
131 win.shoestring = shoestring;
132
133
134
135 /**
136 * Iterates over `shoestring` collections.
137 *
138 * @param {function} callback The callback to be invoked on each element and index
139 * @return shoestring
140 * @this shoestring
141 */
142 shoestring.fn.each = function( callback ){
143 return shoestring.each( this, callback );
144 };
145
146 shoestring.each = function( collection, callback ) {
147 var val;
148 for( var i = 0, il = collection.length; i < il; i++ ){
149 val = callback.call( collection[i], i, collection[i] );
150 if( val === false ){
151 break;
152 }
153 }
154
155 return collection;
156 };
157
158
159
160 /**
161 * Check for array membership.
162 *
163 * @param {object} needle The thing to find.
164 * @param {object} haystack The thing to find the needle in.
165 * @return {boolean}
166 * @this window
167 */
168 shoestring.inArray = function( needle, haystack ){
169 var isin = -1;
170 for( var i = 0, il = haystack.length; i < il; i++ ){
171 if( haystack.hasOwnProperty( i ) && haystack[ i ] === needle ){
172 isin = i;
173 }
174 }
175 return isin;
176 };
177
178
179
180 /**
181 * Bind callbacks to be run when the DOM is "ready".
182 *
183 * @param {function} fn The callback to be run
184 * @return shoestring
185 * @this shoestring
186 */
187 shoestring.ready = function( fn ){
188 if( ready && fn ){
189 fn.call( doc );
190 }
191 else if( fn ){
192 readyQueue.push( fn );
193 }
194 else {
195 runReady();
196 }
197
198 return [doc];
199 };
200
201 // TODO necessary?
202 shoestring.fn.ready = function( fn ){
203 shoestring.ready( fn );
204 return this;
205 };
206
207 // Empty and exec the ready queue
208 var ready = false,
209 readyQueue = [],
210 runReady = function(){
211 if( !ready ){
212 while( readyQueue.length ){
213 readyQueue.shift().call( doc );
214 }
215 ready = true;
216 }
217 };
218
219 // If DOM is already ready at exec time, depends on the browser.
220 // From: https://github.com/mobify/mobifyjs/blob/526841be5509e28fc949038021799e4223479f8d/src/capture.js#L128
221 if (doc.attachEvent ? doc.readyState === "complete" : doc.readyState !== "loading") {
222 runReady();
223 } else {
224 doc.addEventListener( "DOMContentLoaded", runReady, false );
225 doc.addEventListener( "readystatechange", runReady, false );
226 win.addEventListener( "load", runReady, false );
227 }
228
229
230
231 /**
232 * Checks the current set of elements against the selector, if one matches return `true`.
233 *
234 * @param {string} selector The selector to check.
235 * @return {boolean}
236 * @this {shoestring}
237 */
238 shoestring.fn.is = function( selector ){
239 var ret = false, self = this, parents, check;
240
241 // assume a dom element
242 if( typeof selector !== "string" ){
243 // array-like, ie shoestring objects or element arrays
244 if( selector.length && selector[0] ){
245 check = selector;
246 } else {
247 check = [selector];
248 }
249
250 return _checkElements(this, check);
251 }
252
253 parents = this.parent();
254
255 if( !parents.length ){
256 parents = shoestring( doc );
257 }
258
259 parents.each(function( i, e ) {
260 var children;
261
262 children = e.querySelectorAll( selector );
263
264 ret = _checkElements( self, children );
265 });
266
267 return ret;
268 };
269
270 function _checkElements(needles, haystack){
271 var ret = false;
272
273 needles.each(function() {
274 var j = 0;
275
276 while( j < haystack.length ){
277 if( this === haystack[j] ){
278 ret = true;
279 }
280
281 j++;
282 }
283 });
284
285 return ret;
286 }
287
288
289
290 /**
291 * Get data attached to the first element or set data values on all elements in the current set.
292 *
293 * @param {string} name The data attribute name.
294 * @param {any} value The value assigned to the data attribute.
295 * @return {any|shoestring}
296 * @this shoestring
297 */
298 shoestring.fn.data = function( name, value ){
299 if( name !== undefined ){
300 if( value !== undefined ){
301 return this.each(function(){
302 if( !this.shoestringData ){
303 this.shoestringData = {};
304 }
305
306 this.shoestringData[ name ] = value;
307 });
308 }
309 else {
310 if( this[ 0 ] ) {
311 if( this[ 0 ].shoestringData ) {
312 return this[ 0 ].shoestringData[ name ];
313 }
314 }
315 }
316 }
317 else {
318 return this[ 0 ] ? this[ 0 ].shoestringData || {} : undefined;
319 }
320 };
321
322
323 /**
324 * Remove data associated with `name` or all the data, for each element in the current set.
325 *
326 * @param {string} name The data attribute name.
327 * @return shoestring
328 * @this shoestring
329 */
330 shoestring.fn.removeData = function( name ){
331 return this.each(function(){
332 if( name !== undefined && this.shoestringData ){
333 this.shoestringData[ name ] = undefined;
334 delete this.shoestringData[ name ];
335 } else {
336 this[ 0 ].shoestringData = {};
337 }
338 });
339 };
340
341
342
343 /**
344 * An alias for the `shoestring` constructor.
345 */
346 win.$ = shoestring;
347
348
349
350 /**
351 * Add a class to each DOM element in the set of elements.
352 *
353 * @param {string} className The name of the class to be added.
354 * @return shoestring
355 * @this shoestring
356 */
357 shoestring.fn.addClass = function( className ){
358 var classes = className.replace(/^\s+|\s+$/g, '').split( " " );
359
360 return this.each(function(){
361 for( var i = 0, il = classes.length; i < il; i++ ){
362 if( this.className !== undefined &&
363 (this.className === "" ||
364 !this.className.match( new RegExp( "(^|\\s)" + classes[ i ] + "($|\\s)"))) ){
365 this.className += " " + classes[ i ];
366 }
367 }
368 });
369 };
370
371
372
373 /**
374 * Add elements matching the selector to the current set.
375 *
376 * @param {string} selector The selector for the elements to add from the DOM
377 * @return shoestring
378 * @this shoestring
379 */
380 shoestring.fn.add = function( selector ){
381 var ret = [];
382 this.each(function(){
383 ret.push( this );
384 });
385
386 shoestring( selector ).each(function(){
387 ret.push( this );
388 });
389
390 return shoestring( ret );
391 };
392
393
394
395 /**
396 * Insert an element or HTML string as the last child of each element in the set.
397 *
398 * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert.
399 * @return shoestring
400 * @this shoestring
401 */
402 shoestring.fn.append = function( fragment ){
403 if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){
404 fragment = shoestring( fragment );
405 }
406
407 return this.each(function( i ){
408 for( var j = 0, jl = fragment.length; j < jl; j++ ){
409 this.appendChild( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ] );
410 }
411 });
412 };
413
414
415
416 /**
417 * Insert the current set as the last child of the elements matching the selector.
418 *
419 * @param {string} selector The selector after which to append the current set.
420 * @return shoestring
421 * @this shoestring
422 */
423 shoestring.fn.appendTo = function( selector ){
424 return this.each(function(){
425 shoestring( selector ).append( this );
426 });
427 };
428
429
430
431 /**
432 * Get the value of the first element of the set or set the value of all the elements in the set.
433 *
434 * @param {string} name The attribute name.
435 * @param {string} value The new value for the attribute.
436 * @return {shoestring|string|undefined}
437 * @this {shoestring}
438 */
439 shoestring.fn.attr = function( name, value ){
440 var nameStr = typeof( name ) === "string";
441
442 if( value !== undefined || !nameStr ){
443 return this.each(function(){
444 if( nameStr ){
445 this.setAttribute( name, value );
446 } else {
447 for( var i in name ){
448 if( name.hasOwnProperty( i ) ){
449 this.setAttribute( i, name[ i ] );
450 }
451 }
452 }
453 });
454 } else {
455 return this[ 0 ] ? this[ 0 ].getAttribute( name ) : undefined;
456 }
457 };
458
459
460
461 /**
462 * Insert an element or HTML string before each element in the current set.
463 *
464 * @param {string|HTMLElement} fragment The HTML or HTMLElement to insert.
465 * @return shoestring
466 * @this shoestring
467 */
468 shoestring.fn.before = function( fragment ){
469 if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){
470 fragment = shoestring( fragment );
471 }
472
473 return this.each(function( i ){
474 for( var j = 0, jl = fragment.length; j < jl; j++ ){
475 this.parentNode.insertBefore( i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ], this );
476 }
477 });
478 };
479
480
481
482 /**
483 * Get the children of the current collection.
484 * @return shoestring
485 * @this shoestring
486 */
487 shoestring.fn.children = function(){
488 var ret = [],
489 childs,
490 j;
491 this.each(function(){
492 childs = this.children;
493 j = -1;
494
495 while( j++ < childs.length-1 ){
496 if( shoestring.inArray( childs[ j ], ret ) === -1 ){
497 ret.push( childs[ j ] );
498 }
499 }
500 });
501 return shoestring(ret);
502 };
503
504
505
506 /**
507 * Find an element matching the selector in the set of the current element and its parents.
508 *
509 * @param {string} selector The selector used to identify the target element.
510 * @return shoestring
511 * @this shoestring
512 */
513 shoestring.fn.closest = function( selector ){
514 var ret = [];
515
516 if( !selector ){
517 return shoestring( ret );
518 }
519
520 this.each(function(){
521 var element, $self = shoestring( element = this );
522
523 if( $self.is(selector) ){
524 ret.push( this );
525 return;
526 }
527
528 while( element.parentElement ) {
529 if( shoestring(element.parentElement).is(selector) ){
530 ret.push( element.parentElement );
531 break;
532 }
533
534 element = element.parentElement;
535 }
536 });
537
538 return shoestring( ret );
539 };
540
541
542
543 shoestring.cssExceptions = {
544 'float': [ 'cssFloat' ]
545 };
546
547
548
549 (function() {
550 var cssExceptions = shoestring.cssExceptions;
551
552 // IE8 uses marginRight instead of margin-right
553 function convertPropertyName( str ) {
554 return str.replace( /\-([A-Za-z])/g, function ( match, character ) {
555 return character.toUpperCase();
556 });
557 }
558
559 function _getStyle( element, property ) {
560 return win.getComputedStyle( element, null ).getPropertyValue( property );
561 }
562
563 var vendorPrefixes = [ '', '-webkit-', '-ms-', '-moz-', '-o-', '-khtml-' ];
564
565 /**
566 * Private function for getting the computed style of an element.
567 *
568 * **NOTE** Please use the [css](../css.js.html) method instead.
569 *
570 * @method _getStyle
571 * @param {HTMLElement} element The element we want the style property for.
572 * @param {string} property The css property we want the style for.
573 */
574 shoestring._getStyle = function( element, property ) {
575 var convert, value, j, k;
576
577 if( cssExceptions[ property ] ) {
578 for( j = 0, k = cssExceptions[ property ].length; j < k; j++ ) {
579 value = _getStyle( element, cssExceptions[ property ][ j ] );
580
581 if( value ) {
582 return value;
583 }
584 }
585 }
586
587 for( j = 0, k = vendorPrefixes.length; j < k; j++ ) {
588 convert = convertPropertyName( vendorPrefixes[ j ] + property );
589
590 // VendorprefixKeyName || key-name
591 value = _getStyle( element, convert );
592
593 if( convert !== property ) {
594 value = value || _getStyle( element, property );
595 }
596
597 if( vendorPrefixes[ j ] ) {
598 // -vendorprefix-key-name
599 value = value || _getStyle( element, vendorPrefixes[ j ] + property );
600 }
601
602 if( value ) {
603 return value;
604 }
605 }
606
607 return undefined;
608 };
609 })();
610
611
612
613 (function() {
614 var cssExceptions = shoestring.cssExceptions;
615
616 // IE8 uses marginRight instead of margin-right
617 function convertPropertyName( str ) {
618 return str.replace( /\-([A-Za-z])/g, function ( match, character ) {
619 return character.toUpperCase();
620 });
621 }
622
623 /**
624 * Private function for setting the style of an element.
625 *
626 * **NOTE** Please use the [css](../css.js.html) method instead.
627 *
628 * @method _setStyle
629 * @param {HTMLElement} element The element we want to style.
630 * @param {string} property The property being used to style the element.
631 * @param {string} value The css value for the style property.
632 */
633 shoestring._setStyle = function( element, property, value ) {
634 var convertedProperty = convertPropertyName(property);
635
636 element.style[ property ] = value;
637
638 if( convertedProperty !== property ) {
639 element.style[ convertedProperty ] = value;
640 }
641
642 if( cssExceptions[ property ] ) {
643 for( var j = 0, k = cssExceptions[ property ].length; j<k; j++ ) {
644 element.style[ cssExceptions[ property ][ j ] ] = value;
645 }
646 }
647 };
648 })();
649
650
651
652 /**
653 * Get the compute style property of the first element or set the value of a style property
654 * on all elements in the set.
655 *
656 * @method _setStyle
657 * @param {string} property The property being used to style the element.
658 * @param {string|undefined} value The css value for the style property.
659 * @return {string|shoestring}
660 * @this shoestring
661 */
662 shoestring.fn.css = function( property, value ){
663 if( !this[0] ){
664 return;
665 }
666
667 if( typeof property === "object" ) {
668 return this.each(function() {
669 for( var key in property ) {
670 if( property.hasOwnProperty( key ) ) {
671 shoestring._setStyle( this, key, property[key] );
672 }
673 }
674 });
675 } else {
676 // assignment else retrieve first
677 if( value !== undefined ){
678 return this.each(function(){
679 shoestring._setStyle( this, property, value );
680 });
681 }
682
683 return shoestring._getStyle( this[0], property );
684 }
685 };
686
687
688
689 /**
690 * Returns the indexed element wrapped in a new `shoestring` object.
691 *
692 * @param {integer} index The index of the element to wrap and return.
693 * @return shoestring
694 * @this shoestring
695 */
696 shoestring.fn.eq = function( index ){
697 if( this[index] ){
698 return shoestring( this[index] );
699 }
700
701 return shoestring([]);
702 };
703
704
705
706 /**
707 * Filter out the current set if they do *not* match the passed selector or
708 * the supplied callback returns false
709 *
710 * @param {string,function} selector The selector or boolean return value callback used to filter the elements.
711 * @return shoestring
712 * @this shoestring
713 */
714 shoestring.fn.filter = function( selector ){
715 var ret = [];
716
717 this.each(function( index ){
718 var wsel;
719
720 if( typeof selector === 'function' ) {
721 if( selector.call( this, index ) !== false ) {
722 ret.push( this );
723 }
724 } else {
725 if( !this.parentNode ){
726 var context = shoestring( doc.createDocumentFragment() );
727
728 context[ 0 ].appendChild( this );
729 wsel = shoestring( selector, context );
730 } else {
731 wsel = shoestring( selector, this.parentNode );
732 }
733
734 if( shoestring.inArray( this, wsel ) > -1 ){
735 ret.push( this );
736 }
737 }
738 });
739
740 return shoestring( ret );
741 };
742
743
744
745 /**
746 * Find descendant elements of the current collection.
747 *
748 * @param {string} selector The selector used to find the children
749 * @return shoestring
750 * @this shoestring
751 */
752 shoestring.fn.find = function( selector ){
753 var ret = [],
754 finds;
755 this.each(function(){
756 finds = this.querySelectorAll( selector );
757
758 for( var i = 0, il = finds.length; i < il; i++ ){
759 ret = ret.concat( finds[i] );
760 }
761 });
762 return shoestring( ret );
763 };
764
765
766
767 /**
768 * Returns the first element of the set wrapped in a new `shoestring` object.
769 *
770 * @return shoestring
771 * @this shoestring
772 */
773 shoestring.fn.first = function(){
774 return this.eq( 0 );
775 };
776
777
778
779 /**
780 * Returns the raw DOM node at the passed index.
781 *
782 * @param {integer} index The index of the element to wrap and return.
783 * @return {HTMLElement|undefined|array}
784 * @this shoestring
785 */
786 shoestring.fn.get = function( index ){
787
788 // return an array of elements if index is undefined
789 if( index === undefined ){
790 var elements = [];
791
792 for( var i = 0; i < this.length; i++ ){
793 elements.push( this[ i ] );
794 }
795
796 return elements;
797 } else {
798 return this[ index ];
799 }
800 };
801
802
803
804 var set = function( html ){
805 if( typeof html === "string" || typeof html === "number" ){
806 return this.each(function(){
807 this.innerHTML = "" + html;
808 });
809 } else {
810 var h = "";
811 if( typeof html.length !== "undefined" ){
812 for( var i = 0, l = html.length; i < l; i++ ){
813 h += html[i].outerHTML;
814 }
815 } else {
816 h = html.outerHTML;
817 }
818 return this.each(function(){
819 this.innerHTML = h;
820 });
821 }
822 };
823 /**
824 * Gets or sets the `innerHTML` from all the elements in the set.
825 *
826 * @param {string|undefined} html The html to assign
827 * @return {string|shoestring}
828 * @this shoestring
829 */
830 shoestring.fn.html = function( html ){
831 if( typeof html !== "undefined" ){
832 return set.call( this, html );
833 } else { // get
834 var pile = "";
835
836 this.each(function(){
837 pile += this.innerHTML;
838 });
839
840 return pile;
841 }
842 };
843
844
845
846 (function() {
847 function _getIndex( set, test ) {
848 var i, result, element;
849
850 for( i = result = 0; i < set.length; i++ ) {
851 element = set.item ? set.item(i) : set[i];
852
853 if( test(element) ){
854 return result;
855 }
856
857 // ignore text nodes, etc
858 // NOTE may need to be more permissive
859 if( element.nodeType === 1 ){
860 result++;
861 }
862 }
863
864 return -1;
865 }
866
867 /**
868 * Find the index in the current set for the passed selector.
869 * Without a selector it returns the index of the first node within the array of its siblings.
870 *
871 * @param {string|undefined} selector The selector used to search for the index.
872 * @return {integer}
873 * @this {shoestring}
874 */
875 shoestring.fn.index = function( selector ){
876 var self, children;
877
878 self = this;
879
880 // no arg? check the children, otherwise check each element that matches
881 if( selector === undefined ){
882 children = ( ( this[ 0 ] && this[0].parentNode ) || doc.documentElement).childNodes;
883
884 // check if the element matches the first of the set
885 return _getIndex(children, function( element ) {
886 return self[0] === element;
887 });
888 } else {
889
890 // check if the element matches the first selected node from the parent
891 return _getIndex(self, function( element ) {
892 return element === (shoestring( selector, element.parentNode )[ 0 ]);
893 });
894 }
895 };
896 })();
897
898
899
900 /**
901 * Insert the current set before the elements matching the selector.
902 *
903 * @param {string} selector The selector before which to insert the current set.
904 * @return shoestring
905 * @this shoestring
906 */
907 shoestring.fn.insertBefore = function( selector ){
908 return this.each(function(){
909 shoestring( selector ).before( this );
910 });
911 };
912
913
914
915 /**
916 * Returns the last element of the set wrapped in a new `shoestring` object.
917 *
918 * @return shoestring
919 * @this shoestring
920 */
921 shoestring.fn.last = function(){
922 return this.eq( this.length - 1 );
923 };
924
925
926
927 /**
928 * Returns a `shoestring` object with the set of siblings of each element in the original set.
929 *
930 * @return shoestring
931 * @this shoestring
932 */
933 shoestring.fn.next = function(){
934
935 var result = [];
936
937 // TODO need to implement map
938 this.each(function() {
939 var children, item, found;
940
941 // get the child nodes for this member of the set
942 children = shoestring( this.parentNode )[0].childNodes;
943
944 for( var i = 0; i < children.length; i++ ){
945 item = children.item( i );
946
947 // found the item we needed (found) which means current item value is
948 // the next node in the list, as long as it's viable grab it
949 // NOTE may need to be more permissive
950 if( found && item.nodeType === 1 ){
951 result.push( item );
952 break;
953 }
954
955 // find the current item and mark it as found
956 if( item === this ){
957 found = true;
958 }
959 }
960 });
961
962 return shoestring( result );
963 };
964
965
966
967 /**
968 * Removes elements from the current set.
969 *
970 * @param {string} selector The selector to use when removing the elements.
971 * @return shoestring
972 * @this shoestring
973 */
974 shoestring.fn.not = function( selector ){
975 var ret = [];
976
977 this.each(function(){
978 var found = shoestring( selector, this.parentNode );
979
980 if( shoestring.inArray(this, found) === -1 ){
981 ret.push( this );
982 }
983 });
984
985 return shoestring( ret );
986 };
987
988
989
990 /**
991 * Returns the set of first parents for each element in the current set.
992 *
993 * @return shoestring
994 * @this shoestring
995 */
996 shoestring.fn.parent = function(){
997 var ret = [],
998 parent;
999
1000 this.each(function(){
1001 // no parent node, assume top level
1002 // jQuery parent: return the document object for <html> or the parent node if it exists
1003 parent = (this === doc.documentElement ? doc : this.parentNode);
1004
1005 // if there is a parent and it's not a document fragment
1006 if( parent && parent.nodeType !== 11 ){
1007 ret.push( parent );
1008 }
1009 });
1010
1011 return shoestring(ret);
1012 };
1013
1014
1015
1016 /**
1017 * Add an HTML string or element before the children of each element in the current set.
1018 *
1019 * @param {string|HTMLElement} fragment The HTML string or element to add.
1020 * @return shoestring
1021 * @this shoestring
1022 */
1023 shoestring.fn.prepend = function( fragment ){
1024 if( typeof( fragment ) === "string" || fragment.nodeType !== undefined ){
1025 fragment = shoestring( fragment );
1026 }
1027
1028 return this.each(function( i ){
1029
1030 for( var j = 0, jl = fragment.length; j < jl; j++ ){
1031 var insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ];
1032 if ( this.firstChild ){
1033 this.insertBefore( insertEl, this.firstChild );
1034 } else {
1035 this.appendChild( insertEl );
1036 }
1037 }
1038 });
1039 };
1040
1041
1042
1043 /**
1044 * Returns a `shoestring` object with the set of *one* siblingx before each element in the original set.
1045 *
1046 * @return shoestring
1047 * @this shoestring
1048 */
1049 shoestring.fn.prev = function(){
1050
1051 var result = [];
1052
1053 // TODO need to implement map
1054 this.each(function() {
1055 var children, item, found;
1056
1057 // get the child nodes for this member of the set
1058 children = shoestring( this.parentNode )[0].childNodes;
1059
1060 for( var i = children.length -1; i >= 0; i-- ){
1061 item = children.item( i );
1062
1063 // found the item we needed (found) which means current item value is
1064 // the next node in the list, as long as it's viable grab it
1065 // NOTE may need to be more permissive
1066 if( found && item.nodeType === 1 ){
1067 result.push( item );
1068 break;
1069 }
1070
1071 // find the current item and mark it as found
1072 if( item === this ){
1073 found = true;
1074 }
1075 }
1076 });
1077
1078 return shoestring( result );
1079 };
1080
1081
1082
1083 /**
1084 * Returns a `shoestring` object with the set of *all* siblings before each element in the original set.
1085 *
1086 * @return shoestring
1087 * @this shoestring
1088 */
1089 shoestring.fn.prevAll = function(){
1090
1091 var result = [];
1092
1093 this.each(function() {
1094 var $previous = shoestring( this ).prev();
1095
1096 while( $previous.length ){
1097 result.push( $previous[0] );
1098 $previous = $previous.prev();
1099 }
1100 });
1101
1102 return shoestring( result );
1103 };
1104
1105
1106
1107 /**
1108 * Remove an attribute from each element in the current set.
1109 *
1110 * @param {string} name The name of the attribute.
1111 * @return shoestring
1112 * @this shoestring
1113 */
1114 shoestring.fn.removeAttr = function( name ){
1115 return this.each(function(){
1116 this.removeAttribute( name );
1117 });
1118 };
1119
1120
1121
1122 /**
1123 * Remove a class from each DOM element in the set of elements.
1124 *
1125 * @param {string} className The name of the class to be removed.
1126 * @return shoestring
1127 * @this shoestring
1128 */
1129 shoestring.fn.removeClass = function( cname ){
1130 var classes = cname.replace(/^\s+|\s+$/g, '').split( " " );
1131
1132 return this.each(function(){
1133 var newClassName, regex;
1134
1135 for( var i = 0, il = classes.length; i < il; i++ ){
1136 if( this.className !== undefined ){
1137 regex = new RegExp( "(^|\\s)" + classes[ i ] + "($|\\s)", "gmi" );
1138 newClassName = this.className.replace( regex, " " );
1139
1140 this.className = newClassName.replace(/^\s+|\s+$/g, '');
1141 }
1142 }
1143 });
1144 };
1145
1146
1147
1148 /**
1149 * Remove the current set of elements from the DOM.
1150 *
1151 * @return shoestring
1152 * @this shoestring
1153 */
1154 shoestring.fn.remove = function(){
1155 return this.each(function(){
1156 if( this.parentNode ) {
1157 this.parentNode.removeChild( this );
1158 }
1159 });
1160 };
1161
1162
1163
1164 /**
1165 * Replace each element in the current set with that argument HTML string or HTMLElement.
1166 *
1167 * @param {string|HTMLElement} fragment The value to assign.
1168 * @return shoestring
1169 * @this shoestring
1170 */
1171 shoestring.fn.replaceWith = function( fragment ){
1172 if( typeof( fragment ) === "string" ){
1173 fragment = shoestring( fragment );
1174 }
1175
1176 var ret = [];
1177
1178 if( fragment.length > 1 ){
1179 fragment = fragment.reverse();
1180 }
1181 this.each(function( i ){
1182 var clone = this.cloneNode( true ),
1183 insertEl;
1184 ret.push( clone );
1185
1186 // If there is no parentNode, this is pointless, drop it.
1187 if( !this.parentNode ){ return; }
1188
1189 if( fragment.length === 1 ){
1190 insertEl = i > 0 ? fragment[ 0 ].cloneNode( true ) : fragment[ 0 ];
1191 this.parentNode.replaceChild( insertEl, this );
1192 } else {
1193 for( var j = 0, jl = fragment.length; j < jl; j++ ){
1194 insertEl = i > 0 ? fragment[ j ].cloneNode( true ) : fragment[ j ];
1195 this.parentNode.insertBefore( insertEl, this.nextSibling );
1196 }
1197 this.parentNode.removeChild( this );
1198 }
1199 });
1200
1201 return shoestring( ret );
1202 };
1203
1204
1205
1206 /**
1207 * Get all of the sibling elements for each element in the current set.
1208 *
1209 * @return shoestring
1210 * @this shoestring
1211 */
1212 shoestring.fn.siblings = function(){
1213
1214 if( !this.length ) {
1215 return shoestring( [] );
1216 }
1217
1218 var sibs = [], el = this[ 0 ].parentNode.firstChild;
1219
1220 do {
1221 if( el.nodeType === 1 && el !== this[ 0 ] ) {
1222 sibs.push( el );
1223 }
1224
1225 el = el.nextSibling;
1226 } while( el );
1227
1228 return shoestring( sibs );
1229 };
1230
1231
1232
1233 var getText = function( elem ){
1234 var node,
1235 ret = "",
1236 i = 0,
1237 nodeType = elem.nodeType;
1238
1239 if ( !nodeType ) {
1240 // If no nodeType, this is expected to be an array
1241 while ( (node = elem[i++]) ) {
1242 // Do not traverse comment nodes
1243 ret += getText( node );
1244 }
1245 } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
1246 // Use textContent for elements
1247 // innerText usage removed for consistency of new lines (jQuery #11153)
1248 if ( typeof elem.textContent === "string" ) {
1249 return elem.textContent;
1250 } else {
1251 // Traverse its children
1252 for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
1253 ret += getText( elem );
1254 }
1255 }
1256 } else if ( nodeType === 3 || nodeType === 4 ) {
1257 return elem.nodeValue;
1258 }
1259 // Do not include comment or processing instruction nodes
1260
1261 return ret;
1262 };
1263
1264 /**
1265 * Recursively retrieve the text content of the each element in the current set.
1266 *
1267 * @return shoestring
1268 * @this shoestring
1269 */
1270 shoestring.fn.text = function() {
1271
1272 return getText( this );
1273 };
1274
1275
1276
1277
1278 /**
1279 * Get the value of the first element or set the value of all elements in the current set.
1280 *
1281 * @param {string} value The value to set.
1282 * @return shoestring
1283 * @this shoestring
1284 */
1285 shoestring.fn.val = function( value ){
1286 var el;
1287 if( value !== undefined ){
1288 return this.each(function(){
1289 if( this.tagName === "SELECT" ){
1290 var optionSet, option,
1291 options = this.options,
1292 values = [],
1293 i = options.length,
1294 newIndex;
1295
1296 values[0] = value;
1297 while ( i-- ) {
1298 option = options[ i ];
1299 if ( (option.selected = shoestring.inArray( option.value, values ) >= 0) ) {
1300 optionSet = true;
1301 newIndex = i;
1302 }
1303 }
1304 // force browsers to behave consistently when non-matching value is set
1305 if ( !optionSet ) {
1306 this.selectedIndex = -1;
1307 } else {
1308 this.selectedIndex = newIndex;
1309 }
1310 } else {
1311 this.value = value;
1312 }
1313 });
1314 } else {
1315 el = this[0];
1316
1317 if( el.tagName === "SELECT" ){
1318 if( el.selectedIndex < 0 ){ return ""; }
1319 return el.options[ el.selectedIndex ].value;
1320 } else {
1321 return el.value;
1322 }
1323 }
1324 };
1325
1326
1327
1328 /**
1329 * Private function for setting/getting the offset property for height/width.
1330 *
1331 * **NOTE** Please use the [width](width.js.html) or [height](height.js.html) methods instead.
1332 *
1333 * @param {shoestring} set The set of elements.
1334 * @param {string} name The string "height" or "width".
1335 * @param {float|undefined} value The value to assign.
1336 * @return shoestring
1337 * @this window
1338 */
1339 shoestring._dimension = function( set, name, value ){
1340 var offsetName;
1341
1342 if( value === undefined ){
1343 offsetName = name.replace(/^[a-z]/, function( letter ) {
1344 return letter.toUpperCase();
1345 });
1346
1347 return set[ 0 ][ "offset" + offsetName ];
1348 } else {
1349 // support integer values as pixels
1350 value = typeof value === "string" ? value : value + "px";
1351
1352 return set.each(function(){
1353 this.style[ name ] = value;
1354 });
1355 }
1356 };
1357
1358
1359
1360 /**
1361 * Gets the width value of the first element or sets the width for the whole set.
1362 *
1363 * @param {float|undefined} value The value to assign.
1364 * @return shoestring
1365 * @this shoestring
1366 */
1367 shoestring.fn.width = function( value ){
1368 return shoestring._dimension( this, "width", value );
1369 };
1370
1371
1372
1373 /**
1374 * Wraps the child elements in the provided HTML.
1375 *
1376 * @param {string} html The wrapping HTML.
1377 * @return shoestring
1378 * @this shoestring
1379 */
1380 shoestring.fn.wrapInner = function( html ){
1381 return this.each(function(){
1382 var inH = this.innerHTML;
1383
1384 this.innerHTML = "";
1385 shoestring( this ).append( shoestring( html ).html( inH ) );
1386 });
1387 };
1388
1389
1390
1391 function initEventCache( el, evt ) {
1392 if ( !el.shoestringData ) {
1393 el.shoestringData = {};
1394 }
1395 if ( !el.shoestringData.events ) {
1396 el.shoestringData.events = {};
1397 }
1398 if ( !el.shoestringData.loop ) {
1399 el.shoestringData.loop = {};
1400 }
1401 if ( !el.shoestringData.events[ evt ] ) {
1402 el.shoestringData.events[ evt ] = [];
1403 }
1404 }
1405
1406 function addToEventCache( el, evt, eventInfo ) {
1407 var obj = {};
1408 obj.isCustomEvent = eventInfo.isCustomEvent;
1409 obj.callback = eventInfo.callfunc;
1410 obj.originalCallback = eventInfo.originalCallback;
1411 obj.namespace = eventInfo.namespace;
1412
1413 el.shoestringData.events[ evt ].push( obj );
1414
1415 if( eventInfo.customEventLoop ) {
1416 el.shoestringData.loop[ evt ] = eventInfo.customEventLoop;
1417 }
1418 }
1419
1420 /**
1421 * Bind a callback to an event for the currrent set of elements.
1422 *
1423 * @param {string} evt The event(s) to watch for.
1424 * @param {object,function} data Data to be included with each event or the callback.
1425 * @param {function} originalCallback Callback to be invoked when data is define.d.
1426 * @return shoestring
1427 * @this shoestring
1428 */
1429 shoestring.fn.bind = function( evt, data, originalCallback ){
1430
1431 if( typeof data === "function" ){
1432 originalCallback = data;
1433 data = null;
1434 }
1435
1436 var evts = evt.split( " " );
1437
1438 // NOTE the `triggeredElement` is purely for custom events from IE
1439 function encasedCallback( e, namespace, triggeredElement ){
1440 var result;
1441
1442 if( e._namespace && e._namespace !== namespace ) {
1443 return;
1444 }
1445
1446 e.data = data;
1447 e.namespace = e._namespace;
1448
1449 var returnTrue = function(){
1450 return true;
1451 };
1452
1453 e.isDefaultPrevented = function(){
1454 return false;
1455 };
1456
1457 var originalPreventDefault = e.preventDefault;
1458 var preventDefaultConstructor = function(){
1459 if( originalPreventDefault ) {
1460 return function(){
1461 e.isDefaultPrevented = returnTrue;
1462 originalPreventDefault.call(e);
1463 };
1464 } else {
1465 return function(){
1466 e.isDefaultPrevented = returnTrue;
1467 e.returnValue = false;
1468 };
1469 }
1470 };
1471
1472 // thanks https://github.com/jonathantneal/EventListener
1473 e.target = triggeredElement || e.target || e.srcElement;
1474 e.preventDefault = preventDefaultConstructor();
1475 e.stopPropagation = e.stopPropagation || function () {
1476 e.cancelBubble = true;
1477 };
1478
1479 result = originalCallback.apply(this, [ e ].concat( e._args ) );
1480
1481 if( result === false ){
1482 e.preventDefault();
1483 e.stopPropagation();
1484 }
1485
1486 return result;
1487 }
1488
1489 return this.each(function(){
1490 var domEventCallback,
1491 customEventCallback,
1492 customEventLoop,
1493 oEl = this;
1494
1495 for( var i = 0, il = evts.length; i < il; i++ ){
1496 var split = evts[ i ].split( "." ),
1497 evt = split[ 0 ],
1498 namespace = split.length > 0 ? split[ 1 ] : null;
1499
1500 domEventCallback = function( originalEvent ) {
1501 if( oEl.ssEventTrigger ) {
1502 originalEvent._namespace = oEl.ssEventTrigger._namespace;
1503 originalEvent._args = oEl.ssEventTrigger._args;
1504
1505 oEl.ssEventTrigger = null;
1506 }
1507 return encasedCallback.call( oEl, originalEvent, namespace );
1508 };
1509 customEventCallback = null;
1510 customEventLoop = null;
1511
1512 initEventCache( this, evt );
1513
1514 this.addEventListener( evt, domEventCallback, false );
1515
1516 addToEventCache( this, evt, {
1517 callfunc: customEventCallback || domEventCallback,
1518 isCustomEvent: !!customEventCallback,
1519 customEventLoop: customEventLoop,
1520 originalCallback: originalCallback,
1521 namespace: namespace
1522 });
1523 }
1524 });
1525 };
1526
1527 shoestring.fn.on = shoestring.fn.bind;
1528
1529
1530
1531
1532 /**
1533 * Unbind a previous bound callback for an event.
1534 *
1535 * @param {string} event The event(s) the callback was bound to..
1536 * @param {function} callback Callback to unbind.
1537 * @return shoestring
1538 * @this shoestring
1539 */
1540 shoestring.fn.unbind = function( event, callback ){
1541
1542
1543 var evts = event ? event.split( " " ) : [];
1544
1545 return this.each(function(){
1546 if( !this.shoestringData || !this.shoestringData.events ) {
1547 return;
1548 }
1549
1550 if( !evts.length ) {
1551 unbindAll.call( this );
1552 } else {
1553 var split, evt, namespace;
1554 for( var i = 0, il = evts.length; i < il; i++ ){
1555 split = evts[ i ].split( "." ),
1556 evt = split[ 0 ],
1557 namespace = split.length > 0 ? split[ 1 ] : null;
1558
1559 if( evt ) {
1560 unbind.call( this, evt, namespace, callback );
1561 } else {
1562 unbindAll.call( this, namespace, callback );
1563 }
1564 }
1565 }
1566 });
1567 };
1568
1569 function unbind( evt, namespace, callback ) {
1570 var bound = this.shoestringData.events[ evt ];
1571 if( !(bound && bound.length) ) {
1572 return;
1573 }
1574
1575 var matched = [], j, jl;
1576 for( j = 0, jl = bound.length; j < jl; j++ ) {
1577 if( !namespace || namespace === bound[ j ].namespace ) {
1578 if( callback === undefined || callback === bound[ j ].originalCallback ) {
1579 this.removeEventListener( evt, bound[ j ].callback, false );
1580 matched.push( j );
1581 }
1582 }
1583 }
1584
1585 for( j = 0, jl = matched.length; j < jl; j++ ) {
1586 this.shoestringData.events[ evt ].splice( j, 1 );
1587 }
1588 }
1589
1590 function unbindAll( namespace, callback ) {
1591 for( var evtKey in this.shoestringData.events ) {
1592 unbind.call( this, evtKey, namespace, callback );
1593 }
1594 }
1595
1596 shoestring.fn.off = shoestring.fn.unbind;
1597
1598
1599 /**
1600 * Bind a callback to an event for the currrent set of elements, unbind after one occurence.
1601 *
1602 * @param {string} event The event(s) to watch for.
1603 * @param {function} callback Callback to invoke on the event.
1604 * @return shoestring
1605 * @this shoestring
1606 */
1607 shoestring.fn.one = function( event, callback ){
1608 var evts = event.split( " " );
1609
1610 return this.each(function(){
1611 var thisevt, cbs = {}, $t = shoestring( this );
1612
1613 for( var i = 0, il = evts.length; i < il; i++ ){
1614 thisevt = evts[ i ];
1615
1616 cbs[ thisevt ] = function( e ){
1617 var $t = shoestring( this );
1618
1619 for( var j in cbs ) {
1620 $t.unbind( j, cbs[ j ] );
1621 }
1622
1623 return callback.apply( this, [ e ].concat( e._args ) );
1624 };
1625
1626 $t.bind( thisevt, cbs[ thisevt ] );
1627 }
1628 });
1629 };
1630
1631
1632
1633 /**
1634 * Trigger an event on the first element in the set, no bubbling, no defaults.
1635 *
1636 * @param {string} event The event(s) to trigger.
1637 * @param {object} args Arguments to append to callback invocations.
1638 * @return shoestring
1639 * @this shoestring
1640 */
1641 shoestring.fn.triggerHandler = function( event, args ){
1642 var e = event.split( " " )[ 0 ],
1643 el = this[ 0 ],
1644 ret;
1645
1646 // See this.fireEvent( 'on' + evts[ i ], document.createEventObject() ); instead of click() etc in trigger.
1647 if( doc.createEvent && el.shoestringData && el.shoestringData.events && el.shoestringData.events[ e ] ){
1648 var bindings = el.shoestringData.events[ e ];
1649 for (var i in bindings ){
1650 if( bindings.hasOwnProperty( i ) ){
1651 event = doc.createEvent( "Event" );
1652 event.initEvent( e, true, true );
1653 event._args = args;
1654 args.unshift( event );
1655
1656 ret = bindings[ i ].originalCallback.apply( event.target, args );
1657 }
1658 }
1659 }
1660
1661 return ret;
1662 };
1663
1664
1665
1666 /**
1667 * Trigger an event on each of the DOM elements in the current set.
1668 *
1669 * @param {string} event The event(s) to trigger.
1670 * @param {object} args Arguments to append to callback invocations.
1671 * @return shoestring
1672 * @this shoestring
1673 */
1674 shoestring.fn.trigger = function( event, args ){
1675 var evts = event.split( " " );
1676
1677 return this.each(function(){
1678 var split, evt, namespace;
1679 for( var i = 0, il = evts.length; i < il; i++ ){
1680 split = evts[ i ].split( "." ),
1681 evt = split[ 0 ],
1682 namespace = split.length > 0 ? split[ 1 ] : null;
1683
1684 if( evt === "click" ){
1685 if( this.tagName === "INPUT" && this.type === "checkbox" && this.click ){
1686 this.click();
1687 return false;
1688 }
1689 }
1690
1691 if( doc.createEvent ){
1692 var event = doc.createEvent( "Event" );
1693 event.initEvent( evt, true, true );
1694 event._args = args;
1695 event._namespace = namespace;
1696
1697 this.dispatchEvent( event );
1698 }
1699 }
1700 });
1701 };
1702
1703
1704
1705 return shoestring;
1706 }));