{"version":3,"file":"js/chunks/util.js","sources":["webpack:///./org_colony/cartridge/js/util.js"],"sourcesContent":["const forEach = require('lodash.foreach');\nconst isString = require('lodash.isstring');\n\nconst smallBreakpoint = 320;\nconst mediumBreakpoint = 480;\nconst largeBreakpoint = 768;\nconst desktopBreakpoint = 1025;\nconst maxBreakpoint = 1280;\nconst mobileMenuBreakpoint = desktopBreakpoint;\n\nconst util = {\n    /**\n     * @desc Media breakpoints that are used throughout the Javascript\n     */\n    breakpoints: {\n        xs: smallBreakpoint,\n        sm: mediumBreakpoint,\n        md: largeBreakpoint,\n        lg: desktopBreakpoint,\n        xl: maxBreakpoint,\n        'mobile-menu': mobileMenuBreakpoint,\n    },\n\n    /**\n     * @function\n     * @description Returns either an object with all of the available breakpoints or a specific viewport based on the given size\n     * @param {string} size The viewport to return\n     * @param {string} breakpoints A custom breakpoints object\n     */\n    getViewports(size, breakpoints) {\n        const bps = typeof breakpoints !== 'undefined' ? breakpoints : this.breakpoints;\n\n        if (typeof size !== 'undefined') {\n            if (bps[size]) {\n                return bps[size];\n            }\n            window.console.error('Unexpected viewport size given in util.getViewports');\n            throw new Error('Unexpected viewport size given in util.getViewports');\n        } else {\n            return breakpoints;\n        }\n    },\n\n    /**\n     * @function\n     * @description Returns the current viewport name (ex: 'medium') or 'max' if the current window is larger than any defined viewport width\n     */\n    getCurrentViewport() {\n        const w = window.innerWidth;\n        const viewports = util.getViewports();\n        let viewportName = 'max';\n        // traverse the object from small up to desktop, and return the first match\n        forEach(viewports, (value, name) => {\n            if (w <= value) {\n                viewportName = name;\n                return false;\n            }\n            return true;\n        });\n        return viewportName;\n    },\n\n    /**\n     * @function\n     * @description appends the parameter with the given name and value to the given url and returns the changed url\n     * @param {String} url the url to which the parameter will be added\n     * @param {String} name the name of the parameter\n     * @param {String} value the value of the parameter\n     */\n    appendParamToURL(url, name, value) {\n        // quit if the param already exists\n        if (url.indexOf(`${name}=`) !== -1) {\n            return url;\n        }\n        const separator = url.indexOf('?') !== -1 ? '&' : '?';\n        return `${url + separator + name}=${encodeURIComponent(value)}`;\n    },\n\n    /**\n     * @function\n     * @description remove the parameter and its value from the given url and returns the changed url\n     * @param {String} url the url from which the parameter will be removed\n     * @param {String} name the name of parameter that will be removed from url\n     */\n    removeParamFromURL(url, name) {\n        if (url.indexOf('?') === -1 || url.indexOf(`${name}=`) === -1) {\n            return url;\n        }\n        const [domain, query] = url.split('?');\n        const newParams = [];\n        let paramUrl = query;\n        let hash = null;\n\n        // if there is a hash at the end, store the hash\n        if (query.indexOf('#') > -1) {\n            [paramUrl, hash] = query.split('#');\n        }\n        const params = paramUrl.split('&');\n        for (let i = 0; i < params.length; i += 1) {\n            // put back param to newParams array if it is not the one to be removed\n            if (params[i].split('=')[0] !== name) {\n                newParams.push(params[i]);\n            }\n        }\n        return `${domain}?${newParams.join('&')}${hash ? `#${hash}` : ''}`;\n    },\n\n    /**\n     * @function\n     * @description appends the parameters to the given url and returns the changed url\n     * @param {String} url the url to which the parameters will be added\n     * @param {Object} params\n     */\n    appendParamsToUrl(url, params) {\n        let newUrl = url;\n        forEach(params, (value, name) => {\n            newUrl = this.appendParamToURL(newUrl, name, value);\n        });\n        return newUrl;\n    },\n    /**\n     * @function\n     * @description extract the query string from URL\n     * @param {String} url the url to extra query string from\n     * */\n    getQueryString(url) {\n        let qs;\n        if (!isString(url)) { return null; }\n        const a = document.createElement('a');\n        a.href = url;\n        if (a.search) {\n            qs = a.search.substr(1); // remove the leading ?\n        }\n        return qs;\n    },\n    /**\n     * @function\n     * @description\n     * @param {String}\n     * @param {String}\n     */\n    elementInViewport(el, offsetToTop) {\n        const width = el.offsetWidth;\n        const height = el.offsetHeight;\n        let top = el.offsetTop;\n        let left = el.offsetLeft;\n        let currentElement = el;\n\n        while (currentElement.offsetParent) {\n            currentElement = currentElement.offsetParent;\n            top += currentElement.offsetTop;\n            left += currentElement.offsetLeft;\n        }\n\n        if (typeof (offsetToTop) !== 'undefined') {\n            top -= offsetToTop;\n        }\n\n        if (window.pageXOffset !== null) {\n            return (\n                top < (window.pageYOffset + window.innerHeight)\n                && left < (window.pageXOffset + window.innerWidth)\n                && (top + height) > window.pageYOffset\n                && (left + width) > window.pageXOffset\n            );\n        }\n\n        if (document.compatMode === 'CSS1Compat') {\n            return (\n                top < (window.document.documentElement.scrollTop + window.document.documentElement.clientHeight)\n                && left < (window.document.documentElement.scrollLeft + window.document.documentElement.clientWidth)\n                && (top + height) > window.document.documentElement.scrollTop\n                && (left + width) > window.document.documentElement.scrollLeft\n            );\n        }\n        return false;\n    },\n\n    /**\n     * @function\n     * @description Appends the parameter 'format=ajax' to a given path\n     * @param {String} path the relative path\n     */\n    ajaxUrl(path) {\n        return this.appendParamToURL(path, 'format', 'ajax');\n    },\n\n    /**\n     * @function\n     * @description\n     * @param {String} url\n     */\n    toAbsoluteUrl(url) {\n        return url.indexOf('http') !== 0 && url.charAt(0) !== '/' ? `/${url}` : url;\n    },\n    /**\n     * @function\n     * @description Loads css dynamically from given urls\n     * @param {Array} urls Array of urls from which css will be dynamically loaded.\n     */\n    loadDynamicCss(urls) {\n        const len = urls.length;\n        for (let i = 0; i < len; i += 1) {\n            this.loadedCssFiles.push(this.loadCssFile(urls[i]));\n        }\n    },\n\n    /**\n     * @function\n     * @description Loads css file dynamically from given url\n     * @param {String} url The url from which css file will be dynamically loaded.\n     */\n    loadCssFile(url) {\n        return $('<link/>').appendTo($('head')).attr({\n            type: 'text/css',\n            rel: 'stylesheet',\n        }).attr('href', url); // for i.e. <9, href must be added after link has been appended to head\n    },\n    // array to keep track of the dynamically loaded CSS files\n    loadedCssFiles: [],\n\n    /**\n     * @function\n     * @description Removes all css files which were dynamically loaded\n     */\n    clearDynamicCss() {\n        const len = this.loadedCssFiles.length;\n        for (let i = 0; i < len; i += 1) {\n            $(this.loadedCssFiles[i]).remove();\n        }\n        this.loadedCssFiles = [];\n    },\n    /**\n     * @function\n     * @description Loads js dynamically from given urls\n     * @param {Array} urls Array of urls from which js will be dynamically loaded.\n     */\n    loadDynamicJs(urls) {\n        const len = urls.length;\n        for (let i = 0; i < len; i += 1) {\n            this.loadedJsFiles.push(this.loadJsFile(urls[i]));\n        }\n    },\n\n    /**\n     * @function\n     * @description Loads js file dynamically from given url\n     * @param {String} url The url from which js file will be dynamically loaded.\n     */\n    loadJsFile(url) {\n        return $('<script/>').appendTo($('body')).attr({\n            type: 'text/javascript',\n        }).attr('src', url);\n    },\n    // array to keep track of the dynamically loaded JS files\n    loadedJsFiles: [],\n\n    /**\n     * @function\n     * @description Removes all js files which were dynamically loaded\n     */\n    clearDynamicJs() {\n        const len = this.loadedJsFiles.length;\n        for (let i = 0; i < len; i += 1) {\n            $(this.loadedJsFiles[i]).remove();\n        }\n        this.loadedJsFiles = [];\n    },\n    /**\n     * @function\n     * @description Extracts all parameters from a given query string into an object\n     * @param {String} qs The query string from which the parameters will be extracted\n     */\n    getQueryStringParams(qs) {\n        if (!qs || qs.length === 0) { return {}; }\n        const url = qs.toString();\n        const params = {};\n\n        // Use the String::replace method to iterate over each\n        // name-value pair in the string.\n        url.replace(\n            /([^?=&]+)(=([^&]*))?/g,\n            ($0, $1, $2, $3) => {\n                params[$1] = decodeURIComponent($3);\n            },\n        );\n        return params;\n    },\n\n    fillAddressFields(address, $form) {\n        forEach(address, (value, field) => {\n            if (field !== 'ID' && field !== 'UUID' && field !== 'key') {\n                // if the key in address object ends with 'Code', remove that suffix\n                // keys that ends with 'Code' are postalCode, stateCode and countryCode\n                $form.find(`[name$=\"${field.replace('Code', '')}\"]`).val(address[field]);\n                // update the state fields\n                if (field === 'countryCode') {\n                    $form.find('[name$=\"country\"]').trigger('change');\n                    // retrigger state selection after country has changed\n                    // this results in duplication of the state code, but is a necessary evil\n                    // for now because sometimes countryCode comes after stateCode\n                    $form.find('[name$=\"state\"]').val(address.stateCode);\n                }\n            }\n        });\n    },\n\n    fillAddressDisplay(address, $container) {\n        forEach(address, (value, field) => {\n            if (field !== 'ID' && field !== 'UUID' && field !== 'key') {\n                $container.find(`.${field}`).text(address[field]);\n            }\n        });\n    },\n    /**\n     * @function\n     * @description Updates the number of the remaining character\n     * based on the character limit in a text area\n     */\n    limitCharacters() {\n        $('form').find('textarea[data-character-limit], input[data-character-limit]').each((index, element) => {\n            const characterLimit = $(element).data('character-limit');\n            const charCountHtml = String.format(\n                Resources.CHAR_LIMIT_MSG,\n                `<span class=\"char-remain-count\">${characterLimit}</span>`,\n            );\n            let charCountContainer = $(element).next('div.char-count');\n            if (charCountContainer.length === 0) {\n                charCountContainer = $('<div class=\"char-count\"/>').insertAfter($(element));\n            }\n            charCountContainer.html(charCountHtml);\n            // trigger the keydown event so that any existing character data is calculated\n            $(element).change();\n        });\n    },\n    /**\n     * @function\n     * @description Binds the onclick-event to a delete button on a given container,\n     * which opens a confirmation box with a given message\n     * @param {String} container The name of element to which the function will be bind\n     * @param {String} message The message the will be shown upon a click\n     */\n    setDeleteConfirmation(container, message) {\n        $(container).on('click', '.delete', () => window.confirm(message));\n    },\n    /**\n     * @function\n     * @description Scrolls a browser window to a given x point\n     * @param {String} The x coordinate\n     */\n    scrollBrowser(xLocation, callback) {\n        $('html, body').animate({scrollTop: xLocation}, 500, callback);\n    },\n\n    /**\n     * @function\n     * @desc Determines if the device that is being used is mobile\n     * @returns {Boolean}\n     */\n    isMobile() {\n        const mobileAgentHash = ['mobile', 'tablet', 'phone', 'ipad', 'ipod', 'android', 'blackberry', 'windows ce', 'opera mini', 'palm'];\n        let idx = 0;\n        let isMobile = false;\n        const userAgent = (navigator.userAgent).toLowerCase();\n\n        while (mobileAgentHash[idx] && !isMobile) {\n            isMobile = (userAgent.indexOf(mobileAgentHash[idx]) >= 0);\n            idx += 1;\n        }\n        return isMobile;\n    },\n\n    /**\n     * Executes a callback function when the user has stopped resizing the screen.\n     *\n     * @param   {function}  callback\n     * @var     obj         timeout\n     *\n     * @return  {function}\n     */\n    smartResize(callback) {\n        let timeout;\n\n        $(window).on('resize', () => {\n            clearTimeout(timeout);\n            timeout = setTimeout(callback, 100);\n        }).resize();\n\n        return callback;\n    },\n\n    /**\n     * @function\n     * @desc Generates a min-width matchMedia media query based on the given params\n     * @param {string} size - Breakpoint to use for the media query\n     * @param {object} breakpoints - Override of the util breakpoints (optional)\n     */\n    mediaBreakpointUp(size, breakpoints) {\n        const breakpoint = this.getViewports(size, breakpoints);\n        const mediaQuery = window.matchMedia(`(min-width: ${breakpoint}px)`);\n        return mediaQuery.matches;\n    },\n\n    /**\n     * @function\n     * @desc Generates a min-width matchMedia media query based on the given params\n     * @param {string} size - Breakpoint to use for the media query\n     * @param {object} breakpoints - Override of the util breakpoints object (optional)\n     */\n    mediaBreakpointDown(size, breakpoints) {\n        const bps = typeof breakpoints !== 'undefined' ? breakpoints : this.breakpoints;\n        const nextSize = this.getNextObjectKey(bps, size);\n\n        if (typeof nextSize === 'string') {\n            const breakpoint = this.getViewports(nextSize, breakpoints) - 1;\n            const mediaQuery = window.matchMedia(`(max-width: ${breakpoint}px)`);\n            return mediaQuery.matches;\n        }\n        return true;\n    },\n\n    /**\n     * @function\n     * @desc Generates a min-width and max-width matchMedia media queries based on the given params\n     * @param {string} sizeMin - Min breakpoint to use for the media query\n     * @param {string} sizeMax - Max breakpoint to use for the media query\n     * @param {object} breakpoints - Override of the util breakpoints object (optional)\n     */\n    mediaBreakpointBetween(sizeMin, sizeMax, breakpoints) {\n        const min = this.mediaBreakpointUp(sizeMin, breakpoints);\n        const max = this.mediaBreakpointDown(sizeMax, breakpoints);\n\n        return min && max;\n    },\n\n    /**\n     * @function\n     * @desc Generates a min-width and max-width matchMedia media query based on the given params\n     * @param {string} size - Breakpoint to use for the media query\n     * @param {object} breakpoints - Override of the util breakpoints object (optional)\n     */\n    mediaBreakpointOnly(size, breakpoints) {\n        return this.mediaBreakpointBetween(size, size, breakpoints);\n    },\n\n    /**\n     * @function\n     * @desc Retrieves the next key in the object or null if it doesn't exist\n     * @returns {string}|{null}\n     */\n    getNextObjectKey(obj, key) {\n        const keys = Object.keys(obj);\n        const nextIndex = keys.indexOf(key) + 1;\n\n        if (keys.length > nextIndex) {\n            return keys[nextIndex];\n        }\n        return null;\n    },\n\n    /**\n     * @function\n     * @desc Retrieves the util breakpoints object\n     * @returns {object}\n     */\n    getBreakpoints() {\n        return this.breakpoints;\n    },\n\n    /**\n     * @function\n     * @description Generate svg icon\n     * @param icon  {String} svg icon name\n     * @returns {String} SVG tag\n     */\n    svg(icon, extraClasses) {\n        const classes = extraClasses || '';\n        return `<svg class=\"icon ${icon} ${classes} svg-${icon}-dims\"><use xlink:href=\"#${icon}\"/></svg>`;\n    },\n\n    backToTop() {\n        const bttScrollTriggerDesktop = window.SitePreferences.BTT_DESKTOP;\n        const bttScrollTriggerMobile = window.SitePreferences.BTT_MOBILE;\n        const scrollTop = $(window).scrollTop();\n        const desktopQuery = matchMedia('(min-width: 768px)');\n        let bttScrollTrigger;\n\n        if (desktopQuery.matches) {\n            bttScrollTrigger = bttScrollTriggerDesktop;\n        } else {\n            bttScrollTrigger = bttScrollTriggerMobile;\n        }\n\n        if (scrollTop > bttScrollTrigger) {\n            $('.back-to-top').addClass('show');\n        } else {\n            $('.back-to-top').removeClass('show');\n        }\n    },\n\n    /**\n     * @function\n     * @description Sets checkbox values and aria attributes\n     * @param {String} input\n     */\n    setCheckboxValues(input) {\n        const $hiddenInput = $(input).parents('.checkbox').find('.input-checkbox');\n        if ($hiddenInput.is(':checked')) {\n            $hiddenInput.val(false).attr('aria-checked', 'false');\n        } else {\n            $hiddenInput.val(true).attr('aria-checked', 'true');\n        }\n    },\n\n    /**\n     * @function\n     * @description Set's a mutation observer on the einstein product carousel to know when it can init\n     * @param {String} carouselContainerSelector\n     * @param {String} tileContainerSelector\n     * @param {Number} specificSlotSelector\n     * @param {Number} numberOfSlides\n     * @example util.initDynamicCarousel('Dom-Selector', 'Dom-Selector', jQeury Object Index, Number of slides);\n     * */\n    initDynamicCarousel(carouselContainerSelector, tileContainerSelector, specificSlotSelector, numberOfSlides) {\n        // Pass in param that defines which slot on the page we trying to init if there is only one on the page pass in 0\n        const specificSlot = specificSlotSelector || 0;\n        // Grab the defined tile recommendation tile container\n        const carouselContainer = $(carouselContainerSelector).get(specificSlot);\n        // If we don't get a result for any reason return us out\n        if (!carouselContainer) {\n            return;\n        }\n        // Mutation observer config\n        const carouselObserverConfig = {childList: true, subtree: true};\n        // Define mutation observer action too take\n        const dynamicCarouselObserver = new MutationObserver(() => {\n            try {\n                const $tileContainer = $(carouselContainer).find(tileContainerSelector);\n                $tileContainer.not('.slick-initialized').slick({\n                    speed: SitePreferences.SLIDE_SHOW_SPEED,\n                    dots: false,\n                    slide: '.grid-tile',\n                    arrows: true,\n                    slidesToShow: numberOfSlides || 4,\n                    slidesToScroll: 1,\n                    infinite: false,\n                    responsive: [\n                        {\n                            breakpoint: util.getViewports('lg'),\n                            settings: {\n                                slidesToShow: 3,\n                                slidesToScroll: 1,\n                            },\n                        },\n                        {\n                            breakpoint: util.getViewports('md'),\n                            settings: {\n                                slidesToShow: 1,\n                                slidesToScroll: 1,\n                            },\n                        },\n                    ],\n                });\n            // This try catch will catch errors with slick as well as if there are not enough slides to fully init the carousel\n            } catch (e) {\n                console.log(e);\n            }\n        });\n        // Start observing container\n        dynamicCarouselObserver.observe(carouselContainer, carouselObserverConfig);\n    },\n};\n\nmodule.exports = util;\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AAEA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;A","sourceRoot":""}