/** * The following API is shared on both frontend and backend. * * No external libraries are loaded in this file. * * 1. Polyfills * 2. themeComplete plugin JS API * 3. themeComplete jQuery extensions */ /*jshint bitwise: false*/ /** * 1. Polyfills * * The following are required methods for * the String, Number and Object prototype */ ( function() { 'use strict'; /** * String.prototype.isNumeric() * * Determines whether a string contains a numeric value * * https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric */ if ( ! String.prototype.isNumeric ) { Object.defineProperty( String.prototype, 'isNumeric', { value: function() { return ! isNaN( parseFloat( this ) ) && isFinite( this ); } } ); } }() ); // jQuery Mask Plugin v1.14.15 // github.com/igorescobar/jQuery-Mask-Plugin window.jQuery.jMaskGlobals = { maskElements: '.tc-extra-product-options input' }; /** * 2. themeComplete plugin JS API */ ( function( $ ) { 'use strict'; $.epoAPI = {}; $.epoAPI.error = false; $.epoAPI.math = {}; $.epoAPI.dom = {}; $.epoAPI.util = {}; $.epoAPI.locale = {}; $.epoAPI.template = {}; $.epoAPI.math.toFloat = function( s, d ) { var n; if ( ! ( typeof s === 'string' || typeof s === 'number' ) || isNaN( s ) ) { return 0; } n = parseFloat( s ); if ( isNaN( n ) ) { if ( d !== undefined ) { return d; } return s; } return n; }; $.epoAPI.math.toInt = function( s, d ) { var n; if ( ! ( typeof s === 'string' || typeof s === 'number' ) || isNaN( s ) || s === '' ) { return 0; } n = parseInt( s, 10 ); if ( isNaN( n ) ) { if ( d !== undefined ) { return d; } return s; } return n; }; /** * Rounds a number * modified from http://locutus.io/php/math/round/ * * @param {any} value the value to round. * @param {int} precision the precision. * @param {string} mode the rounding mode. */ $.epoAPI.math.round = function( value, precision, mode ) { var m; var f; var isHalf; var n; var sgn; // helper variables // making sure precision is integer precision = parseInt( precision, 10 ); if ( ! Number.isFinite( precision ) ) { precision = 0; } m = Math.pow( 10, precision ); value = value * m; // sign of the number if ( typeof value === 'number' ) { if ( value ) { if ( value < 0 ) { sgn = -1; } else { sgn = 1; } } else { sgn = 0; } } else { sgn = 0; } isHalf = value % 1 === 0.5 * sgn; f = Math.floor( value ); if ( isHalf ) { switch ( mode ) { case 'PHP_ROUND_HALF_DOWN': // rounds .5 toward zero if ( sgn < 0 ) { n = 1; } else { n = 0; } value = f + n; break; case 'PHP_ROUND_HALF_EVEN': // rouds .5 towards the next even integer value = f + ( ( f % 2 ) * sgn ); break; case 'PHP_ROUND_HALF_ODD': // rounds .5 towards the next odd integer n = f % 2; if ( n ) { n = 0; } else { n = 1; } value = f + n; break; default: // rounds .5 away from zero if ( sgn > 0 ) { n = 1; } else { n = 0; } value = f + n; } } if ( ! isHalf ) { value = Math.round( value ); } return value / m; }; // https://locutus.io/php/misc/uniqid/index.html $.epoAPI.math.uniqueid = function( prefix, moreEntropy ) { var retId; var _formatSeed = function( seed, reqWidth ) { seed = parseInt( seed, 10 ).toString( 16 ); // to hex str if ( reqWidth < seed.length ) { // so long we split return seed.slice( seed.length - reqWidth ); } if ( reqWidth > seed.length ) { // so short we pad return new Array( 1 + ( reqWidth - seed.length ) ).join( '0' ) + seed; } return seed; }; var radom; if ( prefix === undefined ) { prefix = ''; } $.epoAPI.php = $.epoAPI.php || {}; if ( ! $.epoAPI.php.uniqidSeed ) { // init seed with big random int $.epoAPI.php.uniqidSeed = Math.floor( Math.random() * 0x75bcd15 ); } $.epoAPI.php.uniqidSeed += 1; // start with prefix, add current milliseconds hex string retId = prefix; retId += _formatSeed( parseInt( Date.now() / 1000, 10 ), 8 ); // add seed hex string retId += _formatSeed( $.epoAPI.php.uniqidSeed, 5 ); if ( moreEntropy ) { // for more entropy we add a float lower to 10 radom = Math.random() * 10; retId += radom.toFixed( 8 ).toString(); } return retId; }; $.epoAPI.dom.id = function( id ) { if ( typeof id === 'undefined' ) { return id; } if ( ! ( typeof id === 'string' || typeof id === 'number' ) || ( typeof id === 'number' && isNaN( id ) ) ) { return id.toString(); } return id.toString().replace( /(%|:|\.|\[|\]|,|=)/g, '\\$1' ); }; $.epoAPI.dom.scroll = function() { var scrollLeft; var scrollTop; if ( window.pageYOffset ) { scrollTop = window.pageYOffset; scrollLeft = window.pageXOffset; } else if ( document.documentElement && document.documentElement.scrollTop ) { scrollTop = document.documentElement.scrollTop; scrollLeft = document.documentElement.scrollLeft; } else if ( document.body ) { scrollTop = document.body.scrollTop; scrollLeft = document.body.scrollLeft; } return { left: scrollLeft, top: scrollTop }; }; $.epoAPI.dom.size = function() { var totalDocumentHeight; var totalDocumentWidth; var fullHeight; var fullWidth; var visibleWidth; var visibleHeight; if ( window.innerHeight && window.scrollMaxY ) { totalDocumentWidth = window.innerWidth + window.scrollMaxX; totalDocumentHeight = window.innerHeight + window.scrollMaxY; } else if ( document.body.scrollHeight > document.body.offsetHeight ) { totalDocumentWidth = document.body.scrollWidth; totalDocumentHeight = document.body.scrollHeight; } else { totalDocumentWidth = document.body.offsetWidth; totalDocumentHeight = document.body.offsetHeight; } if ( window.innerHeight ) { if ( document.documentElement.clientWidth ) { visibleWidth = document.documentElement.clientWidth; } else { visibleWidth = window.innerWidth; } visibleHeight = window.innerHeight; } else if ( document.documentElement && document.documentElement.clientHeight ) { visibleWidth = document.documentElement.clientWidth; visibleHeight = document.documentElement.clientHeight; } else if ( document.body ) { visibleWidth = document.body.clientWidth; visibleHeight = document.body.clientHeight; } if ( totalDocumentHeight < visibleHeight ) { fullHeight = visibleHeight; } else { fullHeight = totalDocumentHeight; } if ( totalDocumentWidth < visibleWidth ) { fullWidth = visibleWidth; } else { fullWidth = totalDocumentWidth; } return { fullWidth: fullWidth, fullHeight: fullHeight, visibleWidth: visibleWidth, visibleHeight: visibleHeight, totalWidth: totalDocumentWidth, totalHeight: totalDocumentHeight }; }; // jQuery trim is deprecated, provide a trim method based on String.prototype.trim $.epoAPI.util.trim = function( str ) { if ( typeof str === 'string' ) { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim#Polyfill return str.replace( /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '' ); } return str; }; $.epoAPI.util.parseJSON = function( s ) { var parsedjson; var JSON = window.JSON; try { parsedjson = JSON.parse( s + '' ); if ( parsedjson && typeof parsedjson === 'object' && parsedjson !== null ) { return parsedjson; } } catch ( err ) { $.epoAPI.error = err; return false; } return false; }; $.epoAPI.util.decodeHTML = function( html ) { var txt = document.createElement( 'textarea' ); txt.innerHTML = html; return txt.value; }; $.epoAPI.util.getStorage = function( type ) { var storage; var x; try { storage = window[ type ]; x = '__storage_test__'; storage.setItem( x, x ); storage.removeItem( x ); return storage; } catch ( e ) { return false; } }; $.epoAPI.util.basename = function( path ) { return path.replace( /.*\//, '' ); }; // https://developer.mozilla.org/en-US/docs/Web/API/structuredClone // https://medium.com/javascript-in-plain-english/how-to-deep-copy-objects-and-arrays-in-javascript-7c911359b089 $.epoAPI.util.deepCopyArray = function( inObject ) { var outObject; var value; if ( window.structuredClone !== undefined ) { return window.structuredClone( inObject ); } if ( typeof inObject !== 'object' || inObject === null ) { return inObject; // Return the value if inObject is not an object } // Create an array or object to hold the values outObject = Array.isArray( inObject ) ? [] : {}; Object.keys( inObject ).forEach( function( key ) { if ( inObject ) { value = inObject[ key ]; // Recursively (deep) copy for nested objects, including arrays outObject[ key ] = typeof value === 'object' && value !== null ? $.epoAPI.util.deepCopyArray( value ) : value; } } ); return outObject; }; $.epoAPI.locale.getSystemDecimalSeparator = function() { var n = 1.1; // This gets null on languages that don't return 1 in a number format. n = /^1(.+)1$/.exec( n.toLocaleString() ); if ( n ) { n = n[ 1 ]; } else { n = ','; } return n; }; $.epoAPI.template.html = function( template, obj ) { var $template_html = template( obj ); $template_html = $template_html.replace( '/**/', '' ); return $template_html; }; $.epoAPI.filters = {}; $.epoAPI.addFilter = function( $tag, $function_to_add, $priority, $accepted_args ) { var $idx; $priority = parseInt( $priority, 10 ); if ( isNaN( $priority ) ) { $priority = 10; } $accepted_args = parseInt( $accepted_args, 10 ); if ( isNaN( $accepted_args ) ) { $accepted_args = 1; } $idx = $function_to_add + '_' + $priority; if ( ! $.epoAPI.filters[ $tag ] ) { $.epoAPI.filters[ $tag ] = {}; } if ( ! $.epoAPI.filters[ $tag ][ $priority ] ) { $.epoAPI.filters[ $tag ][ $priority ] = {}; } $.epoAPI.filters[ $tag ][ $priority ][ $idx ] = { func: $function_to_add, accepted_args: $accepted_args }; return true; }; $.epoAPI.removeFilter = function( $tag, $function_to_remove, $priority ) { var $idx; $priority = parseInt( $priority, 10 ); if ( isNaN( $priority ) ) { $priority = 10; } $idx = $function_to_remove + '_' + $priority; if ( $.epoAPI.filters[ $tag ] && $.epoAPI.filters[ $tag ][ $priority ] && $.epoAPI.filters[ $tag ][ $priority ][ $idx ] ) { delete $.epoAPI.filters[ $tag ][ $priority ][ $idx ]; return true; } return false; }; $.epoAPI.applyFilter = function( $tag, $value ) { var $args = $.makeArray( arguments ); var priorities; $args.splice( 0, 1 ); if ( ! $.epoAPI.filters[ $tag ] ) { return $value; } priorities = $.epoAPI.filters[ $tag ]; $.each( priorities, function( i, el ) { $.each( el, function( i2, el2 ) { var func = el2.func; if ( func instanceof Function ) { $value = func.apply( null, $args ); } else if ( window[ func ] && window[ func ] instanceof Function ) { $value = window[ func ].apply( null, $args ); } } ); } ); return $value; }; // Backwards compatibility (to be removed in the future) $.tc_add_filter = $.epoAPI.addFilter; $.tc_remove_filter = $.epoAPI.removeFilter; $.tc_apply_filters = $.epoAPI.applyFilter; }( window.jQuery ) ); /* * Formatting functions * * Code modifiled from accounting.js * http://openexchangerates.github.io/accounting.js/ */ ( function( $ ) { 'use strict'; /** * Check and normalise the value of precision (must be positive integer) * * @param {any} val the value to check. * @param {any} base the value to return is check fails. */ function checkPrecision( val, base ) { val = Math.round( Math.abs( val ) ); return isNaN( val ) ? base : val; } /** * Takes a string/array of strings, removes all formatting/cruft * and returns the raw float value. * * @param {any} value the value to unformat. * @param {any} decimal the decimal point. */ function unformat( value, decimal ) { var regex; var unformatted; // Recursively unformat arrays: if ( Array.isArray( value ) ) { return value.map( value, function( val ) { return unformat( val, decimal ); } ); } // Fails silently (need decent errors): value = value || 0; // Return the value as-is if it's already a number: if ( typeof value === 'number' ) { return value; } // Default decimal point comes from settings, but could be set to eg. "," in opts: decimal = decimal || '.'; // Build regex to strip out everything except digits, decimal point and minus sign: regex = new RegExp( '[^0-9-' + decimal + ']', [ 'g' ] ); unformatted = parseFloat( ( '' + value ) .replace( regex, '' ) // strip out any cruft .replace( decimal, '.' ) // make sure decimal point is standard ); // This will fail silently which may cause trouble, let's wait and see: return ! isNaN( unformatted ) ? unformatted : 0; } /** * Implementation of toFixed() that treats floats more like decimals * * Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61") that present * problems for accounting- and finance-related software. * * @param {any} value the value to convert. * @param {int} precision the precision. */ function toFixed( value, precision ) { var exponentialForm; var rounded; var finalResult; if ( ! Number.isFinite( value ) ) { return '-'; } precision = checkPrecision( precision, 2 ); exponentialForm = Number( unformat( value ) * Math.pow( 10, precision ) ); rounded = Math.round( exponentialForm ); finalResult = Number( rounded / Math.pow( 10, precision ) ).toFixed( precision ); return finalResult; } /** * Format into currency or a number, with comma-separated thousands and custom precision/decimal places * * opts = { * symbol: "$", // currency symbol is '$' * format: "%s%v", // controls output: %s = symbol, %v = value (can be object, see docs) * decimal: ".", // decimal point separator * thousand: ",", // thousands separator * precision: 2, // decimal places * } * * @param {any} number the value to convert. * @param {Object} opts the options for formatting. */ function format( number, opts ) { var formats; var useFormat; var negative; var base; var mod; // Resursively format arrays: if ( Array.isArray( number ) ) { return number.map( number, function( val ) { return format( val, opts ); } ); } // Clean up number: number = unformat( number ); // Do not format if opts are not correct or missing if ( ! opts ) { return number; } // Clean up precision opts.precision = checkPrecision( opts.precision ); // Format currency if ( opts.format && opts.symbol ) { // Check format (returns object with pos, neg and zero): formats = { pos: opts.format, neg: opts.format.replace( '-', '' ).replace( '%v', '-%v' ), zero: opts.format }; // Choose which format to use for this value: useFormat = number > 0 ? formats.pos : number < 0 ? formats.neg : formats.zero; // Return with currency symbol added: opts.opts = { precision: opts.precision, thousand: opts.thousand, decimal: opts.decimal }; number = useFormat.replace( '%s', opts.symbol ).replace( '%v', format( Math.abs( number ), opts.opts ) ); // Format number } else { if ( ! Number.isFinite( number ) ) { return '-'; } // Do some calc: negative = number < 0 ? '-' : ''; base = parseInt( toFixed( Math.abs( number || 0 ), opts.precision ), 10 ) + ''; mod = base.length > 3 ? base.length % 3 : 0; // Format the number: number = negative + ( mod ? base.substring( 0, mod ) + opts.thousand : '' ) + base.substring( mod ).replace( /(\d{3})(?=\d)/g, '$1' + opts.thousand ) + ( opts.precision ? opts.decimal + toFixed( Math.abs( number ), opts.precision ).split( '.' )[ 1 ] : '' ); } return number; } $.epoAPI.math.unformat = unformat; $.epoAPI.math.format = format; $.epoAPI.math.toFixed = toFixed; }( window.jQuery ) ); /** * 3. themeComplete jQuery extensions * * @param {Object} $ The jQuery object. */ ( function( $ ) { 'use strict'; var expo; var rCRLF = /\r?\n/g; var rcheckableType = /^(?:checkbox|radio)$/i; var rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i; var rsubmittable = /^(?:input|select|textarea|keygen)/i; // Extend jQuery easing if ( ! $.easing.easeInExpo ) { expo = function( p ) { return Math.pow( p, 6 ); }; $.easing.easeInExpo = expo; $.easing.easeOutExpo = function( p ) { return 1 - expo( 1 - p ); }; $.easing.easeInOutExpo = function( p ) { return p < 0.5 ? expo( p * 2 ) / 2 : 1 - ( expo( ( p * -2 ) + 2 ) / 2 ); }; } // Add jQuery plugins $.fn.extend( { // Create checkbox switch tmcheckboxes: function() { var tm_settings_wrap_checkbox = this.find( ":checkbox:not('.tm-default-checkbox')" ).not( '.wp-tab-panel :checkbox, .tm-weekdays-picker-wrap :checkbox, .tm-months-picker-wrap :checkbox' ); tm_settings_wrap_checkbox.wrap( '' ); tm_settings_wrap_checkbox.wrap( '' ); tm_settings_wrap_checkbox.after( '' ); return this; }, // Encode a set of HTML elements as an array of names and values. // Elements do not need to be inside a form tcSerializeArray: function() { return this.find( ':input' ) .filter( function() { var type = this.type; // Use .is( ":disabled" ) so that fieldset[disabled] works return this.name && ! $( this ).is( ':disabled' ) && rsubmittable.test( this.nodeName ) && ! rsubmitterTypes.test( type ) && ( this.checked || ! rcheckableType.test( type ) ); } ) .map( function( i, elem ) { var val = $( this ).val(); if ( val === null ) { return null; } if ( Array.isArray( val ) ) { return $.map( val, function( thisval ) { return { name: elem.name, value: thisval.replace( rCRLF, '\r\n' ) }; } ); } return { name: elem.name, value: val.replace( rCRLF, '\r\n' ) }; } ) .get(); }, // convert element to a valid JSON object tcSerializeObject: function() { var o = {}; var a = this.tcSerializeArray(); $.each( a, function() { if ( o[ this.name ] !== undefined ) { if ( this.name.endsWith( '[]' ) ) { if ( ! o[ this.name ].push ) { o[ this.name ] = [ o[ this.name ] ]; } o[ this.name ].push( this.value || '' ); } else { o[ this.name ] = this.value || ''; } } else { o[ this.name ] = this.value || ''; } } ); return o; }, // Scroll container to a specific element tcScrollTo: function( obj, duration, offset ) { var element = this; obj = $( obj ); if ( obj.length === 0 ) { return this; } if ( ! duration ) { duration = 0; } if ( ! offset ) { offset = 0; } if ( element[ 0 ].self === window ) { element = $( 'html, body' ); } else { if ( element.find( '.woodmart-scroll-content' ).length ) { element = element.find( '.woodmart-scroll-content' ); } if ( ! element.offset() ) { element = $( 'html, body' ); } else { offset = offset + ( element.scrollTop() - element.offset().top ); } } return element.animate( { scrollTop: $( obj ).offset().top + offset }, duration ); }, /** * Textarea and select clone() bug workaround | Spencer Tipping * Licensed under the terms of the MIT source code license * https://github.com/spencertipping/jquery.fix.clone/blob/master/jquery.fix.clone.js */ tcClone: function() { var i; var l; var j; var m; var result = $.fn.clone.apply( this, arguments ); var textareas = this.find( 'textarea' ).add( this.filter( 'textarea' ) ); var resultTextareas = result.find( 'textarea' ).add( result.filter( 'textarea' ) ); var selects = this.find( 'select' ).add( this.filter( 'select' ) ); var resultSelects = result.find( 'select' ).add( result.filter( 'select' ) ); for ( i = 0, l = textareas.length; i < l; i += 1 ) { $( resultTextareas[ i ] ).val( $( textareas[ i ] ).val() ); } for ( i = 0, l = selects.length; i < l; i += 1 ) { for ( j = 0, m = selects[ i ].options.length; j < m; j += 1 ) { if ( selects[ i ].options[ j ].selected === true ) { resultSelects[ i ].options[ j ].selected = true; } } } return result; } } ); }( window.jQuery ) );