Last active
December 24, 2024 09:54
-
-
Save xizon/a3be202a1b78c4853b02d07c1c2c2666 to your computer and use it in GitHub Desktop.
Vanilla-js Helpers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
************************************* | |
* Core Helpers | |
* | |
* @package: https://github.com/xizon/boot-helpers | |
* @version: 0.1.7 | |
* @last update: May 25, 2023 | |
* @author: UIUX Lab <[email protected]> | |
* @license: MIT | |
* | |
************************************* | |
*/ | |
/* | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Page load | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
__( document ).ready( function() { | |
//do something | |
}); | |
__( 'body' ).loader({ | |
imagesSelector: 'body img', | |
videosSelector: 'body video', | |
startEvent: function() { | |
//do something | |
console.log( '=> loading.' ); | |
}, | |
progressEvent: function(percent) { | |
//do something | |
console.log( '=> progress: ' + percent + '%' ); | |
}, | |
endEvent: function() { | |
console.log( '=> loaded!!!' ); | |
//do something | |
} | |
}); | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Dom demos | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
__( '.demo' ).remove(); | |
__( '.demo' ).empty(); | |
__( '.demo' ).show(); | |
__( '.demo' ).hide(); | |
__( '.demo' ).get(-1); //returns all elements | |
__( '.demo' ).get(0); //returns someone element | |
__( '.demo' ).len(); //returns length of elements | |
__( '.demo' ).addClass( 'class-3' ); | |
__( '.demo, .demo2' ).addClass( 'class-4' ); | |
__( '.demo' ).find( 'li' ).addClass( 'class-1 class-2' ); | |
__( '.demo' ).find( 'li' ).removeClass( 'class-2' ); | |
__( '.demo' ).find( '> .demo2' ).addClass( 'class-3' ); | |
__( '.demo' ).find( '> .demo2' ).find( 'li' ).addClass( 'class-3-2' ); | |
__( '.demo' ).closest( '.container' ).addClass( 'class-4' ); | |
__( '.demo' ).css({ | |
'background': '#f00', | |
'font-size': '18px' | |
}); | |
__( '#demo' ).css( 'background-color', '#f60' ); | |
__( '.demo' ).data( 'bg', 'red' ); | |
__( '.demo' ).attr( 'disabled', 'disabled' ); | |
__( '.demo' ).width( 300 ); | |
__( '.menu li:first-child' ).width( "50%" ); | |
if( __( '.demo' ).data( 'activated' ) === null ) { | |
//do something... | |
} | |
if( __( '.demo' ).attr( 'data-activated' ) === null ) { | |
//do something... | |
} | |
__( '.menu li' ).eq(1).append( '<span style="color:green">after</span>'); | |
__( '.menu li' ).eq(1).prepend( '<span style="color:red">before</span>'); | |
__( '.menu li' ).first().before( '<li style="color:green">(first)before</li>'); | |
__( '.menu li' ).last().after( '<li style="color:red">(last)after</li>'); | |
__( 'h1' ).wrapInner( '<span class="new-div" />' ); | |
√√ | |
__( 'h1' ).text( 'New H1' ); | |
__( '.demo1' ).prev().addClass( 'prev' ); | |
__( '.demo2' ).next().addClass( 'next' ); | |
__( '.demo3' ).parent().addClass( 'parent' ); | |
__( '.menu' ).parents().addClass( 'all-parents' ); | |
__( '.demo' ).children().addClass( 'children-all' ); | |
__( '.class-1' ).siblings().addClass( 'class-siblings' ); | |
__( '.demo1' ).prev( 'ul' ).addClass( 'prev' ); | |
__( '.demo2' ).next( 'ul' ).addClass( 'next' ); | |
__( '.demo3' ).parent( 'ul' ).addClass( 'parent' ); | |
__( '.menu' ).parents( 'ul' ).addClass( 'all-parents' ); | |
__( '.demo' ).children( '.demo-children2' ).addClass( 'children-single' ); | |
__( '.class-1' ).siblings( 'ul' ).addClass( 'class-siblings' ); | |
__( '.class-1' ).not( '.class-2' ).addClass( 'class-not' ); | |
__( '.class-1' ).filter( '.class-2' ).addClass( 'class-filter' ); | |
__( '.demo' ).trigger( 'click' ); | |
__( '.demo' ).toggleClass( 'class-toggle-1 class-toggle-2' ); | |
//append HTML Element | |
const htmlObject = document.createElement('div'); | |
htmlObject.innerHTML = '<span style="color:green">after</span>'; | |
__( '.menu li' ).eq(1).append( htmlObject.firstChild ); | |
//clone HTML Element | |
const cloneHTML = __( '.uix-menu__container' ).clone(); | |
__( cloneHTML ).addClass( 'is-mobile' ); | |
__( 'body' ).prependTo( cloneHTML ); | |
__( 'body' ).appendTo( cloneHTML ); | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Click event demos | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
__( '.menu li a' ).off( 'click' ).one( 'click', function( e ) { | |
e.preventDefault(); | |
console.log( 'This will be clicked only once' ); | |
}); | |
__( '.menu li' ).off( 'click' ).on( 'click', function( e ) { | |
console.log('e: ', e); | |
console.log('this: ', this); | |
console.log('index(): ', __( this ).index()); | |
console.log('attr(class): ', __( this ).attr( 'class' )); | |
__( this ).addClass( 'new-class' ) | |
}); | |
__( document ).off( 'click', '.menu li' ); | |
__( document ).on( 'click', '.menu li', function( e ) { | |
console.log('e: ', e); | |
console.log('this: ', this); | |
console.log('index(): ', __( this ).index()); | |
console.log('attr(class): ', __( this ).attr( 'class' )); | |
__( this ).addClass( 'new-class' ) | |
}); | |
__( '#imghere' ).off( 'click', imgFn); | |
__( '#imghere' ).on( 'click', imgFn); | |
function imgFn() { | |
console.log( 'imgFn' ); | |
} | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Retrieve data demos | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
console.log( '<h1> content: ' + __( 'h1' ).html()); | |
console.log( '<h1> content: ' + __( 'h1' ).text()); | |
console.log( 'length of .menu li: ' + __( '.menu li' ).len() ); | |
console.log( 'length of #none: ' + __( '#none' ).len() ); | |
console.log( 'width(): ' + __( '.demo' ).width() ); | |
console.log( 'outerWidth(): ' + __( '.demo' ).outerWidth() ); | |
console.log( 'outerWidth( true ): ' + __( '.demo' ).outerWidth(true) ); | |
console.log( 'height(): ' + __( '.demo' ).height() ); | |
console.log( 'outerHeight(): ' + __( '.demo' ).outerHeight() ); | |
console.log( 'outerHeight( true ): ' + __( '.demo' ).outerHeight(true) ); | |
console.log( 'document h: ', __( document ).height() ); | |
console.log( 'document w: ', __( document ).width() ); | |
console.log( 'window h: ', __( window ).height() ); | |
console.log( 'window h: ', __( window ).width() ); | |
console.log( 'window scrollTop: ', __( window ).scrollTop() ); | |
console.log( 'window scrollLeft: ', __( window ).scrollLeft() ); | |
console.log( 'data: [data-bg] value: ' + __( '.demo' ).data( 'bg' ) ); | |
console.log( 'attr: [disabled] value: ' + __( '.demo' ).attr( 'disabled' ) ); | |
console.log( 'allAttrs(): ', __( '.demo' ).allAttrs() ); | |
console.log( __( '.demo' ).hasClass( 'class-1' ) ); | |
console.log( __( 'h1' ).offset() ); | |
console.log( __( 'h1' ).position() ); | |
console.log( __( '.demo' ).maxDimension() ); | |
console.log( __( '.menu li:nth-child(2)' ).index() ); | |
// Traverse all attribute names and values | |
const allAttrs = __( '#demo' ).allAttrs(); | |
for (let key in allAttrs) { | |
console.log( key + ' = ' + allAttrs[key] ); | |
} | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Loop demos | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
__( '.menu li' ).each( function( index, curSelector ) { | |
console.log( index + ' : ' ); | |
console.log( this ); | |
this.style.background = '#333'; | |
__( this ).css({ | |
'background': '#f00', | |
'font-size': '18px' | |
}); | |
//Nested `each() ` | |
//__( curSelector) are generally used for exact each selector | |
__( curSelector ).find( 'ul > li' ).each( function( index ) { | |
__( this ).attr( 'id', 'li-id-' + index ); | |
}); | |
//Nested `eq()` | |
for (let k = 0; k<liNum; k++) { | |
__( curSelector + ' ul > li' ).eq(k).css({ | |
'font-size' : '18px' | |
}); | |
__( curSelector + ' ul > li:nth-child('+k+') > a' ).css({ | |
'font-size' : '18px' | |
}); | |
} | |
}); | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Animation demos | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
__( '.demo' ).fadeOut(1000, function(){ | |
setTimeout( function() { | |
__( '.demo' ).fadeIn(3000); | |
},1000 ); | |
}); | |
__( '.menu' ).animate( 'marginLeft', 0, 100, 'px', 1500, 'ease-out', function(){ console.log(this.className); } ); | |
__( '.menu' ).animate( 'marginTop', 0, 200, 'px', 1500, 'ease-out', function(){ console.log(this.className); } ); | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// AJAX demos | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
__.ajax({ | |
url: 'https://restcountries.com/v2/name/Argentina', | |
method: 'GET', | |
complete: function( data ) { | |
console.log( '=> ajax ok!' ); | |
console.log( data ); | |
} | |
}); | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Form demos | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
__( '#input-name-1' ).val( ); //form control: `<Input />` | |
__( '#select-name-1' ).val( 'value-3' ); //form control: `<Select />` | |
__( '#switch-name-1' ).val( true ); //form control: `<Switch />` | |
__( '#checkbox-name-1' ).val( true ); //form control: `<Checkbox />` | |
__('input[name="radio-name-1"]').val( 'value-3' ); //form control: `<Radio />` | |
console.log( '__( val(): ' + __( '#input' ).val()); | |
__( '#checkbox1' ).prop('checked', true); | |
console.log( '__( prop(): ' + __( '#checkbox2' ).prop( 'checked' )); | |
__( '#input' ).prop('disabled', true); | |
// To send data in the application/x-www-form-urlencoded format instead | |
const formData = new FormData(); | |
const defaultPostData = { | |
action : 'load_singlepages_ajax_content' | |
}; | |
for(let k in defaultPostData) { | |
formData.append(k, defaultPostData[k]); | |
} | |
// For multiple form fields data acquisition | |
const oldFormData = __( '#form' ).serializeArray(); | |
oldFormData.forEach(function(item){ | |
formData.append(item.name, item.value); | |
}); | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Utilities demos | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
console.log( __.isTouchCapable() ); //true or false | |
console.log( __.browser.isIE ); //.isMobile, .isAndroid, .isPC, .isSafari, .isIE, .supportsPassive | |
console.log( __.GUID.create() ); | |
console.log( __.math.evaluate( '100/3' ) ); | |
console.log( __.math.getRandomFloat(1, 10) ); | |
console.log( __.math.getDegree(135) ); | |
console.log( __.math.getRadian(1) ); | |
console.log( __.math.getPolarCoord(10,30,0) ); | |
console.log( __.cssProperty.getAbsoluteCoordinates( __( '.col-12' )[0] ).left ); | |
console.log( __.cssProperty.getTransitionDuration( __( '.col-12' )[0] ) ); | |
console.log( __.styleFormat( 'font-size: 10px;background: #51B801; color:#fff; border-radius: 5px;padding: 2px 3px;display: inline-block;margin-left: 3px;' ) ) | |
console.log( __.trim( 'string string spacing string' ) ); | |
console.log( __.lastUrlParamFormat( 'string-string-spacing_string' ) ); | |
console.log( __.removeFirstLastStr( ',string,string,string,' ) ); | |
console.log( __.toSlug( 'string String2-s' ) ); | |
console.log( __.htmlEncode( '<span style="color:red">text</span>' ) ); | |
console.log( __.htmlDecode( '<span style="color:red">text</span>' ) ); | |
console.log( __.validate.isMobile( '13167678787' ) ); //true | |
console.log( __.validate.isTel( '123-456-7890' ) ); //true | |
console.log( __.validate.isEmail( '[email protected]' ) ); //true | |
console.log( __.validate.isNumber( '1421.231' ) ); //true | |
console.log( __.validate.isInt( '1421.231' ) ); //false | |
console.log( __.validate.isJSON( '{"a":true}' ) ); //true | |
// Scroll spy | |
const myFunc = function(){ console.log('throttle'); } | |
const throttleFunc = __.throttle(myFunc, 300); | |
window.removeEventListener('scroll', throttleFunc); | |
window.removeEventListener('touchmove', throttleFunc); | |
window.addEventListener('scroll', throttleFunc); | |
window.addEventListener('touchmove', throttleFunc); | |
// Click spy | |
const myFunc = function(){ console.log('debounce'); } | |
const debounceFunc = __.debounce(myFunc, 300); | |
function handleClick() { | |
debounceFunc(); | |
} | |
// Window spy | |
const debounceFuncWindow = __.debounce(windowUpdate, 50); | |
window.removeEventListener('resize', debounceFuncWindow); | |
window.addEventListener('resize', debounceFuncWindow); | |
// Deep clone an element | |
let a = [1,2,3,4], b = __.deepClone(a); | |
let demo = document.querySelector( '#demo' ), demoCopy = __.deepClone(demo); | |
// Set a default configuration | |
function myFun(curElement, config) { | |
if ( typeof curElement === typeof undefined ) return; | |
config = __.setDefaultOptions({ | |
"src" : false, | |
"htmlID" : false, | |
"fixed" : false, | |
"ajax" : false | |
}, config); | |
console.log( config ); //{src: 'https://google.com', htmlID: true, fixed: false, ajax: false} | |
} | |
myFun( __( '.demo-trigger' ), { | |
src: 'https://google.com', | |
htmlID: true | |
}); | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
// Create a new method inherit from the constructor's prototype. | |
//+++++++++++++++++++++++++++++++++++++++++++ | |
__.fn.myPlugin = function( color ) { | |
this.each( function(index) { | |
this.style.background = color; | |
}); | |
return this; | |
}; | |
__('body').myPlugin( '#f00' ); | |
*/ | |
const __ = (function () { | |
'use strict'; | |
/** | |
* Create the constructor (Wrap the selector) | |
* @private | |
* | |
* @param {String|Element|Array} s - The selector to search for or HTML element to wrap with functionality | |
* @param {Element} root - OPTIONAL An HTML element to start the element query from | |
* @return {NodeList} - The collection of elements, wrapped with functionality (see API methods) | |
*/ | |
const Constructor = function (s, root) { | |
if ( typeof (s) === 'undefined' ) return; | |
// Backward compatibility, some methods need to use it | |
this.storeSelector = this; | |
// | |
this.elems = []; | |
root = root || document; | |
if ( Array.isArray(s) ) { | |
//is array | |
// eg. [li#demo1, li#demo2, li#demo3] | |
// [ [li#demo1, li#demo2, li#demo3] ] | |
//---------- | |
// There may be Nested array, the array needs to be flattened | |
s = [].concat(...s); | |
this.elems = s; | |
} else { | |
//not array | |
//---------- | |
if (typeof(s) === 'string') { | |
//1) string | |
this.elems = root.querySelectorAll(s); | |
} else if (s.tagName) { | |
//2) HTML elements | |
this.elems = [s]; | |
} else { | |
//3) window | |
if ( s === window ) this.elems = [window]; | |
//4) document or other | |
switch( s.nodeType ) { | |
case 9: | |
//if Document | |
this.elems = [document.body]; | |
break; | |
default: | |
} | |
} | |
} | |
}; | |
/** | |
* Instantiate a new constructor | |
* @private | |
*/ | |
const $$ = function (s, root) { | |
return new Constructor(s, root); | |
}; | |
/** | |
* Return a prototype that extending by adding new methods | |
* @private | |
*/ | |
$$.fn = Constructor.prototype; | |
/* ------------- Private Methods -------------- */ | |
/** | |
* Get real style data | |
* @private | |
*/ | |
function getStyle( el, attr ) { | |
const self = el; | |
if ( typeof (window) !== 'undefined' ) { | |
//document or window data | |
//----------------- | |
if ( self === document.body ) { | |
if ( attr == 'height' ) return document.body.clientHeight; | |
if ( attr == 'width' ) return document.body.clientWidth; | |
} | |
if ( self === window ) { | |
if ( attr == 'height' ) return window.innerHeight; | |
if ( attr == 'width' ) return window.innerWidth; | |
} | |
//element data | |
//----------------- | |
let _val = 0; | |
const computedStyle = window.getComputedStyle | |
? getComputedStyle(self) // Standards | |
: self.currentStyle; // Old IE | |
if (computedStyle) { // This will be true on nearly all browsers | |
_val = computedStyle[attr]; //return ??px | |
} | |
// if getComputedStyle return 'auto' | |
if ( attr === 'height' && _val === 'auto' ) return actualPropertyValue(self, 'height'); | |
if ( attr === 'width' && _val === 'auto' ) return actualPropertyValue(self, 'width'); | |
// | |
const newVal = /\d+/.exec( _val ); // Array ["123"] | |
return parseFloat( newVal ); | |
} else { | |
return 0; | |
} | |
} | |
/** | |
* Determine whether it is Child Node | |
* @private | |
*/ | |
function isChild( el, p ) { | |
if (!el || !p || !p.childNodes || !p.childNodes.length) { | |
return false; | |
} | |
return ([].slice.call(p.childNodes).filter(function(n) { | |
const found = (n === el); | |
if (!found && n.childNodes && n.childNodes.length) { | |
return isChild(el, n); | |
} | |
return found; | |
})).length; | |
} | |
/** | |
* Determine whether it is in JSON format | |
* @private | |
*/ | |
function isJSON( str ){ | |
if ( typeof(str) === 'string' && str.length > 0 ) { | |
if ( str.replace( /\"\"/g, '' ).replace( /\,/g, '' ) == '[{}]' ) { | |
return false; | |
} else { | |
if (/^[\],:{}\s]*$/.test( str.replace(/\\["\\\/bfnrtu]/g, '@' ). | |
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). | |
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { | |
return true; | |
}else{ | |
return false; | |
} | |
} | |
} else { | |
if ( | |
typeof(str) === 'object' && | |
Object.prototype.toString.call(str) === '[object Object]' && | |
! str.length | |
) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
} | |
/** | |
* Delete elements in the array | |
* @private | |
*/ | |
function removeArray(arr) { | |
let what, | |
args = arguments, | |
len = args.length, | |
index; | |
while (len > 1 && arr.length) { | |
what = args[--len]; | |
while ((index = arr.indexOf(what)) !== -1) { | |
arr.splice(index, 1); | |
} | |
} | |
return arr; | |
} | |
/** | |
* Convert string to camel case | |
* @private | |
*/ | |
function toCamelCase(s) { | |
if (typeof(s) === 'string') { | |
let firstWord=""; | |
let arr = s.split("-"); | |
for (let i = 1; i < arr.length; i++) | |
{ | |
firstWord = arr[i].charAt(0).toUpperCase(); | |
arr[i] = firstWord + arr[i].slice(1); | |
} | |
arr = arr.join(""); //Convert the arr array to a string | |
return arr; | |
} else { | |
return s; | |
} | |
} | |
/** | |
* Get CSS path from Dom element | |
* @private | |
*/ | |
function elementPath(el) { | |
if (!(el instanceof Element)) return; | |
// | |
const path = []; | |
let itemIndex = 0; | |
while (el.nodeType === Node.ELEMENT_NODE) { | |
const oldSelector = el.nodeName.toLowerCase(); | |
let selector = oldSelector; | |
if (el.id) { | |
if ( itemIndex > 0 ) selector += '#' + el.id; | |
} | |
if (el.className) { | |
selector += '.' + el.className.replace(/\s+/g, "."); | |
} | |
selector = selector.replace(/\.\./g, "."); | |
//Add one or more items to the start of an array's result set. | |
path.unshift(selector); | |
// | |
el = el.parentNode; | |
// | |
itemIndex++; | |
} | |
return path.join(" > "); | |
} | |
/** | |
* Check if a string is a valid number | |
* @private | |
*/ | |
function isValidNumeric(str) { | |
if (typeof str != "string") return false // we only process strings! | |
if ( | |
!isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this) | |
!isNaN(parseFloat(str)) // ensure strings of whitespace fail | |
) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/** | |
* Convert string to hump naming | |
* @private | |
*/ | |
function stringlineToHump(str) { | |
if ( typeof str === 'string' && str.length > 0 ) { | |
const re=/-(\w)/g; | |
str=str.replace(re,function($0,$1){ | |
return $1.toUpperCase(); | |
}); | |
return str; | |
} else { | |
return str; | |
} | |
} | |
/** | |
* Get the actual value with user specific methed | |
* it can be 'width', 'height', 'outerWidth', 'outerHeight' | |
* @private | |
* @param {Element} el - A DOM node containing one selector to match against. | |
* @param {String} prop - A string naming the property of style. | |
* @param {?Json} config - Whether or not margin is included. The key `includeMargin` | |
takes effect when set to true | |
* @return {Number} - Returns a pure number. | |
*/ | |
function actualPropertyValue(el, prop, config) { | |
const style = window.getComputedStyle ? window.getComputedStyle(el) : el.currentStyle, | |
display = style.display, | |
position = style.position, | |
visibility = style.visibility; | |
let marginWidth = 0; | |
let marginHeight = 0; | |
let maxVal; | |
let actualVal; | |
if ( config && config.includeMargin === true ) { | |
marginWidth = parseFloat(style.marginLeft) + parseFloat(style.marginRight); | |
marginHeight = parseFloat(style.marginTop) + parseFloat(style.marginBottom); | |
} | |
if ( prop === 'width' ) { | |
maxVal = parseFloat( style.maxWidth ); | |
// if its not hidden we just return normal height | |
if(display !== 'none' && maxVal !== '0') { | |
return el.clientWidth; | |
} | |
} | |
if ( prop === 'height' ) { | |
maxVal = parseFloat( style.maxHeight ); | |
if(display !== 'none' && maxVal !== '0') { | |
return el.clientHeight; | |
} | |
} | |
if ( prop === 'outerWidth' ) { | |
maxVal = parseFloat( style.maxWidth ); | |
if(display !== 'none' && maxVal !== '0') { | |
return el.offsetWidth + marginWidth; | |
} | |
} | |
if ( prop === 'outerHeight' ) { | |
maxVal = parseFloat( style.maxHeight ); | |
if(display !== 'none' && maxVal !== '0') { | |
return el.offsetHeight + marginHeight; | |
} | |
} | |
// the element is hidden so: | |
// making the el block so we can meassure its height but still be hidden | |
el.style.position = 'absolute'; | |
el.style.visibility = 'hidden'; | |
el.style.display = 'block'; | |
if ( prop === 'width' ) actualVal = el.clientWidth; | |
if ( prop === 'height' ) actualVal = el.clientHeight; | |
if ( prop === 'outerWidth' ) actualVal = el.offsetWidth + marginWidth; | |
if ( prop === 'outerHeight' ) actualVal = el.offsetHeight + marginHeight; | |
// reverting to the original values | |
el.style.display = display; | |
el.style.position = position; | |
el.style.visibility = visibility; | |
return actualVal; | |
} | |
/** | |
* Some easing functions | |
* @private | |
* @link: https://easings.net | |
* @param {Number} t - time (Amount of time that has passed since the beginning of the animation. Usually starts at 0 and is slowly increased using a game loop or other update function.) | |
* @param {Number} b - beginning value (The starting point of the animation. Usually it's a static value, you can start at 0 for example.) | |
* @param {Number} c - change in value (The amount of change needed to go from starting point to end point. It's also usually a static value.) | |
* @param {Number} d - duration (Amount of time the animation will take. Usually a static value aswell.) | |
* @return {Number} | |
*/ | |
function easeLinear (t, b, c, d) { | |
return c * t / d + b; | |
} | |
function easeInQuart (t, b, c, d) { | |
return c * (t /= d) * t * t * t + b; | |
} | |
function easeOutQuart (t, b, c, d) { | |
return -c * ((t = t / d - 1) * t * t * t - 1) + b; | |
} | |
function easeInOutQuart (t, b, c, d) { | |
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b; | |
return -c / 2 * ((t -= 2) * t * t * t - 2) + b; | |
} | |
function easeInCubic (t, b, c, d) { | |
return c * (t /= d) * t * t + b; | |
} | |
function easeOutCubic (t, b, c, d) { | |
return c * ((t = t / d - 1) * t * t + 1) + b; | |
} | |
function easeInOutCubic (t, b, c, d) { | |
if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; | |
return c / 2 * ((t -= 2) * t * t + 2) + b; | |
} | |
/* ------------- Independent Methods (public) -------------- */ | |
/** | |
* Object validation | |
* @public | |
* | |
* @return {Boolean} | |
*/ | |
$$.validate = $$.validate || ( () => { | |
function t() { } | |
return t.version = "0.0.1", | |
t.isNumber = function(B) { | |
const A = /^[\d|\.|,]+$/; | |
return A.test(B); | |
}, | |
t.isInt = function(B) { | |
if (B == "") { | |
return false; | |
} | |
const A = /\D+/; | |
return ! A.test(B); | |
}, | |
t.isEmail = function(A) { | |
const B = /^\s*([A-Za-z0-9_-]+(\.\w+)*@(\w+\.)+\w{2,3})\s*$/; | |
return B.test(A); | |
}, | |
t.isTel = function(A) { | |
//const B = /^[\d|\-|\s|\_]+$/; | |
const B = /^[0-9- ]{7,20}$/; | |
return B.test(A); | |
}, | |
t.isMobile = function(A) { | |
//const B = /^13[0-9]{9}|15[012356789][0-9]{8}|18[0256789][0-9]{8}|147[0-9]{8}$/; | |
const B = /^1[0-9]{10}$/; | |
return B.test(A); | |
}, | |
t.isJSON = function(A) { | |
return isJSON(A); | |
}, | |
// | |
t | |
})(); | |
/** | |
* To determine if it is a touch screen. | |
* @public | |
* | |
* @return {Boolean} | |
*/ | |
$$.isTouchCapable = function() { | |
return 'ontouchstart' in window || | |
window.DocumentTouch && document instanceof window.DocumentTouch || | |
window.navigator.maxTouchPoints > 0; | |
}; | |
/** | |
* Determine whether it is a special browser | |
* @public | |
* | |
* @return {Json} - Boolean judgment collection of common browsers | |
*/ | |
$$.browser = $$.browser || ( () => { | |
let browser = {}; | |
if ( typeof (navigator) !== 'undefined' ) { | |
// Test via a getter in the options object to see if the passive property is accessed | |
let supportsPassive = false; | |
try { | |
const opts = Object.defineProperty({}, 'passive', { | |
get: function() { | |
supportsPassive = true; | |
} | |
}); | |
window.addEventListener("testPassive", null, opts); | |
window.removeEventListener("testPassive", null, opts); | |
} catch (e) {} | |
browser = { | |
isMobile : /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), | |
isAndroid : /(android)/i.test(navigator.userAgent), | |
isPC : !navigator.userAgent.match(/(iPhone|iPod|Android|ios|Mobile)/i), | |
isSafari : !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/), /**Test to 9, 10. */ | |
isIE : !!window.ActiveXObject || "ActiveXObject" in window, /**Test to 6 ~ 11 (not edge) */ | |
supportsPassive : supportsPassive | |
}; | |
} | |
return browser; | |
})(); | |
/** | |
* Create GUID / UUID | |
* @public | |
* | |
* @description This function can be used separately in HTML pages or custom JavaScript. | |
* @return {String} - The globally-unique identifiers. | |
*/ | |
$$.GUID = $$.GUID || ( () => { | |
function t() { } | |
return t.version = "0.0.1", | |
/** | |
* Generate a string of unique characters | |
* | |
* @return {String} | |
*/ | |
t.create = function() { | |
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | |
const r = Math.random() * 16 | 0, | |
v = c == 'x' ? r : (r & 0x3 | 0x8); | |
return v.toString(16); | |
}); | |
}, | |
// | |
t | |
})(); | |
/** | |
* Evaluating a string as a mathematical expression in JavaScript | |
* @public | |
* | |
* @description This function can be used separately in HTML pages or custom JavaScript. | |
* @return {String} - New calculation result. | |
*/ | |
$$.math = $$.math || ( () => { | |
function t() { } | |
return t.version = "0.0.2", | |
/** | |
* Expression parsing and evaluation | |
* | |
* @param {String} s - A math expression given in string. | |
* @return {Number} - Returns a pure number calculated. | |
*/ | |
t.evaluate = function(s) { | |
const chars = s.replace(/\s/g, '').split(""); | |
let n = [], op = [], index = 0, oplast = true; | |
n[index] = ""; | |
// Parse the expression | |
for (let c = 0; c < chars.length; c++) { | |
if (isNaN(parseInt(chars[c])) && chars[c] !== "." && !oplast) { | |
op[index] = chars[c]; | |
index++; | |
n[index] = ""; | |
oplast = true; | |
} else { | |
n[index] += chars[c]; | |
oplast = false; | |
} | |
} | |
// Calculate the expression | |
s = parseFloat(n[0]); | |
for (let o = 0; o < op.length; o++) { | |
const num = parseFloat(n[o + 1]); | |
switch (op[o]) { | |
case "+": | |
s = s + num; | |
break; | |
case "-": | |
s = s - num; | |
break; | |
case "*": | |
s = s * num; | |
break; | |
case "/": | |
s = s / num; | |
break; | |
} | |
} | |
return s; | |
}, | |
/** | |
* Generate random number between two numbers | |
* | |
* @return {Number} min - Enter a expected minimum. | |
* @return {Number} max - Enter a expected maximum. | |
* @return {Number} - A new random number | |
*/ | |
t.getRandomFloat = function(min, max) { | |
return Math.random() * (max - min) + min; | |
}, | |
/** | |
* Returns the degree from radian. | |
* | |
* @return {Number} rad - Value of radian. | |
* @return {Number} | |
* @usage: | |
angle = rad / ( Math.PI / 180 ) = rad * ( 180/Math.PI ); | |
*/ | |
t.getDegree = function(rad) { | |
return rad / Math.PI * 180; | |
}, | |
/** | |
* Returns the radian degree . | |
* | |
* @return {Number} deg - Value of degree. | |
* @return {Number} | |
* @usage: | |
rad = Math.PI / 180 * 30 ; | |
*/ | |
t.getRadian = function(deg) { | |
return deg * Math.PI / 180; | |
}, | |
/** | |
* Convert three.js scene rotation to polar coordinates | |
* | |
* @return {Number} x - X coordinate value. | |
* @return {Number} y - Y coordinate value. | |
* @return {Number} z - Z coordinate value. | |
* @return {Json} | |
* @usage: | |
x = r * cos(θ) | |
y = r * sin(θ) | |
*/ | |
t.getPolarCoord = function(x, y, z) { | |
const nx = Math.cos(x) * Math.cos(y) * z, | |
nz = Math.cos(x) * Math.sin(y) * z, | |
ny = Math.sin(x) * z; | |
return { | |
x: nx, | |
y: ny, | |
z: nz | |
}; | |
}, | |
// | |
t | |
})(); | |
/** | |
* Get the CSS property | |
* @public | |
* | |
* @description This function can be used separately in HTML pages or custom JavaScript. | |
* @param {!Element} el - The Element for which to get the computed style. Using class name or ID to locate. | |
* @return {String|Object} - The value of property. | |
*/ | |
$$.cssProperty = $$.cssProperty || ( () => { | |
function t() { } | |
return t.version = "0.0.1", | |
/** | |
* Get the -webkit-transition-duration property | |
* | |
* @param {Element} el - A DOM node containing one selector to match against. | |
* @return {Number} - Returns a pure number. | |
*/ | |
t.getTransitionDuration = function( el ) { | |
if ( typeof el === typeof undefined ) { | |
return 0; | |
} | |
let style = window.getComputedStyle(el), | |
duration = style.webkitTransitionDuration, | |
delay = style.webkitTransitionDelay; | |
if ( typeof duration != typeof undefined ) { | |
// fix miliseconds vs seconds | |
duration = (duration.indexOf("ms")>-1) ? parseFloat(duration) : parseFloat(duration)*1000; | |
delay = (delay.indexOf("ms")>-1) ? parseFloat(delay) : parseFloat(delay)*1000; | |
return duration; | |
} else { | |
return 0; | |
} | |
}, | |
/** | |
* Get an object's absolute position on the page | |
* | |
* @param {Element} el - A DOM node containing one selector to match against. | |
* @return {Json} - An object containing the properties top and left. | |
*/ | |
t.getAbsoluteCoordinates = function( el ) { | |
let windowWidth = window.innerWidth, | |
leftPos = null, | |
topPos = null; | |
if ( ! document.getElementsByTagName( 'body' )[0].className.match(/rtl/) ) { | |
leftPos = ( el.offsetLeft == 0 ) ? el.parentElement.offsetLeft : el.offsetLeft; | |
topPos = ( el.offsetTop == 0 ) ? el.parentElement.offsetTop : el.offsetTop; | |
} else { | |
// width and height in pixels, including padding and border | |
// Corresponds to outerWidth(), outerHeight() | |
leftPos = ( el.offsetLeft == 0 ) ? ( windowWidth - ( el.parentElement.offsetLeft + el.parentElement.offsetWidth ) ) : ( windowWidth - ( el.offsetLeft + el.offsetWidth ) ); | |
topPos = ( el.offsetTop == 0 ) ? ( windowWidth - ( el.parentElement.offsetTop + el.parentElement.offsetHeight ) ) : ( windowWidth - ( el.offsetTop + el.offsetHeight ) ); | |
} | |
return { | |
'left': leftPos, | |
'top': topPos | |
}; | |
}, | |
// | |
t | |
})(); | |
/** | |
* Perform an asynchronous HTTP (Ajax) request. | |
* @public | |
* | |
* @param {Json} props - The attribute and value to be set, the format is JSON | |
* @return {Void} | |
*/ | |
$$.ajax = function(props) { | |
if ( ! isJSON(props) ) return false; | |
const self = this; | |
const _url = typeof(props.url) !== 'undefined' ? props.url : ''; | |
const _method = typeof(props.method) !== 'undefined' ? props.method : 'POST'; | |
const _loadedFn = typeof(props.complete) !== 'undefined' ? props.complete : null; | |
if ( _url != '' ) { | |
let config = { | |
mode: 'cors', | |
method: _method | |
}; | |
fetch(_url, config ) | |
.then(response => response.json()) | |
.then(result => { | |
//console.log('Success:', result); | |
if (_loadedFn && (typeof _loadedFn == 'function')) { | |
_loadedFn.call(self,result); | |
} | |
}) | |
.catch(error => { | |
console.error('Error:', error); | |
}); | |
} | |
}; | |
/** | |
* Remove all spaces in the string | |
* @public | |
* | |
* @param {String} s - Any string. | |
* @param {Boolean} isGlobal - If the value is `true`, remove all spaces including the middle | |
* @return {String} - A new string with no spaces | |
*/ | |
$$.trim = function(s, isGlobal = false) { | |
if (typeof(s) === 'string') { | |
let result; | |
result = s.replace(/(^\s+)|(\s+$)/g, ""); | |
if ( isGlobal === true ) { | |
result = result.replace(/\s/g, ""); | |
} | |
return result; | |
} else { | |
return s; | |
} | |
}; | |
/** | |
* Capitalize the first letter of all words in a string | |
* @public | |
* | |
* @param {String} s - Any string. | |
* @return {String} - A new string. | |
*/ | |
$$.lastUrlParamFormat = function(s) { | |
s = s || ''; | |
if ( s.length > 0 ) { | |
s = s.replace(/\-/g, ' ' ).replace(/\_/g, ' ' ); | |
const pieces = s.split(" "); | |
for ( let i = 0; i < pieces.length; i++ ) | |
{ | |
const j = pieces[i].charAt(0).toUpperCase(); | |
pieces[i] = j + pieces[i].substr(1); | |
} | |
return pieces.join(" "); | |
} else { | |
return s; | |
} | |
}; | |
/** | |
* Convert HTML Element's `Style` Attribute to JSON | |
* @public | |
* | |
* @param {String} str - The content of the style attribute in the HTML element, usually a string | |
* @return {Json} - An HTML element to a JSON object | |
*/ | |
$$.styleFormat = function(s) { | |
s = s || ''; | |
if ( s.length > 0 ) { | |
const styles = s.split(';'), | |
json = {}; | |
let i = styles.length, | |
style, | |
k, | |
v; | |
while (i--) { | |
style = styles[i].split(':'); | |
k = toCamelCase($$.trim(style[0])); | |
v = $$.trim(style[1]); | |
if (k.length > 0 && v.length > 0) | |
{ | |
json[k] = v; | |
} | |
} | |
return json; | |
} else { | |
return ''; | |
} | |
}; | |
/** | |
* Remove first, last or both symbols | |
* @public | |
* | |
* @param {String} str - Any string. | |
* @param {String} symbol - The target string to remove. | |
* @param {Number} type - Type of all or not. if `0`, is all. | |
* @return {String} - An new string. | |
*/ | |
$$.removeFirstLastStr = function( str, symbol = ',', type = 0 ) { | |
if (typeof(str) === 'string') { | |
if ( type == 0 ) { | |
const flRegExp = new RegExp( '^\\'+symbol+'|\\'+symbol+'$' , 'g' ); | |
return str.replace( flRegExp, '' ); | |
} else { | |
const flRegExp = new RegExp( ''+symbol+'\s*$' , 'g' ); | |
return str.replace( flRegExp, '' ); | |
} | |
} else { | |
return str; | |
} | |
}; | |
/** | |
* Convert a string to slug. | |
* @public | |
* | |
* @param {String} str - Any string. | |
* @return {String} - A new string. | |
*/ | |
$$.toSlug = function( str ) { | |
if ( typeof( str ) == 'string' && str.length > 0 ) { | |
return str | |
.toString() | |
.replace(/[^\w\s\-!¥【】\u4e00-\u9eff]/gi, '') | |
.replace(/\s/g, '-') | |
.replace(/(\-){2,}/g, '-') | |
.replace(/\-\s*$/, '' ) | |
.toLowerCase(); | |
} else { | |
return str; | |
} | |
}; | |
/** | |
* Throttle | |
* @public | |
* | |
* @param {Function} fn - A function to be executed within the time limit. | |
* @param {Number} limit - Waiting time. | |
* @return {Function} - Returns a new function. | |
*/ | |
$$.throttle = function( fn, limit = 300 ) { | |
let waiting = false; | |
return function () { | |
if (!waiting) { | |
fn.apply(this, arguments); | |
waiting = true; | |
setTimeout(function () { | |
waiting = false; | |
}, limit); | |
} | |
} | |
}; | |
/** | |
* Debounce | |
* @public | |
* | |
* @param {Function} fn - A function to be executed within the time limit. | |
* @param {Number} limit - Waiting time. | |
* @return {Function} - Returns a new function. | |
*/ | |
$$.debounce = function( fn, limit = 300 ) { | |
let timer; | |
return function() { | |
//Every time this returned function is called, the timer is cleared to ensure that fn is not executed | |
clearTimeout(timer); | |
// When the returned function is called for the last time (that is the user stops a continuous operation) | |
// Execute fn after another delay milliseconds | |
timer = setTimeout(function() { | |
fn.apply(this, arguments); | |
}, limit); | |
} | |
}; | |
/** | |
* Create a deep copy of the set of matched elements. | |
* @public | |
* | |
* @param {Object|Element} obj - The array, JSON or HTML element to be copied. | |
* @return {Object|Element} | |
*/ | |
$$.deepClone = function(obj) { | |
if ( obj.nodeType === 1 ) { | |
return obj.cloneNode(true); | |
} else { | |
let objClone = Array.isArray(obj)?[]:{}; | |
if(obj && typeof obj==="object"){ | |
for(key in obj){ | |
if(obj.hasOwnProperty(key)){ | |
//Determine whether the ojb child element is an object, if it is, copy it recursively | |
if(obj[key] && typeof obj[key] ==="object"){ | |
objClone[key] = deepClone(obj[key]); | |
}else{ | |
//If not, simply copy | |
objClone[key] = obj[key]; | |
} | |
} | |
} | |
} | |
return objClone; | |
} | |
}; | |
/** | |
* HTML entities encode. | |
* @public | |
* | |
* @param {string} str - Input text. | |
* @return {string} - Filtered text. | |
*/ | |
$$.htmlEncode = function(str) { | |
let res = ''; | |
if (typeof (document) === 'undefined') { | |
res = str.replace(/[\u00A0-\u9999<>\&]/g, function(i) { | |
return '&#'+i.charCodeAt(0)+';'; | |
}); | |
} else { | |
const div = document.createElement('div'); | |
//Creates a new Text node. This method can be used to escape HTML characters. | |
div.appendChild(document.createTextNode(str)); | |
res = div.innerHTML; | |
} | |
//Convert single and double quotes | |
res = res.replace(/"/g, '"').replace(/'/g, '''); | |
return res; | |
}; | |
/** | |
* HTML entities decode | |
* @public | |
* | |
* @param {string} str - Input text. | |
* @return {string} - Filtered text. | |
*/ | |
$$.htmlDecode = function(str) { | |
let res = ''; | |
if (typeof (document) === 'undefined') { | |
const entities = [ | |
['amp', '&'], | |
['apos', '\''], | |
['#x27', '\''], | |
['#x2F', '/'], | |
['#39', '\''], | |
['#47', '/'], | |
['lt', '<'], | |
['gt', '>'], | |
['nbsp', ' '], | |
['quot', '"'], | |
['#60', '<'], | |
['#62', '>'] | |
]; | |
for (let i = 0, max = entities.length; i < max; i++) { | |
str = str.replace(new RegExp('&'+entities[i][0]+';', 'g'), entities[i][1]); | |
} | |
res = str; | |
} else { | |
const txt = document.createElement('textarea'); | |
txt.innerHTML = str; | |
res = txt.value; | |
} | |
return res; | |
}; | |
/** | |
* Set a default JSON format configuration | |
* @public | |
* | |
* @param {Json} props - Set some default keys and values. | |
* @param {Json} options - A JSON variable passed in from outside, including key and value. | |
* @return {Json} - Merge the new and old values. | |
*/ | |
$$.setDefaultOptions = function(props, options) { | |
if ( typeof options === typeof undefined || options === null || options === false ) options = {}; | |
//Set a default configuration | |
if ( isJSON(props) ) { | |
const defaultConfigValues = Object.values(props); | |
Object.keys(props).forEach(function(prop,index) { | |
// Well-formed string type | |
Object.keys(options).forEach(function(prop2, index2) { | |
if ( prop2 === prop ) { | |
let _v = options[prop2]; | |
if ( _v == 'true' ) _v = true; | |
if ( _v == 'false' ) _v = false; | |
if ( isValidNumeric(_v) ) _v = parseFloat(_v); | |
if ( isJSON(_v) ) _v = Object.prototype.toString.call(_v) === '[object Object]' ? _v : JSON.parse(_v); | |
options[prop2] = _v; | |
} | |
}); | |
// | |
if ( typeof options[prop] === typeof undefined || options[prop] === null ) options[prop] = defaultConfigValues[index]; | |
}); | |
} | |
return options; | |
}; | |
/* ---------------- API methods ----------------- */ | |
/** | |
* Returns the length of the node | |
* | |
* @return {Number} | |
*/ | |
Constructor.prototype.len = function () { | |
let { elems } = this; | |
return elems.length; | |
}; | |
/** | |
* Returns a native DOM element | |
* | |
* @param {Number} index - A number for index. | |
* @return {Element} | |
*/ | |
Constructor.prototype.get = function (index) { | |
let { elems } = this; | |
if ( index === -1 ) { | |
//get all elements | |
return elems; | |
} else { | |
return elems[index]; | |
} | |
}; | |
/** | |
* Iterate over an object, executing a function for each matched element. | |
* | |
* @param {Function} fn - A function to execute for each matched element. | |
* @return {NodeList} | |
*/ | |
Constructor.prototype.each = function (fn) { | |
if (fn && (typeof fn == 'function')) { | |
let { elems } = this; | |
elems = Array.prototype.slice.call(elems); | |
elems.forEach(function (element, index, array) { | |
//If the ID does not exist, itemDomsStr cannot be obtained | |
// Very critical, other possible methods of manipulation dom will use id | |
if ( (element.id !== undefined && element.id.length === 0) || element.id === undefined ) { | |
element.id = 'eachitem-' + $$.GUID.create(); | |
} | |
//!import: The returned HTML element must be current, | |
//otherwise all HTML elements under document may be queried | |
const itemDomsStr = '#' + element.id; | |
fn.call(element, index, itemDomsStr, array); | |
}); | |
} | |
}; | |
/** | |
* Code included inside the code will run once the entire page (all DOM) is ready. | |
* | |
* @param {Function} fn - A function to execute after the DOM is ready. | |
* @return {Void} | |
*/ | |
Constructor.prototype.ready = function (fn) { | |
if (document.readyState != 'loading') { | |
fn(); | |
} else if (document.addEventListener) { | |
document.addEventListener('DOMContentLoaded', fn); | |
} else { | |
document.attachEvent('onreadystatechange', function () { | |
if (document.readyState != 'loading') fn(); | |
}); | |
} | |
}; | |
/** | |
* Detect when images and videos have been loaded. | |
* | |
* @param {Json} props - Contains three event functions before loading, loading, and loading completed. | |
* @return {Void} | |
*/ | |
Constructor.prototype.loader = function (props) { | |
this.each(function () { | |
const self = this; | |
const sources = []; | |
let loadingFn = null, | |
progressFn = null, | |
loadedFn = null, | |
imagesSelector = 'body img', | |
videosSelector = 'body video'; | |
if (isJSON(props)) { | |
loadingFn = props.startEvent; | |
progressFn = props.progressEvent; | |
loadedFn = props.endEvent; | |
imagesSelector = props.imagesSelector; | |
videosSelector = props.videosSelector; | |
} | |
//count all images on a page | |
if (typeof (document.images) !== 'undefined' && document.images.length == 0) { | |
const imgPlaceholder = document.createElement("div"); | |
imgPlaceholder.innerHTML = '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="" style="display:none">'; | |
// insert liLast at the end of <> | |
document.body.append(imgPlaceholder); | |
} | |
//loading | |
if (loadingFn && (typeof loadingFn == 'function')) { | |
loadingFn(); | |
} | |
//Push all images from page | |
const imgs = document.querySelectorAll(imagesSelector); | |
for (let i = 0; i < imgs.length; i++) { | |
sources.push( | |
{ | |
"url": imgs[i].src, | |
"type": 'img' | |
} | |
); | |
} | |
//Push all videos from page | |
const videos = document.querySelectorAll(videosSelector); | |
for (let i = 0; i < videos.length; i++) { | |
const _sources = videos[i].getElementsByTagName('source'); | |
sources.push( | |
{ | |
"url": _sources.length > 0 ? _sources[0].src : videos[i].src, | |
"type": 'video' | |
} | |
); | |
} | |
//Execute after all images and videos have loaded | |
let per = 0; | |
let perInit = 1; | |
if (sources.length == 0) { | |
per = 100; | |
} | |
const loadResources = function () { | |
let promises = []; | |
for (let i = 0; i < sources.length; i++) { | |
if (sources[i].type == 'img') { | |
/////////// | |
// IMAGE // | |
/////////// | |
promises.push( | |
new Promise(function (resolve, reject) { | |
const img = document.createElement('img'); | |
img.crossOrigin = 'anonymous'; | |
img.src = sources[i].url; | |
img.onload = function (image) { | |
//Compatible with safari and firefox | |
if (typeof image.path === typeof undefined) { | |
return resolve(image.target.currentSrc); | |
} else { | |
return resolve(image.path[0].currentSrc); | |
} | |
}; | |
}).then(textureLoaded) | |
); | |
} else { | |
/////////// | |
// VIDEO // | |
/////////// | |
promises.push( | |
new Promise(function (resolve, reject) { | |
const video = document.createElement('video'); | |
video.addEventListener('loadedmetadata', function (video) { | |
//Compatible with safari and firefox | |
if (typeof video.path === typeof undefined) { | |
return resolve(video.target.currentSrc); | |
} else { | |
return resolve(video.path[0].currentSrc); | |
} | |
}, false); | |
video.src = sources[i].url; | |
}).then(textureLoaded) | |
); | |
} | |
}//end for | |
return Promise.all(promises); | |
}; | |
const textureLoaded = function (url) { | |
//progress number | |
per = parseInt(100 * (perInit / sources.length)); | |
if (isNaN(per)) per = 100; | |
//Call back progress | |
/** console.log( 'progress: ' + per + '%' ); */ | |
if (progressFn && (typeof progressFn == 'function')) { | |
progressFn.call(self, per); | |
} | |
perInit++; | |
return per; | |
}; | |
//and videos loaded | |
//Must be placed behind the loadResources() | |
loadResources().then(function (images) { | |
if (loadedFn && (typeof loadedFn == 'function')) { | |
loadedFn(); | |
} | |
}); | |
}); | |
return this; | |
}; | |
/** | |
* Puts data inside an element at the last index (Vanilla JS also has this method) | |
* | |
* @param {Element|String} el - An element or string to be parsed as HTML or XML and inserted into the tree. | |
* @return {Void} | |
*/ | |
Constructor.prototype.append = function (el) { | |
this.each(function () { | |
if (typeof (el) === 'string') { | |
// Just inside the element, after its last child. | |
if (document.createElement("div").insertAdjacentHTML) { | |
this.insertAdjacentHTML("beforeend", el); | |
} | |
} else { | |
const html = (typeof (el) === 'string') ? el : el.outerHTML; | |
this.innerHTML += html; | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Puts the prepending element at the first index. (Vanilla JS also has this method) | |
* | |
* @param {Element|String} el - An element or string to be parsed as HTML or XML and inserted into the tree. | |
* @return {Void} | |
*/ | |
Constructor.prototype.prepend = function (el) { | |
this.each(function () { | |
if (typeof (el) === 'string') { | |
// Just inside the element, before its first child. | |
if (document.createElement("div").insertAdjacentHTML) { | |
this.insertAdjacentHTML("afterbegin", el); | |
} | |
} else { | |
const html = (typeof (el) === 'string') ? el : el.outerHTML; | |
this.innerHTML = html + this.innerHTML; | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Before the element itself. | |
* | |
* @param {String} el - The string to be parsed as HTML or XML and inserted into the tree. | |
* @return {Void} | |
*/ | |
Constructor.prototype.before = function (el) { | |
this.each(function () { | |
if (typeof (el) === 'string') { | |
// Before the element itself. | |
if (document.createElement("div").insertAdjacentHTML) { | |
this.insertAdjacentHTML("beforebegin", el); | |
} | |
} else { | |
const html = (typeof (el) === 'string') ? el : el.outerHTML; | |
this.insertAdjacentHTML("beforebegin", html); | |
} | |
}); | |
return this; | |
}; | |
/** | |
* After the element itself. | |
* | |
* @param {String} el - The string to be parsed as HTML or XML and inserted into the tree. | |
* @return {Void} | |
*/ | |
Constructor.prototype.after = function (el) { | |
this.each(function () { | |
if (typeof (el) === 'string') { | |
// After the element itself. | |
if (document.createElement("div").insertAdjacentHTML) { | |
this.insertAdjacentHTML("afterend", el); | |
} | |
} else { | |
const html = (typeof (el) === 'string') ? el : el.outerHTML; | |
this.insertAdjacentHTML("afterend", html); | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Insert an element as the first child node of another | |
* | |
* @param {Element} el - A DOM node containing one selector to match against. | |
*/ | |
Constructor.prototype.prependTo = function (el) { | |
this.each(function () { | |
if (this.firstChild) { | |
this.insertBefore(el, this.firstChild); | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Insert an element to the end of the target | |
* | |
* @param {Element} el - A DOM node containing one selector to match against. | |
*/ | |
Constructor.prototype.appendTo = function (el) { | |
this.each(function () { | |
this.appendChild(el); | |
}); | |
return this; | |
}; | |
/** | |
* Wrap an HTML structure around the content of each element in the set of matched elements. | |
* | |
* @param {String} el - An HTML snippet. | |
* @return {Void} | |
*/ | |
Constructor.prototype.wrapInner = function (el) { | |
this.each(function () { | |
//get old value | |
const val = this.innerHTML; | |
//empty default value | |
this.innerHTML = ''; | |
//The DOMParser() method is awesome, but the parseFromString() method stops at IE10. | |
const support = (function () { | |
if (!window.DOMParser) return false; | |
const parser = new DOMParser(); | |
try { | |
parser.parseFromString('x', 'text/html'); | |
} catch (err) { | |
return false; | |
} | |
return true; | |
})(); | |
//Convert a template string into HTML DOM nodes | |
const stringToHTML = function stringToHTML(str) { | |
// If DOMParser is supported, use it | |
if (support) { | |
const parser = new DOMParser(); | |
const doc = parser.parseFromString(str, 'text/html'); | |
const wrapperEl = doc.body; | |
return wrapperEl.children[0]; | |
} | |
// Otherwise, fallback to old-school method | |
const dom = document.createElement('div'); | |
dom.innerHTML = str; | |
const wrapperEl = dom; | |
return wrapperEl.children[0]; | |
}; | |
if (typeof el === 'string') { | |
const div = this.appendChild(stringToHTML(el)); | |
div.innerHTML = val; | |
while (this.firstChild !== div) { | |
div.appendChild(this.firstChild); | |
} | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Get the HTML contents of the first element in the set of matched elements or set the HTML contents of every matched element. | |
* | |
* @param {?String} el - A string of HTML to set as the content of each matched element. | |
* @return {Void|String} - The HTML content to set | |
*/ | |
Constructor.prototype.html = function (el) { | |
const rootObject = this; | |
let res = null; | |
this.each(function () { | |
if (el === undefined) { | |
res = this.innerHTML; | |
} else { | |
this.innerHTML = el; | |
res = rootObject; | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Get the combined text contents of each element in the set of matched elements, including their descendants, or set the text contents of the matched elements. | |
* | |
* @param {?String} el - The text to set as the content of each matched element. | |
* @return {Void|String} - The HTML content to set | |
*/ | |
Constructor.prototype.text = function (el) { | |
const rootObject = this; | |
let res = null; | |
this.each(function () { | |
if (el === undefined) { | |
//Remove HTML Tags | |
let htmlstr = this.innerHTML; | |
htmlstr = htmlstr.replace(/(<([^>]+)>)/ig, ''); | |
res = htmlstr; | |
} else { | |
this.innerHTML = $$.htmlEncode(el); | |
res = rootObject; | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Create a deep copy of the set of matched elements. | |
* | |
* @return {Array} - Contains only a collection of HTML elements. | |
* Returns a duplicate of the node on which this method was called. | |
*/ | |
Constructor.prototype.clone = function () { | |
let res = this; | |
this.each(function () { | |
res = this.cloneNode(true); | |
}); | |
return res; | |
}; | |
/** | |
* Adds the specified class(es) to each element in the set of matched elements. | |
* | |
* @param {String} c - One or more space-separated classes to be added to the class attribute of each matched element. | |
* @return {Void} | |
*/ | |
Constructor.prototype.addClass = function (c) { | |
this.each(function () { | |
if (! /^\S+$/g.test(c)) { | |
// It has only whitespace | |
const classArray = c.split(' '); | |
let className; | |
// Loop through the array of classes to add one class at a time | |
for (let j = 0; j < classArray.length; j++) { | |
className = classArray[j]; | |
this.classList.add(className); | |
} | |
} else { | |
this.classList.add(c); | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Remove a single class, multiple classes, or all classes from each element in the set of matched elements. | |
* | |
* @param {String} c - One or more space-separated classes to be removed from the class attribute of each matched element. | |
* @return {Void} | |
*/ | |
Constructor.prototype.removeClass = function (c) { | |
this.each(function () { | |
if (! /^\S+$/g.test(c)) { | |
// It has only whitespace | |
const classArray = c.split(' '); | |
let className; | |
// Loop through the array of classes to add one class at a time | |
for (let j = 0; j < classArray.length; j++) { | |
className = classArray[j]; | |
this.classList.remove(className); | |
} | |
} else { | |
this.classList.remove(c); | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Add or remove one or more classes from each element in the set of matched elements, | |
* depending on either the class's presence or the value of the state argument. | |
* | |
* @param {String} c - One or more classes (separated by spaces) to be toggled for each element in the matched set. | |
* @return {Void} | |
*/ | |
Constructor.prototype.toggleClass = function (c) { | |
this.each(function () { | |
if (! /^\S+$/g.test(c)) { | |
// It has only whitespace | |
const classArray = c.split(' '); | |
let className; | |
// Loop through the array of classes to add one class at a time | |
for (let j = 0; j < classArray.length; j++) { | |
className = classArray[j]; | |
if (this.classList.contains(className)) { | |
this.classList.remove(className); | |
} else { | |
this.classList.add(className); | |
} | |
} | |
} else { | |
if (this.classList.contains(c)) { | |
this.classList.remove(c); | |
} else { | |
this.classList.add(c); | |
} | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Set the style properties of elements: | |
* | |
* @param {Json} props - The attribute and value to be set, the format is JSON | |
* @return {Void} | |
*/ | |
Constructor.prototype.css = function (props, value) { | |
this.each(function () { | |
const self = this; | |
if (isJSON(props)) { | |
//the json is ok | |
Object.keys(props).forEach(function (prop) { | |
self.style[prop] = props[prop]; | |
}); | |
} else { | |
if (value !== undefined) { | |
self.style[props] = value; | |
} | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Get the descendants of each element in the current set of matched elements | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - The collection of elements | |
*/ | |
Constructor.prototype.find = function (s) { | |
let res = []; | |
this.each(function () { | |
// The symbol ">" is not allowed at the beginning of the find() method. | |
if (/(^\s*|,\s*)>/.test(s)) { | |
let removeId; | |
if (!this.id) { | |
this.id = 'ID_' + new Date().getTime(); | |
removeId = true; | |
} | |
s = s.replace(/(^\s*|,\s*)>/g, '$1#' + this.id + ' >'); | |
[].slice.call(document.querySelectorAll(s)).forEach(function(element){ | |
res.push(element); | |
}); | |
if (removeId) { | |
this.id = null; | |
} | |
} else { | |
[].slice.call(this.querySelectorAll(s)).forEach(function(element){ | |
res.push(element); | |
}); | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Reduce the set of matched elements to the one at the specified index. | |
* | |
* @param {Number} index - A number for index. | |
* @return {Array} - Contains only a collection of HTML elements. | |
*/ | |
Constructor.prototype.eq = function (index) { | |
let res = []; | |
let { elems } = this; | |
elems = Array.prototype.slice.call(elems); | |
elems.forEach(function (element, listIndex) { | |
if ( index === listIndex ) res.push(element); | |
}); | |
return $$(res); | |
}; | |
/** | |
* Returns the Element immediately prior to the specified one in its parent's children list, | |
* or null if the specified element is the first one in the list. | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - Contains only a collection of HTML elements. | |
*/ | |
Constructor.prototype.prev = function (s) { | |
let res = []; | |
this.each(function () { | |
const el = this.previousElementSibling; | |
if (s === undefined) { | |
if (el !== null) res.push(el); | |
} else { | |
if ( | |
el !== null && | |
//Determine whether the ID, class and HTML nodes match | |
(el.nodeName.toLowerCase() === s || el.classList.contains(s.replace(/\./g, '')) || '#' + el.id === s) | |
) { | |
res.push(el); | |
} | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Returns the element immediately following the specified one in its parent's children list, | |
* or null if the specified element is the last one in the list. | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - Contains only a collection of HTML elements. | |
*/ | |
Constructor.prototype.next = function (s) { | |
let res = []; | |
this.each(function () { | |
const el = this.nextElementSibling; | |
if (s === undefined) { | |
if (el !== null) res.push(el); | |
} else { | |
if ( | |
el !== null && | |
//Determine whether the ID, class and HTML nodes match | |
(el.nodeName.toLowerCase() === s || el.classList.contains(s.replace(/\./g, '')) || '#' + el.id === s) | |
) { | |
res.push(el); | |
} | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Returns the DOM node's parent Element, or null if the node either has no parent, | |
* or its parent isn't a DOM Element. | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - Contains only a collection of HTML elements. | |
*/ | |
Constructor.prototype.parent = function (s) { | |
let res = []; | |
this.each(function () { | |
const el = this.parentElement; | |
if (s === undefined) { | |
if (el !== null) res.push(el); | |
} else { | |
if ( | |
el !== null && | |
//Determine whether the ID, class and HTML nodes match | |
(el.nodeName.toLowerCase() === s || el.classList.contains(s.replace(/\./g, '')) || '#' + el.id === s) | |
) { | |
res.push(el); | |
} | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Get the ancestors of each element in the current set of matched elements. | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - Contains only a collection of HTML elements. | |
*/ | |
Constructor.prototype.parents = function (s) { | |
let res = []; | |
this.each(function () { | |
let parentSelector = document.querySelector(s); | |
// If no parentSelector defined will bubble up all the way to *document* | |
if (parentSelector === undefined) { | |
parentSelector = document; | |
} | |
let _parent = this.parentNode; | |
while (_parent !== parentSelector) { | |
const _currentNode = _parent; | |
//Determine whether the ID, class and HTML nodes match | |
if (s !== undefined) { | |
if ( | |
_currentNode.nodeName.toLowerCase() === s || | |
_currentNode.classList.contains(s.replace(/\./g, '')) || | |
'#' + _currentNode.id === s | |
) { | |
res.push(_currentNode); | |
} | |
} else { | |
res.push(_currentNode); | |
} | |
// | |
_parent = _currentNode.parentNode; | |
} | |
// Push that parentSelector you wanted to stop at | |
if (parentSelector !== null) res.push(parentSelector); | |
}); | |
return $$(res); | |
}; | |
/** | |
* Returns a live HTMLCollection which contains all of the child elements | |
* of the node upon which it was called. | |
* | |
* @param {Element} s - The selector that needs to be filtered. A DOMstring containing | |
* one selector to match against. | |
* @return {Array} - The collection of elements | |
*/ | |
Constructor.prototype.children = function (s) { | |
let res = []; | |
this.each(function () { | |
const self = this; | |
const childrenList = self.children; | |
if (childrenList) { | |
for (let i = 0; i < childrenList.length; i++) { | |
const _currentNode = childrenList[i]; | |
//Determine whether the ID, class and HTML nodes match | |
if (s !== undefined) { | |
if ( | |
_currentNode.nodeName.toLowerCase() === s || | |
_currentNode.classList.contains(s.replace(/\./g, '')) || | |
'#' + _currentNode.id === s | |
) { | |
res.push(_currentNode); | |
} | |
} else { | |
res.push(_currentNode); | |
} | |
} | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Reduce the set of matched elements to those that match the selector or pass the function's test. | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - The collection of elements | |
*/ | |
Constructor.prototype.filter = function (s) { | |
let res = []; | |
this.each(function () { | |
if (s !== undefined) { | |
if (this.classList.contains(s.replace(/\./g, ''))) { | |
res.push(this); | |
} | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Remove elements from the set of matched elements. | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - The collection of elements | |
*/ | |
Constructor.prototype.not = function (s) { | |
let res = []; | |
this.each(function () { | |
if (s !== undefined) { | |
if (!this.classList.contains(s.replace(/\./g, ''))) { | |
res.push(this); | |
} | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Get the siblings of each element in the set of matched elements | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - The collection of elements | |
*/ | |
Constructor.prototype.siblings = function (s) { | |
let res = []; | |
this.each(function () { | |
const self = this; | |
// if no parent, return no sibling | |
if ( self.parentNode ) { | |
// first child of the parent node | |
let sibling = self.parentNode.firstChild; | |
// collecting siblings | |
while (sibling) { | |
if (sibling.nodeType === 1 && sibling !== self) { | |
//Determine whether the ID, class and HTML nodes match | |
if (s !== undefined) { | |
if ( | |
sibling.nodeName.toLowerCase() === s || | |
sibling.classList.contains(s.replace(/\./g, '')) || | |
'#' + sibling.id === s | |
) { | |
res.push(sibling); | |
} | |
} else { | |
res.push(sibling); | |
} | |
} | |
// | |
sibling = sibling.nextSibling; | |
} | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Reduce the set of matched elements to the first in the set. | |
* | |
* @return {Array} - Contains only a collection of HTML elements. | |
*/ | |
Constructor.prototype.first = function () { | |
let res = []; | |
let { elems } = this; | |
elems = Array.prototype.slice.call(elems); | |
elems.forEach(function (element, listIndex) { | |
if ( 0 === listIndex ) res.push(element); | |
}); | |
return $$(res); | |
}; | |
/** | |
* Reduce the set of matched elements to the last in the set. | |
* | |
* @return {Array} - Contains only a collection of HTML elements. | |
*/ | |
Constructor.prototype.last = function () { | |
let res = []; | |
let { elems, storeSelector } = this; | |
let max = storeSelector.elems.length; | |
elems = Array.prototype.slice.call(elems); | |
elems.forEach(function (element, listIndex) { | |
if ( max-1 === listIndex ) res.push(element); | |
}); | |
return $$(res); | |
}; | |
/** | |
* Traverses the Element and its parents (heading toward the document root) | |
* until it finds a node that matches the provided selector string. | |
* | |
* @param {String} s - A string containing a selector expression to match elements against. | |
* @return {Array} - Contains only a collection of HTML elements. | |
*/ | |
Constructor.prototype.closest = function (s) { | |
let res = []; | |
this.each(function () { | |
//using recursivity | |
const el = this.closest(s); | |
//Must judge the result of closest() | |
if (el !== null) { | |
res.push(el); | |
} | |
}); | |
return $$(res); | |
}; | |
/** | |
* Set one or more attributes for the set of matched elements. | |
* | |
* @param {String} a - The name of the attribute to set. | |
* @param {String} v - A value to set for the attribute. | |
* @return {Void|String} - Get the value of an attribute for the first element in the set of matched elements. | |
*/ | |
Constructor.prototype.attr = function (a, v) { | |
const rootObject = this; | |
let res = null; | |
this.each(function () { | |
a = a || ''; | |
if (v === undefined) { | |
let curVal = this.getAttribute(a); | |
// Non-existent attributes | |
res = curVal === null ? null : curVal; | |
} else { | |
this.setAttribute(a, v); | |
res = rootObject; | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Store arbitrary data associated with the matched elements. | |
* | |
* @param {String} a - A string naming the piece of data to set | |
* @param {String} v - The new data value. | |
* @return {Void|String} - Return arbitrary data associated with the first element as set by data() or by an HTML5 data-* attribute. | |
*/ | |
Constructor.prototype.data = function (a, v) { | |
const rootObject = this; | |
let res = null; | |
this.each(function () { | |
a = a || ''; | |
const _s = stringlineToHump(a); | |
if (v === undefined) { | |
let curVal = this.dataset[_s]; | |
if (curVal == 'true') curVal = true; | |
if (curVal == 'false') curVal = false; | |
if (isValidNumeric(curVal)) curVal = parseFloat(curVal); | |
//check if Array or JSON format | |
if (isJSON(curVal)) { | |
if (Object.prototype.toString.call(curVal) === '[object Object]') { | |
curVal = curVal; | |
} else { | |
//If the curValult is an array, you need to determine whether it is the expected array | |
curVal = JSON.parse(curVal); | |
} | |
} | |
// Non-existent attributes | |
res = curVal === undefined ? null : curVal; | |
} else { | |
this.dataset[_s] = v; | |
res = rootObject; | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Set one or more attributes for the set of matched Form elements. | |
* | |
* @param {String} a - The name of the attribute to set. | |
* @param {String} v - A value to set for the attribute. | |
* @return {Void|String} - Get the value of an attribute for the first element in the set of matched elements. | |
*/ | |
Constructor.prototype.prop = function (a, v) { | |
const rootObject = this; | |
let res = null; | |
this.each(function () { | |
a = a || ''; | |
if (v === undefined) { | |
let curVal = this[a]; | |
if (curVal == 'true') curVal = true; | |
if (curVal == 'false') curVal = false; | |
if (isValidNumeric(curVal)) curVal = parseFloat(curVal); | |
//check if Array or JSON format | |
if (isJSON(curVal)) { | |
if (Object.prototype.toString.call(curVal) === '[object Object]') { | |
curVal = curVal; | |
} else { | |
//If the curValult is an array, you need to determine whether it is the expected array | |
curVal = JSON.parse(curVal); | |
} | |
} | |
// Non-existent attributes | |
res = curVal === undefined ? null : curVal; | |
} else { | |
this[a] = v; | |
res = rootObject; | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Remove an attribute from each element in the set of matched elements. | |
* | |
* @param {String} a - A string naming the piece of data to delete. | |
* @return {Void} | |
*/ | |
Constructor.prototype.removeAttr = function (a) { | |
this.each(function () { | |
a = a || ''; | |
this.removeAttribute(a); | |
}); | |
return this; | |
}; | |
/** | |
* Remove a previously-stored piece of data. | |
* | |
* @param {String} a - A string naming the piece of data to delete. | |
* @return {Void} | |
*/ | |
Constructor.prototype.removeData = function (a) { | |
this.each(function () { | |
a = a || ''; | |
const _s = stringlineToHump(a); | |
delete this.dataset[_s]; | |
}); | |
return this; | |
}; | |
/** | |
* Attach a handler to an event for the elements. The handler is executed at most once per element per event type. | |
* | |
* @param {String} eventType - One event types and optional namespaces, such as "click" | |
* @param {?String} selector - A selector string to filter the descendants of the selected elements that trigger the event. | |
* @param {Function} fn - A function to execute when the event is triggered. | |
* @return {Void} | |
*/ | |
Constructor.prototype.one = function (eventType, selector, fn) { | |
this.each(function () { | |
$$(this).on(eventType, selector, fn, true); | |
}); | |
return this; | |
}; | |
/** | |
* Attach an event handler function for one or more events to the selected elements. | |
* | |
* @param {String} eventType - One event types and optional namespaces, such as "click" | |
* @param {?String} selector - A selector string to filter the descendants of the selected elements | |
* that trigger the event. | |
* @param {Function} fn - A function to execute when the event is triggered. | |
* @param {Boolean} once - A boolean value indicating that the listener should be invoked at most | |
* once after being added. If true, the listener would be automatically | |
* removed when invoked. | |
* @return {Void} | |
*/ | |
Constructor.prototype.on = function (eventType, selector, fn, once) { | |
this.each(function () { | |
if (typeof (once) === 'undefined') once = false; | |
let _curFun = null; | |
if (typeof (fn) !== 'function') { | |
fn = selector; | |
selector = null; | |
} | |
if (!this.myListeners) { | |
this.myListeners = []; | |
}; | |
if (selector) { | |
//if string | |
_curFun = function (evt) { | |
[].slice.call(this.querySelectorAll(selector)).forEach(function (el) { | |
if (el === evt.target) { | |
fn.call(el, evt); | |
} else if (isChild(evt.target, el)) { | |
fn.call(el, evt); | |
} | |
}); | |
}; | |
this.myListeners.push({ | |
eType: eventType, | |
callBack: _curFun, | |
function: fn, | |
selector: selector | |
}); | |
} else { | |
//if HTML element | |
_curFun = function (evt) { | |
fn.call(this, evt); | |
}; | |
this.myListeners.push({ | |
eType: eventType, | |
callBack: _curFun, | |
function: fn, | |
selector: selector | |
}); | |
} | |
if (once) { | |
this.addEventListener(eventType, _curFun, { once: true }); | |
} else { | |
this.addEventListener(eventType, _curFun); | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Remove an event handler. | |
* | |
* @param {?String} eventType - One event types and optional namespaces, such as "click" | |
* @param {?String|Function} curSelector - A selector string or function to filter the descendants of the selected elements that trigger the event. | |
* @return {Void} | |
*/ | |
Constructor.prototype.off = function (eventType, curSelector) { | |
this.each(function () { | |
if (this.myListeners) { | |
for (let i = 0; i < this.myListeners.length; i++) { | |
if (typeof (curSelector) !== 'undefined') { | |
if (typeof curSelector === 'function') { | |
//is function | |
if (curSelector === this.myListeners[i].function) this.removeEventListener(this.myListeners[i].eType, this.myListeners[i].callBack); | |
} else { | |
//is string | |
if (curSelector === this.myListeners[i].selector) this.removeEventListener(this.myListeners[i].eType, this.myListeners[i].callBack); | |
} | |
} else { | |
this.removeEventListener(this.myListeners[i].eType, this.myListeners[i].callBack); | |
} | |
}; | |
delete this.myListeners; | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Get the current coordinates of the first element in the set of matched elements, relative to the document. | |
* | |
* @return {Json} - An object containing the properties top and left. | |
*/ | |
Constructor.prototype.offset = function () { | |
let res = { top: 0, left: 0 }; | |
this.each(function () { | |
const box = this.getBoundingClientRect(); | |
let top = 0, | |
left = 0; | |
//Include scrollbar and border | |
top = box.top + window.pageYOffset - document.documentElement.clientTop; | |
left = box.left + window.pageXOffset - document.documentElement.clientLeft; | |
res = { top: top, left: left }; | |
}); | |
return res; | |
}; | |
/** | |
* Get the current coordinates of the first element in the set of matched elements, relative to the offset parent. | |
* | |
* @return {Json} - An object containing the properties top and left. | |
*/ | |
Constructor.prototype.position = function () { | |
let res = { top: 0, left: 0 }; | |
this.each(function () { | |
let top = this.offsetTop ? this.offsetTop : 0, | |
left = this.offsetLeft ? this.offsetLeft : 0; | |
res = { top: top, left: left }; | |
}); | |
return res; | |
}; | |
/** | |
* Get the number of pixels that an element's content is scrolled vertically. | |
* | |
* @return {Number} - Returns a pure number calculated. | |
*/ | |
Constructor.prototype.scrollTop = function () { | |
let res = 0; | |
this.each(function () { | |
const supportPageOffset = window.pageXOffset !== undefined; | |
const isCSS1Compat = ((document.compatMode || "") === "CSS1Compat"); | |
const scrollTop = supportPageOffset ? window.pageYOffset : isCSS1Compat ? this.scrollTop : document.body.scrollTop; | |
res = scrollTop; | |
}); | |
return res; | |
}; | |
/** | |
* Get the number of pixels that an element's content is scrolled from its left edge. | |
* | |
@return {Number} - Returns a pure number calculated. | |
*/ | |
Constructor.prototype.scrollLeft = function (val) { | |
let res = 0; | |
this.each(function () { | |
const supportPageOffset = window.pageXOffset !== undefined; | |
const isCSS1Compat = ((document.compatMode || "") === "CSS1Compat"); | |
const scrollLeft = supportPageOffset ? window.pageXOffset : isCSS1Compat ? this.scrollLeft : document.body.scrollLeft; | |
res = scrollLeft; | |
}); | |
return res; | |
}; | |
/** | |
* Get or set the current computed width for elenments | |
* | |
* @param {?String|?Number} val - An integer representing the number of pixels, or | |
* an integer along with an optional unit of measure appended (as a string). | |
* @return {Void|Number} - Get the current computed width for the first element in the set of matched elements. | |
*/ | |
Constructor.prototype.width = function (val) { | |
const rootObject = this; | |
let res = 0; | |
this.each(function () { | |
const self = this; | |
if (typeof (val) !== 'undefined') { | |
self.style.width = !isNaN(val) ? val + 'px' : val; | |
res = rootObject; | |
} else { | |
res = getStyle(self, 'width'); | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Get or set the current computed height for elenments | |
* | |
* @param {?String|?Number} val - An integer representing the number of pixels, or | |
* an integer along with an optional unit of measure appended (as a string). | |
* @return {Void|Number} - Get the current computed height for the first element in the set of matched elements. | |
*/ | |
Constructor.prototype.height = function (val) { | |
const rootObject = this; | |
let res = 0; | |
this.each(function () { | |
const self = this; | |
if (typeof (val) !== 'undefined') { | |
self.style.height = !isNaN(val) ? val + 'px' : val; | |
res = rootObject; | |
} else { | |
res = getStyle(self, 'height'); | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Get or set the current computed outer width for elenments (including padding, border, and optionally margin) | |
* | |
* @param {Boolean} includeMargin - A Boolean indicating whether to include the element's margin in the calculation. | |
* @return {Number} - Returns the width of the element, including left and right padding, border, and optionally margin, in pixels. | |
*/ | |
Constructor.prototype.outerWidth = function (includeMargin) { | |
let res = 0; | |
this.each(function () { | |
const self = this; | |
const width_IncPaddingBorderScrollbar = self.offsetWidth; | |
const marginLeft = getStyle(self, 'marginLeft'); | |
const marginRight = getStyle(self, 'marginRight'); | |
const borderLeftWidth = getStyle(self, 'borderLeftWidth') || 0; | |
const borderRightWidth = getStyle(self, 'borderRightWidth') || 0; | |
let totalWidth = width_IncPaddingBorderScrollbar; | |
if (typeof (includeMargin) !== 'undefined') { | |
totalWidth = totalWidth + marginLeft + marginRight; | |
} | |
res = totalWidth; | |
}); | |
return res; | |
}; | |
/** | |
* Get or set the current computed outer height for elenments (including padding, border, and optionally margin) | |
* | |
* @param {Boolean} includeMargin - A Boolean indicating whether to include the element's margin in the calculation. | |
* @return {Number} - Returns the height of the element, including left and right padding, border, and optionally margin, in pixels. | |
*/ | |
Constructor.prototype.outerHeight = function (includeMargin) { | |
let res = 0; | |
this.each(function () { | |
const self = this; | |
const height_IncPaddingBorderScrollbar = self.offsetHeight; | |
const marginTop = getStyle(self, 'marginTop'); | |
const marginBottom = getStyle(self, 'marginBottom'); | |
const borderTopWidth = getStyle(self, 'borderTopWidth') || 0; | |
const borderBottomWidth = getStyle(self, 'borderBottomWidth') || 0; | |
let totalHeight = height_IncPaddingBorderScrollbar; | |
if (typeof (includeMargin) !== 'undefined') { | |
totalHeight = totalHeight + marginTop + marginBottom; | |
} | |
res = totalHeight; | |
}); | |
return res; | |
}; | |
/** | |
* Remove the set of matched elements from the DOM. | |
* | |
* @return {Void} | |
*/ | |
Constructor.prototype.remove = function () { | |
this.each(function () { | |
this.parentNode.removeChild(this); | |
}); | |
return this; | |
}; | |
/** | |
* Remove all child nodes of the set of matched elements from the DOM. | |
* | |
* @return {Void} | |
*/ | |
Constructor.prototype.empty = function () { | |
this.each(function () { | |
while (this.firstChild) { | |
this.removeChild(this.firstChild); | |
} | |
}); | |
return this; | |
}; | |
/** | |
* Traverse all the attribute names and values in an HTML element | |
* | |
* @return {Array} - A new array containing the properties name and value. | |
*/ | |
Constructor.prototype.allAttrs = function () { | |
let res = []; | |
this.each(function () { | |
const newArr = []; | |
Array.from(this.attributes) | |
.filter(obj => { | |
return obj.specified; | |
}) | |
.map(obj => { | |
newArr[obj.nodeName] = obj.textContent; | |
}); | |
res = newArr; | |
}); | |
return res; | |
}; | |
/** | |
* Determine whether any of the matched elements are assigned the given class. | |
* | |
* @param {String} v - The class name to search for. | |
* @return {Boolean} - Return true if the class is assigned to an element | |
*/ | |
Constructor.prototype.hasClass = function (v) { | |
let res = false; | |
this.each(function () { | |
res = this.classList.contains(v) ? true : false; | |
}); | |
return res; | |
}; | |
/** | |
* Get or set the current value of the first element in the set of matched elements. | |
* | |
* @param {?String|?Number|?Array} v - Corresponding to the value of each matched element. | |
* @return {String} - Get the values of form elements. | |
*/ | |
Constructor.prototype.val = function (v) { | |
const rootObject = this; | |
let res = null; | |
this.each(function () { | |
const self = this; | |
let controlType = ''; | |
if (this.tagName == "INPUT" || this.tagName == "TEXTARTA") { | |
//not `radio`, `checkbox` | |
if (this.type != 'checkbox' && this.type != 'radio') { | |
controlType = 'input-textarea'; | |
} | |
//`checkbox` | |
if (this.type == 'checkbox') { | |
controlType = 'checkbox'; | |
} | |
//`radio` | |
if (this.type == 'radio') { | |
controlType = 'radio'; | |
} | |
} | |
//`select` | |
if (this.tagName == "SELECT") { | |
controlType = 'select'; | |
} | |
//set value | |
if (typeof (v) !== 'undefined') { | |
switch (controlType) { | |
case "input-textarea": | |
this.value = v; | |
res = rootObject; | |
break; | |
case "checkbox": | |
this.checked = v; | |
res = rootObject; | |
break; | |
case "radio": | |
if (self.value == v.toString()) { | |
self.checked = true; | |
} | |
res = rootObject; | |
break; | |
case "select": | |
this.value = v; | |
this.dispatchEvent(new Event('change')); | |
res = rootObject; | |
break; | |
default: | |
this.value = v; | |
res = rootObject; | |
}//end switch | |
} else { | |
switch (controlType) { | |
case "input-textarea": | |
res = this.value; | |
break; | |
case "checkbox": | |
res = this.checked ? 1 : 0; | |
break; | |
case "radio": | |
if (self.checked) { | |
// do whatever you want with the checked radio | |
res = self.value; | |
} | |
break; | |
case "select": | |
res = this.value; | |
break; | |
default: | |
res = this.value; | |
}//end switch | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Display the matched elements. | |
* | |
* @return {Void} | |
*/ | |
Constructor.prototype.show = function () { | |
this.each(function () { | |
// use inherit so that your CSS controls block/flex/inline-block etc | |
this.style.display = 'inherit'; | |
}); | |
return this; | |
}; | |
/** | |
* Hide the matched elements. | |
* | |
* @return {Void} | |
*/ | |
Constructor.prototype.hide = function () { | |
this.each(function () { | |
this.style.display = 'none'; | |
}); | |
return this; | |
}; | |
/** | |
* Display the matched elements by fading them to opaque. | |
* | |
* @param {Number} speed - A string or number determining how long the animation will run. | |
* @param {Function} callback - A function to call once the animation is complete, called once per matched element. | |
* @return {Void} | |
*/ | |
Constructor.prototype.fadeIn = function (speed, callback) { | |
this.each(function () { | |
const elem = this; | |
let opacity = 0; | |
if (!elem.style.opacity) { | |
elem.style.opacity = 0; | |
} | |
elem.style.display = "inherit"; | |
const inInterval = setInterval(function () { | |
opacity += .02; | |
elem.style.opacity = opacity; | |
if (opacity >= 1) { | |
clearInterval(inInterval); | |
//do something after inInterval() | |
elem.style.removeProperty("opacity"); | |
if (callback && (typeof callback == 'function')) { | |
callback(); | |
} | |
} | |
}, speed / 50); | |
}); | |
return this; | |
}; | |
/** | |
* Hide the matched elements by fading them to transparent. | |
* | |
* @param {Number} speed - A string or number determining how long the animation will run. | |
* @param {Function} callback - A function to call once the animation is complete, called once per matched element. | |
* @return {Void} | |
*/ | |
Constructor.prototype.fadeOut = function (speed, callback) { | |
this.each(function () { | |
const elem = this; | |
let opacity = 1; | |
if (!elem.style.opacity) { | |
elem.style.opacity = 1; | |
} | |
const outInterval = setInterval(function () { | |
opacity -= .02; | |
elem.style.opacity = opacity; | |
if (opacity <= 0) { | |
clearInterval(outInterval); | |
//do something after outInterval() | |
elem.style.opacity = 0; | |
elem.style.display = "none"; //adding dispaly property and equall to none | |
if (callback && (typeof callback == 'function')) { | |
callback(); | |
} | |
} | |
}, speed / 50); | |
}); | |
return this; | |
}; | |
/** | |
* Serialize html form to JSON | |
* | |
* @param {Array} types - An array of field strings. | |
* @return {Array} - A collection of JSON arrays | |
*/ | |
Constructor.prototype.serializeArray = function (types) { | |
let res = []; | |
this.each(function () { | |
const form = this; | |
const objects = []; | |
if (typeof form == 'object' && form.nodeName.toLowerCase() == "form") { | |
const fieldsTypes = typeof (types) === 'undefined' ? ['input', 'textarea', 'select', 'checkbox', 'progress', 'datalist'] : types; | |
fieldsTypes.map((item, index) => { | |
const fields = form.getElementsByTagName(item); | |
for (let i = 0; i < fields.length; i++) { | |
const _name = fields[i].getAttribute("name"); | |
let _value = fields[i].value; | |
// if field is Array | |
if ( _name !== null && _name.match(/(\[.*?\])/gi) ) { | |
// foo[], foo[n] | |
const inputs = form.querySelectorAll("[name='"+_name+"']"); | |
const _arrFieldValue = []; | |
for (let j = 0; j < inputs.length; j++) { | |
const _arrField = inputs[j]; | |
//if checkbox or radio | |
if (_arrField.type === "radio" || _arrField.type === "checkbox") { | |
if (_arrField.checked === true) { | |
_arrFieldValue.push(_arrField.value); | |
} else { | |
_arrFieldValue.push(""); | |
} | |
} else { | |
_arrFieldValue.push(_arrField.value); | |
} | |
} | |
_value = _arrFieldValue; | |
} | |
//if checkbox or radio | |
if ( fields[i].type === 'radio' || fields[i].type === 'checkbox' ) { | |
if ( fields[i].checked === true ) { | |
objects[objects.length] = { | |
name: _name, | |
value: _value | |
}; | |
} | |
} else { | |
objects[objects.length] = { | |
name: _name, | |
value: _value | |
}; | |
} | |
} | |
}); | |
} | |
// remove Duplicate objects from JSON Array | |
const clean = objects.filter((item, index, self) => index === self.findIndex((t) => (t.name === item.name))); | |
res = clean; | |
}); | |
return res; | |
}; | |
/** | |
* Search for a given element from among the matched elements. | |
* | |
* @return {Number} - The return value is an integer indicating the position of the | |
* first element within the object relative to its sibling elements. | |
*/ | |
Constructor.prototype.index = function () { | |
let res = -1; | |
this.each(function () { | |
const self = this; | |
const children = self.parentNode.childNodes; | |
let num = 0; | |
for (let i = 0; i < children.length; i++) { | |
if (children[i] == self) res = num; | |
if (children[i].nodeType == 1) num++; | |
} | |
}); | |
return res; | |
}; | |
/** | |
* Bind an event in the HTML element | |
* | |
* @param {String} eventType - One event types and optional namespaces, such as "click" | |
* @return {Void} | |
*/ | |
Constructor.prototype.trigger = function (eventType) { | |
this.each(function () { | |
const fire = function (elem, type) { | |
// create and dispatch the event | |
const event = new CustomEvent(type, { | |
detail: { | |
hazcheeseburger: true | |
} | |
}); | |
elem.dispatchEvent(event); | |
}; | |
document.addEventListener("plop", function () { }, false); | |
fire(this, eventType); | |
}); | |
return this; | |
}; | |
/** | |
* Find the Tallest or widest of all elements | |
* | |
* @return {Json} - An object containing the properties width and height. | |
*/ | |
Constructor.prototype.maxDimension = function () { | |
const { storeSelector } = this; | |
let res = { | |
'height': 0, | |
'width': 0 | |
}; | |
let traverseIndex = 0; | |
let max = storeSelector.elems.length; | |
this.each(function () { | |
if (traverseIndex === max - 1) { | |
const elementHeights = Array.prototype.map.call(storeSelector.elems, function (el) { | |
return el.clientHeight; | |
}); | |
const elementWidths = Array.prototype.map.call(storeSelector.elems, function (el) { | |
return el.clientWidth; | |
}); | |
const maxHeight = Math.max.apply(null, elementHeights); | |
const maxWidth = Math.max.apply(null, elementWidths); | |
res = { | |
'height': maxHeight, | |
'width': maxWidth | |
}; | |
} | |
// Traverse the counter of a selector, reset to 0 when calling `__(selector).XXX()` | |
traverseIndex++; | |
}); | |
return res; | |
}; | |
/** | |
* Create a new Animation, applies it to the element, then plays the animation | |
* @param {String} prop - The style property to be set. | |
* @param {Number} from - The initial offset of the object | |
* @param {Number} to - The end offset of the object | |
* @param {String} unit - Unit string, such as `px` and `%`, and so on. | |
* @param {Number} duration - The number of milliseconds each iteration of the animation takes to complete | |
* @param {String} easing - The rate of the animation's change over time. Accepts the pre-defined values "linear", "ease-in", "ease-out", and "ease-in-out" | |
* @param {Function} complete - A function to call once the animation is complete, called once per matched element. | |
* @return {Void} | |
*/ | |
Constructor.prototype.animate = function (prop, from, to, unit, duration, easing, complete) { | |
this.each(function () { | |
const el = this; | |
const start = new Date().getTime(); | |
const timer = setInterval(function () { | |
const time = new Date().getTime() - start; | |
let val; | |
switch (easing) { | |
case "linear": | |
val = easeLinear(time, from, to - from, duration); | |
break; | |
case "ease-in": | |
val = easeInCubic(time, from, to - from, duration); | |
break; | |
case "ease-out": | |
val = easeOutCubic(time, from, to - from, duration); | |
break; | |
case "ease-in-out": | |
val = easeInOutCubic(time, from, to - from, duration); | |
break; | |
default: | |
val = easeLinear(time, from, to - from, duration); | |
} | |
// | |
const styleValue = val + unit; | |
el.style[prop] = styleValue; | |
if (time >= duration) { | |
clearInterval(timer); | |
// | |
if (complete && (typeof complete == 'function')) { | |
complete.call(el); | |
} | |
} | |
}, 1000 / 60); | |
}); | |
return this; | |
}; | |
/* ------------- Global -------------- */ | |
if ( typeof (window) !== 'undefined' ) { | |
window.__ = $$; | |
} | |
// | |
return $$; | |
})(); | |
//export default __; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment