';\n\n htmlString += htmlSlide;\n });\n var $varName = $('.product-detail .set-items');\n var $carouselImage;\n var $tempDiv;\n\n if ($varName.length) {\n $tempDiv = $('div.set-items').find('div[data-pid=\"' + productID + '\"]');\n $carouselImage = $tempDiv.find('.product-carousel');\n } else {\n $carouselImage = $('.product-carousel');\n }\n $carouselImage.html(htmlString);\n}\n\nvar exportBase = $.extend({}, base, {\n carouselInit: carouselInit,\n carouselInitBonus: carouselInitBonus,\n carouselUnslick: carouselUnslick,\n carouselUnslickBonus: carouselUnslickBonus,\n updateMainImages: updateMainImages\n});\n\nmodule.exports = exportBase;\n","require('bootstrap/js/src/util.js');\nrequire('bootstrap/js/src/alert.js');\n// require('bootstrap/js/src/button.js');\nrequire('bootstrap/js/src/carousel.js');\nrequire('bootstrap/js/src/collapse.js');\n// require('bootstrap/js/src/dropdown.js');\nrequire('bootstrap/js/src/modal.js');\nrequire('bootstrap/js/src/scrollspy.js');\nrequire('bootstrap/js/src/tab.js');\nrequire('bootstrap/js/src/tooltip.js');\n// require('bootstrap/js/src/popover.js');\n","'use strict';\n\n/**\n * isString - native function replacing lodash\n * @param {*} str - passed to function to determine type\n * @description - replaces lodash version\n * @returns {boolean} is it a string?\n */\nfunction isString(str) {\n if (str != null && typeof str.valueOf() === 'string') {\n return true;\n }\n return false;\n}\n\nvar utils = {\n /**\n * @desc Media breakpoints that are used throughout the Javascript\n */\n breakpoints: { // TODO: LFED Update with project breakpoints\n xs: 0,\n sm: 576,\n md: 768,\n lg: 992,\n xl: 1200\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 {Object=} breakpoints A custom breakpoints object\n * @returns {Object|string} - breakpoints or specific viewport\n */\n getViewports: function (size, breakpoints) {\n var bps = breakpoints || this.breakpoints;\n\n if (size) {\n var viewport = bps[size];\n\n if (viewport) {\n return viewport;\n }\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 * @returns {string} - current viewport name\n */\n getCurrentViewport: function () {\n var w = window.innerWidth;\n var viewports = utils.getViewports();\n var viewport = 'max';\n // Traverse the object from small up to desktop, and return the first match\n viewports.forEach((value, name) => {\n if (w <= value) {\n viewport = name;\n }\n });\n return viewport;\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 * @returns {string} - URL with parameter\n */\n appendParamToURL: function (url, name, value) {\n // quit if the param already exists\n if (url.indexOf(name + '=' + value) !== -1) {\n return url;\n }\n var 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 * @param {string} value the value of the parameter that will be remove from url (optional)\n * @returns {string} - URL without parameter\n */\n removeParamFromURL: function (url, name, value) {\n var paramValue = value || '';\n if (url.indexOf('?') === -1 || url.indexOf(name + '=' + paramValue) === -1) {\n return url;\n }\n var hash;\n var params;\n var domain = url.split('?')[0];\n var paramUrl = url.split('?')[1];\n var newParams = [];\n // if there is a hash at the end, store the hash\n if (paramUrl.indexOf('#') > -1) {\n hash = paramUrl.split('#')[1] || '';\n paramUrl = paramUrl.split('#')[0];\n }\n params = paramUrl.split('&');\n for (var i = 0; i < params.length; i++) {\n // put back param to newParams array if it is not the one to be removed\n var paramAndValue = params[i].split('=');\n\n if (paramAndValue[0] !== name || (paramAndValue[0] === name && paramAndValue[1] !== paramValue && paramValue !== '')) {\n newParams.push(params[i]);\n }\n }\n return domain + '?' + newParams.join('&') + (hash ? '#' + hash : '');\n },\n\n /**\n * appends params to a url\n * @param {string} url - Original url\n * @param {Object} params - Parameters to append\n * @returns {string} result url with appended parameters\n */\n appendToUrl: function (url, params) {\n var newUrl = url;\n newUrl += (newUrl.indexOf('?') !== -1 ? '&' : '?') + Object.keys(params).map(function (key) {\n return key + '=' + encodeURIComponent(params[key]);\n }).join('&');\n\n return newUrl;\n },\n\n /**\n * @function\n * @description extract the query string from URL\n * @param {string} url the url to extra query string from\n * @returns {string|Object} - Query String from URL\n **/\n getQueryString: function (url) {\n var qs;\n if (!isString(url)) {\n return null;\n }\n var 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 /**\n * @function\n * @description Checks to see if the given element is in the current viewport\n * @param {string} el - Element to check\n * @param {string} offsetToTop - Offset to give the top value\n * @returns {boolean} - Whether or not the element is in the viewport\n */\n elementInViewport: function (el, offsetToTop) {\n var top = el.offsetTop;\n var left = el.offsetLeft;\n var width = el.offsetWidth;\n var height = el.offsetHeight;\n\n while (el.offsetParent) {\n var offsetParent = el.offsetParent;\n top += offsetParent.offsetTop;\n left += offsetParent.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\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 * @returns {string} - URL with format param of ajax\n */\n ajaxUrl: function (path) {\n return this.appendParamToURL(path, 'format', 'ajax');\n },\n\n /**\n * @function\n * @description Converts the given relative URL to an absolute URL\n * @param {string} url - URL to convert\n * @returns {string} - Absolute URL\n */\n toAbsoluteUrl: function (url) {\n var absURL = url;\n if (url.indexOf('http') !== 0 && url.charAt(0) !== '/') {\n absURL = '/' + url;\n }\n return absURL;\n },\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: function (urls) {\n var i = urls.length;\n var len = urls.length;\n for (i = 0; i < len; i++) {\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 * @returns {jQuery} - CSS Link Element\n */\n loadCssFile: function (url) {\n return $('').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: function () {\n var i = this.loadedCssFiles.length;\n while (i-- > 0) {\n $(this.loadedCssFiles[i]).remove();\n }\n this.loadedCssFiles = [];\n },\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 * @returns {Object} - Object with params from the query string\n */\n getQueryStringParams: function (qs) {\n if (!qs || qs.length === 0) { return {}; }\n var params = {};\n var unescapedQS = decodeURIComponent(qs);\n // Use the String::replace method to iterate over each\n // name-value pair in the string.\n unescapedQS.replace(new RegExp('([^?=&]+)(=([^&]*))?', 'g'),\n function ($0, $1, $2, $3) {\n params[$1] = $3;\n }\n );\n return params;\n },\n\n /**\n * @function\n * @description Fills in the given form with the given address information\n * @param {Object} address - Address fields object\n * @param {Object} $form - Form jQuery object\n */\n fillAddressFields: function (address, $form) {\n var fields = Object.keys(address);\n for (var i = 0; i < fields.length; i++) {\n var field = fields[i];\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 /**\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: function (container, message) {\n $(container).on('click', '.delete', function () {\n return window.confirm(message); // eslint-disable-line\n });\n },\n\n /**\n * @function\n * @description Scrolls a browser window to a given x point\n * @param {string} xLocation - The x coordinate\n */\n scrollBrowser: function (xLocation) {\n $('html, body').animate({\n scrollTop: xLocation\n }, 500);\n },\n\n /**\n * @function\n * @desc Determines if the device that is being used is mobile\n * @returns {boolean} - Wether or not the browser is currently mobile\n */\n isMobile: function () {\n var mobileAgentHash = ['mobile', 'tablet', 'phone', 'ipad', 'ipod', 'android', 'blackberry', 'windows ce', 'opera mini', 'palm'];\n var idx = 0;\n var isMobile = false;\n var userAgent = (navigator.userAgent).toLowerCase();\n\n while (mobileAgentHash[idx] && !isMobile) {\n isMobile = (userAgent.indexOf(mobileAgentHash[idx]) >= 0);\n idx++;\n }\n return isMobile;\n },\n\n /**\n * @function\n * @description Executes a callback function when the user has stopped resizing the screen.\n * @param {function} callback - Callback function for the resize event\n * @return {function} - Callback function for the resize event\n */\n smartResize: function (callback) {\n var timeout;\n\n $(window).on('resize', function () {\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 * @returns {boolean} - Wether or not the given media query matches\n */\n mediaBreakpointUp: function (size, breakpoints) {\n var breakpoint = this.getViewports(size, breakpoints);\n var mediaQuery = window.matchMedia('screen and (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 * @returns {boolean} - Wether or not the given media query matches\n */\n mediaBreakpointDown: function (size, breakpoints) {\n var bps = typeof breakpoints !== 'undefined' ? breakpoints : this.breakpoints;\n var nextSize = this.getNextObjectKey(bps, size);\n\n if (typeof nextSize === 'string') {\n var breakpoint = this.getViewports(nextSize, breakpoints) - 1;\n var mediaQuery = window.matchMedia('screen and (max-width: ' + breakpoint + 'px)');\n return mediaQuery.matches;\n }\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 * @returns {boolean} - Wether or not the given media query matches\n */\n mediaBreakpointBetween: function (sizeMin, sizeMax, breakpoints) {\n var min = this.mediaBreakpointUp(sizeMin, breakpoints);\n var 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 * @returns {boolean} - Wether or not the given media query matches\n */\n mediaBreakpointOnly: function (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 * @param {Object} obj - Object to get the next key from\n * @param {string} key - Key to base the next key index on\n * @returns {string}|{null} - The next key of the given object or null if one doesn't exist\n */\n getNextObjectKey: function (obj, key) {\n var keys = Object.keys(obj);\n var nextIndex = keys.indexOf(key) + 1;\n\n if (keys.length > nextIndex) {\n return keys[nextIndex];\n }\n\n return null;\n },\n\n /**\n * @function\n * @desc Retrieves the util breakpoints object\n * @returns {Object} - All of the breakpoints\n */\n getBreakpoints: function () {\n return this.breakpoints;\n }\n};\n\nmodule.exports = utils;\n","'use strict';\n\nvar base = require('../product/base');\nvar focusHelper = require('../components/focus');\n\n/**\n * appends params to a url\n * @param {string} url - Original url\n * @param {Object} params - Parameters to append\n * @returns {string} result url with appended parameters\n */\nfunction appendToUrl(url, params) {\n var newUrl = url;\n newUrl += (newUrl.indexOf('?') !== -1 ? '&' : '?') + Object.keys(params).map(function (key) {\n return key + '=' + encodeURIComponent(params[key]);\n }).join('&');\n\n return newUrl;\n}\n\n/**\n * Checks whether the basket is valid. if invalid displays error message and disables\n * checkout button\n * @param {Object} data - AJAX response from the server\n */\nfunction validateBasket(data) {\n if (data.valid.error) {\n if (data.valid.message) {\n var errorHtml = '
'\n );\n $('.number-of-items').empty().append(data.resources.numberOfItems);\n $('.minicart-quantity').empty().append(data.numItems);\n $('.minicart-link').attr({\n 'aria-label': data.resources.minicartCountOfItems,\n title: data.resources.minicartCountOfItems\n });\n $('.minicart .popover').empty();\n $('.minicart .popover').removeClass('show');\n }\n\n $('.checkout-btn').addClass('disabled');\n } else {\n $('.checkout-btn').removeClass('disabled');\n }\n}\n\n/**\n * re-renders the order totals and the number of items in the cart\n * @param {Object} data - AJAX response from the server\n */\nfunction updateCartTotals(data) {\n $('.number-of-items').empty().append(data.resources.numberOfItems);\n $('.shipping-cost').empty().append(data.totals.totalShippingCost);\n $('.tax-total').empty().append(data.totals.totalTax);\n $('.grand-total').empty().append(data.totals.grandTotal);\n $('.sub-total').empty().append(data.totals.subTotal);\n $('.minicart-quantity').empty().append(data.numItems);\n $('.minicart-link').attr({\n 'aria-label': data.resources.minicartCountOfItems,\n title: data.resources.minicartCountOfItems\n });\n if (data.totals.orderLevelDiscountTotal.value > 0) {\n $('.order-discount').removeClass('hide-order-discount');\n $('.order-discount-total').empty()\n .append('- ' + data.totals.orderLevelDiscountTotal.formatted);\n } else {\n $('.order-discount').addClass('hide-order-discount');\n }\n\n if (data.totals.shippingLevelDiscountTotal.value > 0) {\n $('.shipping-discount').removeClass('hide-shipping-discount');\n $('.shipping-discount-total').empty().append('- ' +\n data.totals.shippingLevelDiscountTotal.formatted);\n } else {\n $('.shipping-discount').addClass('hide-shipping-discount');\n }\n\n data.items.forEach(function (item) {\n if (data.totals.orderLevelDiscountTotal.value > 0) {\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n }\n if (item.renderedPromotions) {\n $('.item-' + item.UUID).empty().append(item.renderedPromotions);\n } else {\n $('.item-' + item.UUID).empty();\n }\n $('.uuid-' + item.UUID + ' .unit-price').empty().append(item.renderedPrice);\n $('.line-item-price-' + item.UUID + ' .unit-price').empty().append(item.renderedPrice);\n $('.item-total-' + item.UUID).empty().append(item.priceTotal.renderedPrice);\n });\n}\n\n/**\n * re-renders the order totals and the number of items in the cart\n * @param {Object} message - Error message to display\n */\nfunction createErrorNotification(message) {\n var errorHtml = '
' +\n '' + message + '
';\n\n $('.cart-error').append(errorHtml);\n}\n\n/**\n * re-renders the approaching discount messages\n * @param {Object} approachingDiscounts - updated approaching discounts for the cart\n */\nfunction updateApproachingDiscounts(approachingDiscounts) {\n var html = '';\n $('.approaching-discounts').empty();\n if (approachingDiscounts.length > 0) {\n approachingDiscounts.forEach(function (item) {\n html += '
'\n + item.discountMsg + '
';\n });\n }\n $('.approaching-discounts').append(html);\n}\n\n/**\n * Updates the availability of a product line item\n * @param {Object} data - AJAX response from the server\n * @param {string} uuid - The uuid of the product line item to update\n */\nfunction updateAvailability(data, uuid) {\n var lineItem;\n var messages = '';\n\n for (var i = 0; i < data.items.length; i++) {\n if (data.items[i].UUID === uuid) {\n lineItem = data.items[i];\n break;\n }\n }\n\n if (lineItem != null) {\n $('.availability-' + lineItem.UUID).empty();\n\n if (lineItem.availability) {\n if (lineItem.availability.messages) {\n lineItem.availability.messages.forEach(function (message) {\n messages += '
' + message + '
';\n });\n }\n\n if (lineItem.availability.inStockDate) {\n messages += '
'\n + lineItem.availability.inStockDate\n + '
';\n }\n }\n\n $('.availability-' + lineItem.UUID).html(messages);\n }\n}\n\n/**\n * Finds an element in the array that matches search parameter\n * @param {array} array - array of items to search\n * @param {function} match - function that takes an element and returns a boolean indicating if the match is made\n * @returns {Object|null} - returns an element of the array that matched the query.\n */\nfunction findItem(array, match) { // eslint-disable-line no-unused-vars\n for (var i = 0, l = array.length; i < l; i++) {\n if (match.call(this, array[i])) {\n return array[i];\n }\n }\n return null;\n}\n\n/**\n * Updates details of a product line item\n * @param {Object} data - AJAX response from the server\n * @param {string} uuid - The uuid of the product line item to update\n */\nfunction updateProductDetails(data, uuid) {\n $('.card.product-info.uuid-' + uuid).replaceWith(data.renderedTemplate);\n}\n\n/**\n * Generates the modal window on the first call.\n *\n */\nfunction getModalHtmlElement() {\n if ($('#editProductModal').length !== 0) {\n $('#editProductModal').remove();\n }\n var htmlString = ''\n + '
'\n + ''\n + '
'\n + ''\n + '
'\n + '
'\n + ' '\n + '
'\n + ''\n + ''\n + '
'\n + '
'\n + '
';\n $('body').append(htmlString);\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n *\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
').append($.parseHTML(html));\n\n var body = $html.find('.product-quickview');\n var footer = $html.find('.modal-footer').children();\n\n return { body: body, footer: footer };\n}\n\n/**\n * replaces the content in the modal window for product variation to be edited.\n * @param {string} editProductUrl - url to be used to retrieve a new product model\n */\nfunction fillModalElement(editProductUrl) {\n $('.modal-body').spinner().start();\n $.ajax({\n url: editProductUrl,\n method: 'GET',\n dataType: 'json',\n success: function (data) {\n var parsedHtml = parseHtml(data.renderedTemplate);\n\n $('#editProductModal .modal-body').empty();\n $('#editProductModal .modal-body').html(parsedHtml.body);\n $('#editProductModal .modal-footer').html(parsedHtml.footer);\n $('#editProductModal .modal-header .close .sr-only').text(data.closeButtonText);\n $('#editProductModal .enter-message').text(data.enterDialogMessage);\n $('#editProductModal').modal('show');\n $('body').trigger('editproductmodal:ready');\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n}\n\n/**\n * replace content of modal\n * @param {string} actionUrl - url to be used to remove product\n * @param {string} productID - pid\n * @param {string} productName - product name\n * @param {string} uuid - uuid\n */\nfunction confirmDelete(actionUrl, productID, productName, uuid) {\n var $deleteConfirmBtn = $('.cart-delete-confirmation-btn');\n var $productToRemoveSpan = $('.product-to-remove');\n\n $deleteConfirmBtn.data('pid', productID);\n $deleteConfirmBtn.data('action', actionUrl);\n $deleteConfirmBtn.data('uuid', uuid);\n\n $productToRemoveSpan.empty().append(productName);\n}\n\nmodule.exports = function () {\n $('body').on('click', '.remove-product', function (e) {\n e.preventDefault();\n\n var actionUrl = $(this).data('action');\n var productID = $(this).data('pid');\n var productName = $(this).data('name');\n var uuid = $(this).data('uuid');\n confirmDelete(actionUrl, productID, productName, uuid);\n });\n\n $('body').on('afterRemoveFromCart', function (e, data) {\n e.preventDefault();\n confirmDelete(data.actionUrl, data.productID, data.productName, data.uuid);\n });\n\n $('.optional-promo').click(function (e) {\n e.preventDefault();\n $('.promo-code-form').toggle();\n });\n\n $('body').on('click', '.cart-delete-confirmation-btn', function (e) {\n e.preventDefault();\n\n var productID = $(this).data('pid');\n var url = $(this).data('action');\n var uuid = $(this).data('uuid');\n var urlParams = {\n pid: productID,\n uuid: uuid\n };\n\n url = appendToUrl(url, urlParams);\n\n $('body > .modal-backdrop').remove();\n\n $.spinner().start();\n\n $('body').trigger('cart:beforeUpdate');\n\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n success: function (data) {\n if (data.basket.items.length === 0) {\n $('.cart').empty().append('
' +\n '
' +\n '
' + data.basket.resources.emptyCartMsg + '
' +\n '
' +\n '
'\n );\n $('.number-of-items').empty().append(data.basket.resources.numberOfItems);\n $('.minicart-quantity').empty().append(data.basket.numItems);\n $('.minicart-link').attr({\n 'aria-label': data.basket.resources.minicartCountOfItems,\n title: data.basket.resources.minicartCountOfItems\n });\n $('.minicart .popover').empty();\n $('.minicart .popover').removeClass('show');\n $('body').removeClass('modal-open');\n $('html').removeClass('veiled');\n } else {\n if (data.toBeDeletedUUIDs && data.toBeDeletedUUIDs.length > 0) {\n for (var i = 0; i < data.toBeDeletedUUIDs.length; i++) {\n $('.uuid-' + data.toBeDeletedUUIDs[i]).remove();\n }\n }\n $('.uuid-' + uuid).remove();\n if (!data.basket.hasBonusProduct) {\n $('.bonus-product').remove();\n }\n $('.coupons-and-promos').empty().append(data.basket.totals.discountsHtml);\n updateCartTotals(data.basket);\n updateApproachingDiscounts(data.basket.approachingDiscounts);\n $('body').trigger('setShippingMethodSelection', data.basket);\n validateBasket(data.basket);\n }\n\n $('body').trigger('cart:update', data);\n\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n });\n\n $('body').on('change', '.quantity-form > .quantity', function () {\n var preSelectQty = $(this).data('pre-select-qty');\n var quantity = $(this).val();\n var productID = $(this).data('pid');\n var url = $(this).data('action');\n var uuid = $(this).data('uuid');\n\n var urlParams = {\n pid: productID,\n quantity: quantity,\n uuid: uuid\n };\n url = appendToUrl(url, urlParams);\n\n $(this).parents('.card').spinner().start();\n\n $('body').trigger('cart:beforeUpdate');\n\n $.ajax({\n url: url,\n type: 'get',\n context: this,\n dataType: 'json',\n success: function (data) {\n $('.quantity[data-uuid=\"' + uuid + '\"]').val(quantity);\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n updateAvailability(data, uuid);\n validateBasket(data);\n $(this).data('pre-select-qty', quantity);\n\n $('body').trigger('cart:update', data);\n\n $.spinner().stop();\n if ($(this).parents('.product-info').hasClass('bonus-product-line-item') && $('.cart-page').length) {\n location.reload();\n }\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $(this).val(parseInt(preSelectQty, 10));\n $.spinner().stop();\n }\n }\n });\n });\n\n $('.shippingMethods').change(function () {\n var url = $(this).attr('data-actionUrl');\n var urlParams = {\n methodID: $(this).find(':selected').attr('data-shipping-id')\n };\n // url = appendToUrl(url, urlParams);\n\n $('.totals').spinner().start();\n $('body').trigger('cart:beforeShippingMethodSelected');\n $.ajax({\n url: url,\n type: 'post',\n dataType: 'json',\n data: urlParams,\n success: function (data) {\n if (data.error) {\n window.location.href = data.redirectUrl;\n } else {\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n validateBasket(data);\n }\n\n $('body').trigger('cart:shippingMethodSelected', data);\n $.spinner().stop();\n },\n error: function (err) {\n if (err.redirectUrl) {\n window.location.href = err.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n });\n\n $('.promo-code-form').submit(function (e) {\n e.preventDefault();\n $.spinner().start();\n $('.coupon-missing-error').hide();\n $('.coupon-error-message').empty();\n if (!$('.coupon-code-field').val()) {\n $('.promo-code-form .form-control').addClass('is-invalid');\n $('.promo-code-form .form-control').attr('aria-describedby', 'missingCouponCode');\n $('.coupon-missing-error').show();\n $.spinner().stop();\n return false;\n }\n var $form = $('.promo-code-form');\n $('.promo-code-form .form-control').removeClass('is-invalid');\n $('.coupon-error-message').empty();\n $('body').trigger('promotion:beforeUpdate');\n\n $.ajax({\n url: $form.attr('action'),\n type: 'GET',\n dataType: 'json',\n data: $form.serialize(),\n success: function (data) {\n if (data.error) {\n $('.promo-code-form .form-control').addClass('is-invalid');\n $('.promo-code-form .form-control').attr('aria-describedby', 'invalidCouponCode');\n $('.coupon-error-message').empty().append(data.errorMessage);\n $('body').trigger('promotion:error', data);\n } else {\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n validateBasket(data);\n $('body').trigger('promotion:success', data);\n }\n $('.coupon-code-field').val('');\n $.spinner().stop();\n },\n error: function (err) {\n $('body').trigger('promotion:error', err);\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.errorMessage);\n $.spinner().stop();\n }\n }\n });\n return false;\n });\n\n $('body').on('click', '.remove-coupon', function (e) {\n e.preventDefault();\n\n var couponCode = $(this).data('code');\n var uuid = $(this).data('uuid');\n var $deleteConfirmBtn = $('.delete-coupon-confirmation-btn');\n var $productToRemoveSpan = $('.coupon-to-remove');\n\n $deleteConfirmBtn.data('uuid', uuid);\n $deleteConfirmBtn.data('code', couponCode);\n\n $productToRemoveSpan.empty().append(couponCode);\n });\n\n $('body').on('click', '.delete-coupon-confirmation-btn', function (e) {\n e.preventDefault();\n\n var url = $(this).data('action');\n var uuid = $(this).data('uuid');\n var couponCode = $(this).data('code');\n var urlParams = {\n code: couponCode,\n uuid: uuid\n };\n\n url = appendToUrl(url, urlParams);\n\n $('body > .modal-backdrop').remove();\n\n $.spinner().start();\n $('body').trigger('promotion:beforeUpdate');\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n success: function (data) {\n $('.coupon-uuid-' + uuid).remove();\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n validateBasket(data);\n $.spinner().stop();\n $('body').trigger('promotion:success', data);\n },\n error: function (err) {\n $('body').trigger('promotion:error', err);\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n });\n $('body').on('click', '.cart-page .bonus-product-button', function () {\n $.spinner().start();\n $(this).addClass('launched-modal');\n $.ajax({\n url: $(this).data('url'),\n method: 'GET',\n dataType: 'json',\n success: function (data) {\n base.methods.editBonusProducts(data);\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n });\n\n $('body').on('hidden.bs.modal', '#chooseBonusProductModal', function () {\n $('#chooseBonusProductModal').remove();\n $('.modal-backdrop').remove();\n $('body').removeClass('modal-open');\n\n if ($('.cart-page').length) {\n $('.launched-modal .btn-outline-primary').trigger('focus');\n $('.launched-modal').removeClass('launched-modal');\n } else {\n $('.product-detail .add-to-cart').focus();\n }\n });\n\n $('body').on('click', '.cart-page .product-edit .edit, .cart-page .bundle-edit .edit', function (e) {\n e.preventDefault();\n\n var editProductUrl = $(this).attr('href');\n getModalHtmlElement();\n fillModalElement(editProductUrl);\n });\n\n $('body').on('shown.bs.modal', '#editProductModal', function () {\n $('#editProductModal').siblings().attr('aria-hidden', 'true');\n $('#editProductModal .close').focus();\n });\n\n $('body').on('hidden.bs.modal', '#editProductModal', function () {\n $('#editProductModal').siblings().attr('aria-hidden', 'false');\n });\n\n $('body').on('keydown', '#editProductModal', function (e) {\n var focusParams = {\n event: e,\n containerSelector: '#editProductModal',\n firstElementSelector: '.close',\n lastElementSelector: '.update-cart-product-global',\n nextToLastElementSelector: '.modal-footer .quantity-select'\n };\n focusHelper.setTabNextFocus(focusParams);\n });\n\n $('body').on('product:updateAddToCart', function (e, response) {\n // update global add to cart (single products, bundles)\n var dialog = $(response.$productContainer)\n .closest('.quick-view-dialog');\n\n $('.update-cart-product-global', dialog).attr('disabled',\n !$('.global-availability', dialog).data('ready-to-order')\n || !$('.global-availability', dialog).data('available')\n );\n });\n\n $('body').on('product:updateAvailability', function (e, response) {\n // bundle individual products\n $('.product-availability', response.$productContainer)\n .data('ready-to-order', response.product.readyToOrder)\n .data('available', response.product.available)\n .find('.availability-msg')\n .empty()\n .html(response.message);\n\n\n var dialog = $(response.$productContainer)\n .closest('.quick-view-dialog');\n\n if ($('.product-availability', dialog).length) {\n // bundle all products\n var allAvailable = $('.product-availability', dialog).toArray()\n .every(function (item) { return $(item).data('available'); });\n\n var allReady = $('.product-availability', dialog).toArray()\n .every(function (item) { return $(item).data('ready-to-order'); });\n\n $('.global-availability', dialog)\n .data('ready-to-order', allReady)\n .data('available', allAvailable);\n\n $('.global-availability .availability-msg', dialog).empty()\n .html(allReady ? response.message : response.resources.info_selectforstock);\n } else {\n // single product\n $('.global-availability', dialog)\n .data('ready-to-order', response.product.readyToOrder)\n .data('available', response.product.available)\n .find('.availability-msg')\n .empty()\n .html(response.message);\n }\n });\n\n $('body').on('product:afterAttributeSelect', function (e, response) {\n if ($('.modal.show .product-quickview .bundle-items').length) {\n $('.modal.show').find(response.container).data('pid', response.data.product.id);\n $('.modal.show').find(response.container).find('.product-id').text(response.data.product.id);\n } else {\n $('.modal.show .product-quickview').data('pid', response.data.product.id);\n }\n });\n\n $('body').on('change', '.quantity-select', function () {\n var selectedQuantity = $(this).val();\n $('.modal.show .update-cart-url').data('selected-quantity', selectedQuantity);\n });\n\n $('body').on('change', '.options-select', function () {\n var selectedOptionValueId = $(this).children('option:selected').data('value-id');\n $('.modal.show .update-cart-url').data('selected-option', selectedOptionValueId);\n });\n\n $('body').on('click', '.update-cart-product-global', function (e) {\n e.preventDefault();\n\n var updateProductUrl = $(this).closest('.cart-and-ipay').find('.update-cart-url').val();\n var selectedQuantity = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('selected-quantity');\n var selectedOptionValueId = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('selected-option');\n var uuid = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('uuid');\n\n var form = {\n uuid: uuid,\n pid: base.getPidValue($(this)),\n quantity: selectedQuantity,\n selectedOptionValueId: selectedOptionValueId\n };\n\n $(this).parents('.card').spinner().start();\n\n $('body').trigger('cart:beforeUpdate');\n\n if (updateProductUrl) {\n $.ajax({\n url: updateProductUrl,\n type: 'post',\n context: this,\n data: form,\n dataType: 'json',\n success: function (data) {\n $('#editProductModal').modal('hide');\n\n $('.coupons-and-promos').empty().append(data.cartModel.totals.discountsHtml);\n updateCartTotals(data.cartModel);\n updateApproachingDiscounts(data.cartModel.approachingDiscounts);\n updateAvailability(data.cartModel, uuid);\n updateProductDetails(data, uuid);\n\n if (data.uuidToBeDeleted) {\n $('.uuid-' + data.uuidToBeDeleted).remove();\n }\n\n validateBasket(data.cartModel);\n\n $('body').trigger('cart:update', data);\n\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n }\n });\n\n base.selectAttribute();\n base.colorAttribute();\n base.removeBonusProduct();\n base.selectBonusProduct();\n base.enableBonusProductSelection();\n base.showMoreBonusProducts();\n base.addBonusProductsToCart();\n base.focusChooseBonusProductModal();\n base.trapChooseBonusProductModalFocus();\n base.onClosingChooseBonusProductModal();\n};\n","'use strict';\n\n/**\n * Validate whole form. Requires `this` to be set to form object\n * @param {jQuery.event} event - Event to be canceled if form is invalid.\n * @returns {boolean} - Flag to indicate if form is valid\n */\nfunction validateForm(event) {\n var valid = true;\n if (this.checkValidity && !this.checkValidity()) {\n // safari\n valid = false;\n if (event) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n }\n $(this).find('input, select').each(function () {\n if (!this.validity.valid) {\n $(this).trigger('invalid', this.validity);\n }\n });\n }\n return valid;\n}\n\n/**\n * Remove all validation. Should be called every time before revalidating form\n * @param {element} form - Form to be cleared\n * @returns {void}\n */\nfunction clearForm(form) {\n $(form).find('.form-control.is-invalid').removeClass('is-invalid');\n}\n\nmodule.exports = {\n invalid: function () {\n $('form input, form select').on('invalid', function (e) {\n e.preventDefault();\n this.setCustomValidity('');\n if (!this.validity.valid) {\n var validationMessage = this.validationMessage;\n $(this).addClass('is-invalid');\n if (this.validity.patternMismatch && $(this).data('pattern-mismatch')) {\n validationMessage = $(this).data('pattern-mismatch');\n }\n if ((this.validity.rangeOverflow || this.validity.rangeUnderflow)\n && $(this).data('range-error')) {\n validationMessage = $(this).data('range-error');\n }\n if ((this.validity.tooLong || this.validity.tooShort)\n && $(this).data('range-error')) {\n validationMessage = $(this).data('range-error');\n }\n if (this.validity.valueMissing && $(this).data('missing-error')) {\n validationMessage = $(this).data('missing-error');\n }\n $(this).parents('.form-group').find('.invalid-feedback')\n .text(validationMessage);\n }\n });\n },\n\n submit: function () {\n $('form').on('submit', function (e) {\n return validateForm.call(this, e);\n });\n },\n\n buttonClick: function () {\n $('form button[type=\"submit\"], form input[type=\"submit\"]').on('click', function () {\n // clear all errors when trying to submit the form\n clearForm($(this).parents('form'));\n });\n },\n\n functions: {\n validateForm: function (form, event) {\n validateForm.call($(form), event || null);\n },\n clearForm: clearForm\n }\n};\n","'use strict';\nmodule.exports = function () {\n var sizes = ['xs', 'sm', 'md', 'lg', 'xl'];\n\n sizes.forEach(function (size) {\n var selector = '.collapsible-' + size + ' .title';\n $('body').on('click', selector, function (e) {\n e.preventDefault();\n $(this).parents('.collapsible-' + size).toggleClass('active');\n\n if ($(this).parents('.collapsible-' + size).hasClass('active')) {\n $(this).attr('aria-expanded', true);\n } else {\n $(this).attr('aria-expanded', false);\n }\n });\n });\n};\n","'use strict';\n\n/**\n * Get cookie value by cookie name from browser\n * @param {string} cookieName - name of the cookie\n * @returns {string} cookie value of the found cookie name\n */\nfunction getCookie(cookieName) {\n var name = cookieName + '=';\n var decodedCookie = decodeURIComponent(document.cookie);\n var cookieArray = decodedCookie.split(';');\n for (var i = 0; i < cookieArray.length; i++) {\n var cookieItem = cookieArray[i];\n while (cookieItem.charAt(0) === ' ') {\n cookieItem = cookieItem.substring(1);\n }\n if (cookieItem.indexOf(name) === 0) {\n return cookieItem.substring(name.length, cookieItem.length);\n }\n }\n return '';\n}\n\nmodule.exports = function () {\n if ($('.valid-cookie-warning').length > 0) {\n var previousSessionID = window.localStorage.getItem('previousSid');\n var currentSessionID = getCookie('sid');\n if (!previousSessionID && currentSessionID) {\n // When a user first time visit the home page,\n // set the previousSessionID to currentSessionID\n // and Show the cookie alert\n previousSessionID = currentSessionID;\n window.localStorage.setItem('previousSid', previousSessionID);\n $('.cookie-warning-messaging').show();\n } else if (previousSessionID && previousSessionID === currentSessionID) {\n // Hide the cookie alert if user is in the same session\n $('.cookie-warning-messaging').hide();\n } else {\n // Clear the previousSessionID from localStorage\n // when user session is changed or expired\n window.localStorage.removeItem('previousSid');\n }\n }\n};\n","'use strict';\n\nvar keyboardAccessibility = require('./keyboardAccessibility');\n\nmodule.exports = function () {\n $('.country-selector a').click(function (e) {\n e.preventDefault();\n var action = $('.page').data('action');\n var localeCode = $(this).data('locale');\n var localeCurrencyCode = $(this).data('currencycode');\n var queryString = $('.page').data('querystring');\n var url = $('.country-selector').data('url');\n\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n data: {\n code: localeCode,\n queryString: queryString,\n CurrencyCode: localeCurrencyCode,\n action: action\n },\n success: function (response) {\n $.spinner().stop();\n if (response && response.redirectUrl) {\n window.location.href = response.redirectUrl;\n }\n },\n error: function () {\n $.spinner().stop();\n }\n });\n });\n\n keyboardAccessibility('.navbar-header .country-selector',\n {\n 40: function ($countryOptions) { // down\n if ($(this).is(':focus')) {\n $countryOptions.first().focus();\n } else {\n $(':focus').next().focus();\n }\n },\n 38: function ($countryOptions) { // up\n if ($countryOptions.first().is(':focus') || $(this).is(':focus')) {\n $(this).focus();\n $(this).removeClass('show');\n } else {\n $(':focus').prev().focus();\n }\n },\n 27: function () { // escape\n $(this).focus();\n $(this).removeClass('show').children('.dropdown-menu').removeClass('show');\n },\n 9: function () { // tab\n $(this).removeClass('show').children('.dropdown-menu').removeClass('show');\n }\n },\n function () {\n if (!($(this).hasClass('show'))) {\n $(this).addClass('show');\n }\n return $(this).find('.dropdown-country-selector').children('a');\n }\n );\n\n $('.navbar-header .country-selector').on('focusin', function () {\n $(this).addClass('show').children('.dropdown-menu').addClass('show');\n });\n};\n","'use strict';\n\nmodule.exports = {\n setTabNextFocus: function (focusParams) {\n var KEYCODE_TAB = 9;\n var isTabPressed = (focusParams.event.key === 'Tab' || focusParams.event.keyCode === KEYCODE_TAB);\n\n if (!isTabPressed) {\n return;\n }\n\n var firstFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.firstElementSelector);\n var lastFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.lastElementSelector);\n\n if ($(focusParams.containerSelector + ' ' + focusParams.lastElementSelector).is(':disabled')) {\n lastFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.nextToLastElementSelector);\n if ($('.product-quickview.product-set').length > 0) {\n var linkElements = $(focusParams.containerSelector + ' a#fa-link.share-icons');\n lastFocusableEl = linkElements[linkElements.length - 1];\n }\n }\n\n if (focusParams.event.shiftKey) /* shift + tab */ {\n if ($(':focus').is(firstFocusableEl)) {\n lastFocusableEl.focus();\n focusParams.event.preventDefault();\n }\n } else /* tab */ {\n if ($(':focus').is(lastFocusableEl)) { // eslint-disable-line\n firstFocusableEl.focus();\n focusParams.event.preventDefault();\n }\n }\n }\n};\n","'use strict';\n\n/**\n * Remove all validation. Should be called every time before revalidating form\n * @param {element} form - Form to be cleared\n * @returns {void}\n */\nfunction clearFormErrors(form) {\n $(form).find('.form-control.is-invalid').removeClass('is-invalid');\n}\n\nmodule.exports = function (formElement, payload) {\n // clear form validation first\n clearFormErrors(formElement);\n $('.alert', formElement).remove();\n\n if (typeof payload === 'object' && payload.fields) {\n Object.keys(payload.fields).forEach(function (key) {\n if (payload.fields[key]) {\n var feedbackElement = $(formElement).find('[name=\"' + key + '\"]')\n .parent()\n .children('.invalid-feedback');\n\n if (feedbackElement.length > 0) {\n if (Array.isArray(payload[key])) {\n feedbackElement.html(payload.fields[key].join(' '));\n } else {\n feedbackElement.html(payload.fields[key]);\n }\n feedbackElement.siblings('.form-control').addClass('is-invalid');\n }\n }\n });\n }\n if (payload && payload.error) {\n var form = $(formElement).prop('tagName') === 'FORM'\n ? $(formElement)\n : $(formElement).parents('form');\n\n form.prepend('
'\n + payload.error.join(' ') + '
');\n }\n};\n","'use strict';\n\nmodule.exports = function (selector, keyFunctions, preFunction) {\n $(selector).on('keydown', function (e) {\n var key = e.which;\n var supportedKeyCodes = [37, 38, 39, 40, 27];\n if (supportedKeyCodes.indexOf(key) >= 0) {\n e.preventDefault();\n }\n var returnedScope = preFunction.call(this);\n if (keyFunctions[key]) {\n keyFunctions[key].call(this, returnedScope);\n }\n });\n};\n","'use strict';\n\nvar cart = require('../cart/cart');\n\nvar updateMiniCart = true;\n\nmodule.exports = function () {\n cart();\n\n $('.minicart').on('count:update', function (event, count) {\n if (count && $.isNumeric(count.quantityTotal)) {\n $('.minicart .minicart-quantity').text(count.quantityTotal);\n $('.minicart .minicart-link').attr({\n 'aria-label': count.minicartCountOfItems,\n title: count.minicartCountOfItems\n });\n }\n });\n\n $('.minicart').on('mouseenter focusin touchstart', function () {\n if ($('.search:visible').length === 0) {\n return;\n }\n var url = $('.minicart').data('action-url');\n var count = parseInt($('.minicart .minicart-quantity').text(), 10);\n\n if (count !== 0 && $('.minicart .popover.show').length === 0) {\n if (!updateMiniCart) {\n $('.minicart .popover').addClass('show');\n return;\n }\n\n $('.minicart .popover').addClass('show');\n $('.minicart .popover').spinner().start();\n $.get(url, function (data) {\n $('.minicart .popover').empty();\n $('.minicart .popover').append(data);\n updateMiniCart = false;\n $.spinner().stop();\n });\n }\n });\n $('body').on('touchstart click', function (e) {\n if ($('.minicart').has(e.target).length <= 0) {\n $('.minicart .popover').removeClass('show');\n }\n });\n $('.minicart').on('mouseleave focusout', function (event) {\n if ((event.type === 'focusout' && $('.minicart').has(event.target).length > 0)\n || (event.type === 'mouseleave' && $(event.target).is('.minicart .quantity'))\n || $('body').hasClass('modal-open')) {\n event.stopPropagation();\n return;\n }\n $('.minicart .popover').removeClass('show');\n });\n $('body').on('change', '.minicart .quantity', function () {\n if ($(this).parents('.bonus-product-line-item').length && $('.cart-page').length) {\n location.reload();\n }\n });\n $('body').on('product:afterAddToCart', function () {\n updateMiniCart = true;\n });\n $('body').on('cart:update', function () {\n updateMiniCart = true;\n });\n};\n","'use strict';\nvar focusHelper = require('../components/focus');\n\n/**\n * Retrieves the relevant pid value\n * @param {jquery} $el - DOM container for a given add to cart button\n * @return {string} - value to be used when adding product to cart\n */\nfunction getPidValue($el) {\n var pid;\n\n if ($('#quickViewModal').hasClass('show') && !$('.product-set').length) {\n pid = $($el).closest('.modal-content').find('.product-quickview').data('pid');\n } else if ($('.product-set-detail').length || $('.product-set').length) {\n pid = $($el).closest('.product-detail').find('.product-id').text();\n } else {\n pid = $('.product-detail:not(\".bundle-item\")').data('pid');\n }\n\n return pid;\n}\n\n/**\n * Retrieve contextual quantity selector\n * @param {jquery} $el - DOM container for the relevant quantity\n * @return {jquery} - quantity selector DOM container\n */\nfunction getQuantitySelector($el) {\n var quantitySelected;\n if ($el && $('.set-items').length) {\n quantitySelected = $($el).closest('.product-detail').find('.quantity-select');\n } else if ($el && $('.product-bundle').length) {\n var quantitySelectedModal = $($el).closest('.modal-footer').find('.quantity-select');\n var quantitySelectedPDP = $($el).closest('.bundle-footer').find('.quantity-select');\n if (quantitySelectedModal.val() === undefined) {\n quantitySelected = quantitySelectedPDP;\n } else {\n quantitySelected = quantitySelectedModal;\n }\n } else {\n quantitySelected = $('.quantity-select');\n }\n return quantitySelected;\n}\n\n/**\n * Retrieves the value associated with the Quantity pull-down menu\n * @param {jquery} $el - DOM container for the relevant quantity\n * @return {string} - value found in the quantity input\n */\nfunction getQuantitySelected($el) {\n return getQuantitySelector($el).val();\n}\n\n/**\n * Process the attribute values for an attribute that has image swatches\n *\n * @param {Object} attr - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {Object[]} attr.values - Array of attribute value objects\n * @param {string} attr.values.value - Attribute coded value\n * @param {string} attr.values.url - URL to de/select an attribute value of the product\n * @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be\n * selected. If there is no variant that corresponds to a specific combination of attribute\n * values, an attribute may be disabled in the Product Detail Page\n * @param {jQuery} $productContainer - DOM container for a given product\n * @param {Object} msgs - object containing resource messages\n */\nfunction processSwatchValues(attr, $productContainer, msgs) {\n attr.values.forEach(function (attrValue) {\n var $attrValue = $productContainer.find('[data-attr=\"' + attr.id + '\"] [data-attr-value=\"' +\n attrValue.value + '\"]');\n var $swatchButton = $attrValue.parent();\n\n if (attrValue.selected) {\n $attrValue.addClass('selected');\n $attrValue.siblings('.selected-assistive-text').text(msgs.assistiveSelectedText);\n } else {\n $attrValue.removeClass('selected');\n $attrValue.siblings('.selected-assistive-text').empty();\n }\n\n if (attrValue.url) {\n $swatchButton.attr('data-url', attrValue.url);\n } else {\n $swatchButton.removeAttr('data-url');\n }\n\n // Disable if not selectable\n $attrValue.removeClass('selectable unselectable');\n\n $attrValue.addClass(attrValue.selectable ? 'selectable' : 'unselectable');\n });\n}\n\n/**\n * Process attribute values associated with an attribute that does not have image swatches\n *\n * @param {Object} attr - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {Object[]} attr.values - Array of attribute value objects\n * @param {string} attr.values.value - Attribute coded value\n * @param {string} attr.values.url - URL to de/select an attribute value of the product\n * @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be\n * selected. If there is no variant that corresponds to a specific combination of attribute\n * values, an attribute may be disabled in the Product Detail Page\n * @param {jQuery} $productContainer - DOM container for a given product\n */\nfunction processNonSwatchValues(attr, $productContainer) {\n var $attr = '[data-attr=\"' + attr.id + '\"]';\n var $defaultOption = $productContainer.find($attr + ' .select-' + attr.id + ' option:first');\n $defaultOption.attr('value', attr.resetUrl);\n\n attr.values.forEach(function (attrValue) {\n var $attrValue = $productContainer\n .find($attr + ' [data-attr-value=\"' + attrValue.value + '\"]');\n $attrValue.attr('value', attrValue.url)\n .removeAttr('disabled');\n\n if (!attrValue.selectable) {\n $attrValue.attr('disabled', true);\n }\n });\n}\n\n/**\n * Routes the handling of attribute processing depending on whether the attribute has image\n * swatches or not\n *\n * @param {Object} attrs - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {jQuery} $productContainer - DOM element for a given product\n * @param {Object} msgs - object containing resource messages\n */\nfunction updateAttrs(attrs, $productContainer, msgs) {\n // Currently, the only attribute type that has image swatches is Color.\n var attrsWithSwatches = ['color'];\n\n attrs.forEach(function (attr) {\n if (attrsWithSwatches.indexOf(attr.id) > -1) {\n processSwatchValues(attr, $productContainer, msgs);\n } else {\n processNonSwatchValues(attr, $productContainer);\n }\n });\n}\n\n/**\n * Updates the availability status in the Product Detail Page\n *\n * @param {Object} response - Ajax response object after an\n * attribute value has been [de]selected\n * @param {jQuery} $productContainer - DOM element for a given product\n */\nfunction updateAvailability(response, $productContainer) {\n var availabilityValue = '';\n var availabilityMessages = response.product.availability.messages;\n if (!response.product.readyToOrder) {\n availabilityValue = '
';\n });\n }\n });\n\n return html;\n}\n\n/**\n * @typedef UpdatedOptionValue\n * @type Object\n * @property {string} id - Option value ID for look up\n * @property {string} url - Updated option value selection URL\n */\n\n/**\n * @typedef OptionSelectionResponse\n * @type Object\n * @property {string} priceHtml - Updated price HTML code\n * @property {Object} options - Updated Options\n * @property {string} options.id - Option ID\n * @property {UpdatedOptionValue[]} options.values - Option values\n */\n\n/**\n * Updates DOM using post-option selection Ajax response\n *\n * @param {OptionSelectionResponse} optionsHtml - Ajax response optionsHtml from selecting a product option\n * @param {jQuery} $productContainer - DOM element for current product\n */\nfunction updateOptions(optionsHtml, $productContainer) {\n\t// Update options\n $productContainer.find('.product-options').empty().html(optionsHtml);\n}\n\n/**\n * Dynamically creates Bootstrap carousel from response containing images\n * @param {Object[]} imgs - Array of large product images,along with related information\n * @param {jQuery} $productContainer - DOM element for a given product\n */\nfunction createCarousel(imgs, $productContainer) {\n var carousel = $productContainer.find('.carousel');\n $(carousel).carousel('dispose');\n var carouselId = $(carousel).attr('id');\n $(carousel).empty().append('' + $(carousel).data('prev') + '' + $(carousel).data('next') + '');\n for (var i = 0; i < imgs.length; i++) {\n $('
').appendTo($(carousel).find('.carousel-inner'));\n $('').appendTo($(carousel).find('.carousel-indicators'));\n }\n $($(carousel).find('.carousel-item')).first().addClass('active');\n $($(carousel).find('.carousel-indicators > li')).first().addClass('active');\n if (imgs.length === 1) {\n $($(carousel).find('.carousel-indicators, a[class^=\"carousel-control-\"]')).detach();\n }\n $(carousel).carousel();\n $($(carousel).find('.carousel-indicators')).attr('aria-hidden', true);\n}\n\n/**\n * Parses JSON from Ajax call made whenever an attribute value is [de]selected\n * @param {Object} response - response from Ajax call\n * @param {Object} response.product - Product object\n * @param {string} response.product.id - Product ID\n * @param {Object[]} response.product.variationAttributes - Product attributes\n * @param {Object[]} response.product.images - Product images\n * @param {boolean} response.product.hasRequiredAttrsSelected - Flag as to whether all required\n * attributes have been selected. Used partially to\n * determine whether the Add to Cart button can be enabled\n * @param {jQuery} $productContainer - DOM element for a given product.\n */\nfunction handleVariantResponse(response, $productContainer) {\n var isChoiceOfBonusProducts =\n $productContainer.parents('.choose-bonus-product-dialog').length > 0;\n var isVaraint;\n if (response.product.variationAttributes) {\n updateAttrs(response.product.variationAttributes, $productContainer, response.resources);\n isVaraint = response.product.productType === 'variant';\n if (isChoiceOfBonusProducts && isVaraint) {\n $productContainer.parent('.bonus-product-item')\n .data('pid', response.product.id);\n\n $productContainer.parent('.bonus-product-item')\n .data('ready-to-order', response.product.readyToOrder);\n }\n }\n\n // Update primary images\n var primaryImageUrls = response.product.images.large;\n createCarousel(primaryImageUrls, $productContainer);\n\n // Update pricing\n if (!isChoiceOfBonusProducts) {\n var $priceSelector = $('.prices .price', $productContainer).length\n ? $('.prices .price', $productContainer)\n : $('.prices .price');\n $priceSelector.replaceWith(response.product.price.html);\n }\n\n // Update promotions\n $productContainer.find('.promotions').empty().html(response.product.promotionsHtml);\n\n updateAvailability(response, $productContainer);\n\n if (isChoiceOfBonusProducts) {\n var $selectButton = $productContainer.find('.select-bonus-product');\n $selectButton.trigger('bonusproduct:updateSelectButton', {\n product: response.product, $productContainer: $productContainer\n });\n } else {\n // Enable \"Add to Cart\" button if all required attributes have been selected\n $('button.add-to-cart, button.add-to-cart-global, button.update-cart-product-global').trigger('product:updateAddToCart', {\n product: response.product, $productContainer: $productContainer, resources: response.resources\n }).trigger('product:statusUpdate', response.product);\n }\n\n // Update attributes\n $productContainer.find('.main-attributes').empty()\n .html(getAttributesHtml(response.product.attributes));\n}\n\n/**\n * @typespec UpdatedQuantity\n * @type Object\n * @property {boolean} selected - Whether the quantity has been selected\n * @property {string} value - The number of products to purchase\n * @property {string} url - Compiled URL that specifies variation attributes, product ID, options,\n * etc.\n */\n\n/**\n * Updates the quantity DOM elements post Ajax call\n * @param {UpdatedQuantity[]} quantities -\n * @param {jQuery} $productContainer - DOM container for a given product\n */\nfunction updateQuantities(quantities, $productContainer) {\n if ($productContainer.parent('.bonus-product-item').length <= 0) {\n var optionsHtml = quantities.map(function (quantity) {\n var selected = quantity.selected ? ' selected ' : '';\n return '';\n }).join('');\n getQuantitySelector($productContainer).empty().html(optionsHtml);\n }\n}\n\n/**\n * updates the product view when a product attribute is selected or deselected or when\n * changing quantity\n * @param {string} selectedValueUrl - the Url for the selected variation value\n * @param {jQuery} $productContainer - DOM element for current product\n */\nfunction attributeSelect(selectedValueUrl, $productContainer) {\n if (selectedValueUrl) {\n $('body').trigger('product:beforeAttributeSelect',\n { url: selectedValueUrl, container: $productContainer });\n\n $.ajax({\n url: selectedValueUrl,\n method: 'GET',\n success: function (data) {\n handleVariantResponse(data, $productContainer);\n updateOptions(data.product.optionsHtml, $productContainer);\n updateQuantities(data.product.quantities, $productContainer);\n $('body').trigger('product:afterAttributeSelect',\n { data: data, container: $productContainer });\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n }\n}\n\n/**\n * Retrieves url to use when adding a product to the cart\n *\n * @return {string} - The provided URL to use when adding a product to the cart\n */\nfunction getAddToCartUrl() {\n return $('.add-to-cart-url').val();\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n *\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
').append($.parseHTML(html));\n\n var body = $html.find('.choice-of-bonus-product');\n var footer = $html.find('.modal-footer').children();\n\n return { body: body, footer: footer };\n}\n\n/**\n * Retrieves url to use when adding a product to the cart\n *\n * @param {Object} data - data object used to fill in dynamic portions of the html\n */\nfunction chooseBonusProducts(data) {\n $('.modal-body').spinner().start();\n\n if ($('#chooseBonusProductModal').length !== 0) {\n $('#chooseBonusProductModal').remove();\n }\n var bonusUrl;\n if (data.bonusChoiceRuleBased) {\n bonusUrl = data.showProductsUrlRuleBased;\n } else {\n bonusUrl = data.showProductsUrlListBased;\n }\n\n var htmlString = ''\n + '
'\n );\n setTimeout(function () {\n $('.add-to-basket-alert').remove();\n if ($('.cart-page').length) {\n location.reload();\n }\n }, 1500);\n }\n },\n error: function () {\n $.spinner().stop();\n }\n });\n });\n },\n\n getPidValue: getPidValue,\n getQuantitySelected: getQuantitySelected,\n miniCartReportingUrl: miniCartReportingUrl\n};\n","'use strict';\n\nmodule.exports = function (include) {\n if (typeof include === 'function') {\n include();\n } else if (typeof include === 'object') {\n Object.keys(include).forEach(function (key) {\n if (typeof include[key] === 'function') {\n include[key]();\n }\n });\n }\n};\n","'use strict';\n\nvar base = require('../product/base');\nvar focusHelper = require('base/components/focus');\nvar qtyInput = require('../components/quantityInput');\n\n/**\n * Show restricted products message.\n */\nfunction showRestrictedProductMessage() {\n $('.restricted-products').show();\n}\n/**\n * Hide restricted product message\n */\nfunction hideRestrictedProductsMessage() {\n $('.restricted-products').hide();\n}\n\n/**\n * Update restricted product message for each line item in cart.\n *\n * @param {Object} data - server response that should have restricted product UUID's array and restricted product\n * message for line item.\n */\nfunction updateProductRestrictions(data) {\n $('.card').each(function () {\n $(this).find('.restricted-message').remove();\n });\n hideRestrictedProductsMessage();\n for (var i = 0; i < data.restrictedProducts.length; i++) {\n var restrictedProductUUID = data.restrictedProducts[i];\n var pli = $('.uuid-' + restrictedProductUUID);\n var pliParentAttr = pli.find('.product-edit');\n var $restrictedProduct = $('
' + data.restrictedProductMessage + '
');\n $(pliParentAttr).prepend($restrictedProduct);\n }\n\n if ($('.success-ship-msg').length) {\n $('.success-ship-msg').remove();\n }\n\n if (data.restrictedProducts.length > 0) {\n showRestrictedProductMessage();\n $('.checkout-btn').addClass('disabled');\n $('.restricted-products-cart-message .content-asset').removeClass('d-none');\n }\n\n if (data.restrictedProducts.length === 0) {\n $('.restricted-products-cart-message .content-asset').addClass('d-none');\n $('.restricted-products-cart-message').append('
' + data.noRestrictedProductsMessage + '
');\n $('.checkout-btn').removeClass('disabled');\n }\n}\n\n/**\n * re-renders the order totals and the number of items in the cart\n * @param {Object} message - Error message to display\n */\nfunction createErrorNotification(message) {\n var errorHtml = '
' +\n '' + message + '
';\n\n $('.cart-error').empty();\n $('.cart-error').append(errorHtml);\n}\n\n/**\n * Initialize shipping restriction functionality\n */\nfunction shippingStatesInit() {\n $('.checkout-btn').addClass('disabled');\n\n $('.shipping-restrictions').on('click', function (e) {\n e.preventDefault();\n e.stopPropagation();\n var shippingRestrictionsUrl = $(this).attr('href');\n var stateCode = $('.shipping-states').find('select[name$=_stateCode],input[name$=_stateCode]').val();\n\n if (stateCode === '') {\n return;\n }\n\n $('.card').spinner().start();\n $('body').trigger('cart:beforeUpdate');\n\n if (stateCode) {\n $(this).parents('.card').spinner().start();\n $.ajax({\n url: shippingRestrictionsUrl + '?stateCode=' + stateCode,\n type: 'get',\n dataType: 'json',\n success: function (data) {\n updateProductRestrictions(data);\n\n $('body').trigger('cart:update', data);\n\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n }\n });\n}\n\n/**\n * appends params to a url\n * @param {string} url - Original url\n * @param {Object} params - Parameters to append\n * @returns {string} result url with appended parameters\n */\nfunction appendToUrl(url, params) {\n var newUrl = url;\n newUrl += (newUrl.indexOf('?') !== -1 ? '&' : '?') + Object.keys(params).map(function (key) {\n return key + '=' + encodeURIComponent(params[key]);\n }).join('&');\n\n return newUrl;\n}\n\n/**\n * Checks whether the basket is valid. if invalid displays error message and disables\n * checkout button\n * @param {Object} data - AJAX response from the server\n */\nfunction validateBasket(data) {\n if (data.valid.error) {\n if (data.valid.message) {\n var errorHtml = '
'\n );\n $('.number-of-items').empty().append(data.resources.numberOfItems);\n $('.minicart-quantity').empty().append(data.numItems);\n $('.minicart-link').attr({\n 'aria-label': data.resources.minicartCountOfItems,\n title: data.resources.minicartCountOfItems\n });\n $('.minicart .popover').empty();\n $('.minicart .popover').removeClass('show');\n }\n\n $('.checkout-btn').addClass('disabled');\n } else {\n $('.shipping-restrictions').click();\n }\n}\n\n/**\n * re-renders the order totals and the number of items in the cart\n * @param {Object} data - AJAX response from the server\n */\nfunction updateCartTotals(data) {\n $('.number-of-items').empty().append(data.resources.numberOfItems);\n $('.shipping-cost').empty().append(data.totals.totalShippingCost);\n $('.tax-total').empty().append(data.totals.totalTax);\n $('.grand-total').empty().append(data.totals.grandTotal);\n $('.sub-total').empty().append(data.totals.subTotal);\n $('.minicart-quantity').empty().append(data.numItems);\n $('.minicart-link').attr({\n 'aria-label': data.resources.minicartCountOfItems,\n title: data.resources.minicartCountOfItems\n });\n if (data.totals.orderLevelDiscountTotal.value > 0) {\n $('.order-discount').removeClass('hide-order-discount');\n $('.order-discount-total').empty()\n .append('- ' + data.totals.orderLevelDiscountTotal.formatted);\n } else {\n $('.order-discount').addClass('hide-order-discount');\n }\n\n if (data.totals.shippingLevelDiscountTotal.value > 0) {\n $('.shipping-discount').removeClass('hide-shipping-discount');\n $('.shipping-discount-total').empty().append('- ' +\n data.totals.shippingLevelDiscountTotal.formatted);\n } else {\n $('.shipping-discount').addClass('hide-shipping-discount');\n }\n\n data.items.forEach(function (item) {\n if (data.totals.orderLevelDiscountTotal.value > 0) {\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n }\n if (item.renderedPromotions) {\n $('.item-' + item.UUID).empty().append(item.renderedPromotions);\n } else {\n $('.item-' + item.UUID).empty();\n }\n $('.uuid-' + item.UUID + ' .unit-price').empty().append(item.renderedPrice);\n $('.line-item-price-' + item.UUID + ' .unit-price').empty().append(item.renderedPrice);\n $('.item-total-' + item.UUID).empty().append(item.priceTotal.renderedPrice);\n });\n}\n\n/**\n * re-renders the approaching discount messages\n * @param {Object} approachingDiscounts - updated approaching discounts for the cart\n */\nfunction updateApproachingDiscounts(approachingDiscounts) {\n var html = '';\n $('.approaching-discounts').empty();\n if (approachingDiscounts.length > 0) {\n approachingDiscounts.forEach(function (item) {\n html += '
'\n + item.discountMsg + '
';\n });\n }\n $('.approaching-discounts').append(html);\n}\n\n/**\n * Updates the availability of a product line item\n * @param {Object} data - AJAX response from the server\n * @param {string} uuid - The uuid of the product line item to update\n */\nfunction updateAvailability(data, uuid) {\n var lineItem;\n var messages = '';\n\n for (var i = 0; i < data.items.length; i++) {\n if (data.items[i].UUID === uuid) {\n lineItem = data.items[i];\n break;\n }\n }\n\n if (lineItem != null) {\n $('.availability-' + lineItem.UUID).empty();\n\n if (lineItem.availability) {\n if (lineItem.availability.messages) {\n lineItem.availability.messages.forEach(function (message) {\n messages += '
' + message + '
';\n });\n }\n\n if (lineItem.availability.inStockDate) {\n messages += '
'\n + lineItem.availability.inStockDate\n + '
';\n }\n }\n\n $('.availability-' + lineItem.UUID).html(messages);\n }\n}\n\n/**\n * Finds an element in the array that matches search parameter\n * @param {array} array - array of items to search\n * @param {function} match - function that takes an element and returns a boolean indicating if the match is made\n * @returns {Object|null} - returns an element of the array that matched the query.\n */\nfunction findItem(array, match) { // eslint-disable-line no-unused-vars\n for (var i = 0, l = array.length; i < l; i++) {\n if (match.call(this, array[i])) {\n return array[i];\n }\n }\n return null;\n}\n\n/**\n * Updates details of a product line item\n * @param {Object} data - AJAX response from the server\n * @param {string} uuid - The uuid of the product line item to update\n */\nfunction updateProductDetails(data, uuid) {\n $('.card.product-info.uuid-' + uuid).replaceWith(data.renderedTemplate);\n}\n\n/**\n * Generates the modal window on the first call.\n *\n */\nfunction getModalHtmlElement() {\n if ($('#editProductModal').length !== 0) {\n $('#editProductModal').remove();\n }\n var htmlString = ''\n + '
'\n + ''\n + '
'\n + ''\n + '
'\n + '
'\n + ' '\n + '
'\n + ''\n + ''\n + '
'\n + '
'\n + '
';\n $('body').append(htmlString);\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n *\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
').append($.parseHTML(html));\n\n var body = $html.find('.product-quickview');\n var footer = $html.find('.modal-footer').children();\n\n return { body: body, footer: footer };\n}\n\n/**\n * replaces the content in the modal window for product variation to be edited.\n * @param {string} editProductUrl - url to be used to retrieve a new product model\n */\nfunction fillModalElement(editProductUrl) {\n $('.modal-body').spinner().start();\n $.ajax({\n url: editProductUrl,\n method: 'GET',\n dataType: 'json',\n success: function (data) {\n var parsedHtml = parseHtml(data.renderedTemplate);\n $('#editProductModal .modal-body').empty();\n $('#editProductModal .modal-body').html(parsedHtml.body);\n $('#editProductModal .modal-footer').html(parsedHtml.footer);\n $('#editProductModal .modal-header .close .sr-only').text(data.closeButtonText);\n $('#editProductModal .enter-message').text(data.enterDialogMessage);\n $('#editProductModal .customShopInput').text(data.customShopFirearmDetails);\n $('#editProductModal').modal('show');\n $('body').trigger('editproductmodal:ready');\n $.spinner().stop();\n qtyInput.qvInit();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n}\n\n/**\n * replace content of modal\n * @param {string} actionUrl - url to be used to remove product\n * @param {string} productID - pid\n * @param {string} productName - product name\n * @param {string} uuid - uuid\n */\nfunction confirmDelete(actionUrl, productID, productName, uuid) {\n var $deleteConfirmBtn = $('.cart-delete-confirmation-btn');\n var $productToRemoveSpan = $('.product-to-remove');\n\n $deleteConfirmBtn.data('pid', productID);\n $deleteConfirmBtn.data('action', actionUrl);\n $deleteConfirmBtn.data('uuid', uuid);\n\n $productToRemoveSpan.empty().append(productName);\n}\n\n/**\n * Retrieve product options\n * @param {jQuery} el - current DOM element\n */\nfunction removeSavedItem(el) {\n $.ajax({\n url: el,\n method: 'GET',\n success: function (response) {\n if (!response.error) {\n $('.saved-products-list').html(response);\n $.spinner().stop();\n }\n },\n error: function () {\n $.spinner().stop();\n }\n });\n}\n\n/**\n * replace content of modal\n * @param {jQuery} el - current DOM element\n */\nfunction removeLineItem(el) {\n var productID = $(el).data('pid');\n var url = $(el).data('action');\n var uuid = $(el).data('uuid');\n var urlParams = {\n pid: productID,\n uuid: uuid\n };\n\n url = appendToUrl(url, urlParams);\n\n $('body > .modal-backdrop').remove();\n\n $.spinner().start();\n $('body').trigger('cart:beforeUpdate');\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n success: function (data) {\n if (data.basket.items.length === 0) {\n $('.cart').empty().append('
' +\n '
' +\n '
' + data.basket.resources.emptyCartMsg + '
' +\n '
' +\n '
'\n );\n $('.number-of-items').empty().append(data.basket.resources.numberOfItems);\n $('.minicart-quantity').empty().append(data.basket.numItems);\n $('.minicart-link').attr({\n 'aria-label': data.basket.resources.minicartCountOfItems,\n title: data.basket.resources.minicartCountOfItems\n });\n $('.minicart .popover').empty();\n $('.minicart .popover').removeClass('show');\n $('body').removeClass('modal-open');\n $('html').removeClass('veiled');\n } else {\n if (data.toBeDeletedUUIDs && data.toBeDeletedUUIDs.length > 0) {\n for (var i = 0; i < data.toBeDeletedUUIDs.length; i++) {\n $('.uuid-' + data.toBeDeletedUUIDs[i]).remove();\n }\n }\n $('.uuid-' + uuid).remove();\n if (!data.basket.hasBonusProduct) {\n $('.bonus-product').remove();\n }\n $('.coupons-and-promos').empty().append(data.basket.totals.discountsHtml);\n updateCartTotals(data.basket);\n updateApproachingDiscounts(data.basket.approachingDiscounts);\n $('body').trigger('setShippingMethodSelection', data.basket);\n validateBasket(data.basket);\n }\n\n $('body').trigger('cart:update', data);\n\n $.spinner().stop();\n\n if (data.shouldRefresh) {\n location.reload();\n }\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n}\n\n/**\n * Initialize shipping restriction functionality\n */\nfunction reinitPromo() {\n $('.promo-code-form').submit(function (e) {\n e.preventDefault();\n $.spinner().start();\n $('.coupon-missing-error').hide();\n $('.coupon-error-message').empty();\n if (!$('.coupon-code-field').val()) {\n $('.promo-code-form .form-control').addClass('is-invalid');\n $('.promo-code-form .form-control').attr('aria-describedby', 'missingCouponCode');\n $('.coupon-missing-error').show();\n $.spinner().stop();\n return false;\n }\n var $form = $('.promo-code-form');\n $('.promo-code-form .form-control').removeClass('is-invalid');\n $('.coupon-error-message').empty();\n $('body').trigger('promotion:beforeUpdate');\n\n $.ajax({\n url: $form.attr('action'),\n type: 'GET',\n dataType: 'json',\n data: $form.serialize(),\n success: function (data) {\n if (data.error) {\n $('.promo-code-form .form-control').addClass('is-invalid');\n $('.promo-code-form .form-control').attr('aria-describedby', 'invalidCouponCode');\n $('.coupon-error-message').empty().append(data.errorMessage);\n $('body').trigger('promotion:error', data);\n } else {\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n validateBasket(data);\n $('body').trigger('promotion:success', data);\n }\n $('.coupon-code-field').val('');\n $.spinner().stop();\n },\n error: function (err) {\n $('body').trigger('promotion:error', err);\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.errorMessage);\n $.spinner().stop();\n }\n }\n });\n return false;\n });\n}\n\n/**\n * Retrieve product options\n * @param {jQuery} el - current DOM element\n */\nfunction updateProductLineItems(el) {\n var cartShowUrl = $(el).data('action-cart');\n var removeSavedItemUrl = $(el).data('action-remove');\n $.ajax({\n url: cartShowUrl,\n method: 'GET',\n success: function (data) {\n $('.container.cart.cart-page').html(data);\n removeSavedItem(removeSavedItemUrl);\n reinitPromo();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n}\n\n/**\n * Retrieve product options\n *\n * @param {jQuery} $productContainer - DOM element for current product\n * @return {string} - Product options and their selected values\n */\nfunction getOptions($productContainer) {\n var options = $productContainer\n .find('.product-option')\n .map(function () {\n var $elOption = $(this).find('.options-select');\n var urlValue = $elOption.val();\n var selectedValueId = $elOption.find('option[value=\"' + urlValue + '\"]')\n .data('value-id');\n return {\n optionId: $(this).data('option-id'),\n selectedValueId: selectedValueId\n };\n }).toArray();\n\n return JSON.stringify(options);\n}\n\n/**\n * Retrieves the bundle product item ID's for the Controller to replace bundle master product\n * items with their selected variants\n *\n * @return {string[]} - List of selected bundle product item ID's\n */\nfunction getChildProducts() {\n var childProducts = [];\n $('.bundle-item').each(function () {\n childProducts.push({\n pid: $(this).find('.product-id').text(),\n quantity: parseInt($(this).find('label.quantity').data('quantity'), 10)\n });\n });\n\n return childProducts.length ? JSON.stringify(childProducts) : [];\n}\n\n/**\n * Updates the Mini-Cart quantity value after the customer has pressed the \"Add to Cart\" button\n * @param {string} response - ajax response from clicking the add to cart button\n */\nfunction handleSavedProductStatus(response) {\n $('.minicart').trigger('count:update', response);\n var messageType = response.error ? 'alert-danger' : 'alert-success';\n if ($('.add-to-cart-messages').length === 0) {\n $('body').append(\n ''\n );\n }\n\n $('.add-to-cart-messages').append(\n '
'\n + response.message\n + '
'\n );\n\n setTimeout(function () {\n $('.add-to-basket-alert').remove();\n }, 5000);\n}\n\nmodule.exports = function () {\n qtyInput.cartInit();\n qtyInput.bonusInit();\n\n $('body').on('click', '.remove-product', function (e) {\n e.preventDefault();\n\n var actionUrl = $(this).data('action');\n var productID = $(this).data('pid');\n var productName = $(this).data('name');\n var uuid = $(this).data('uuid');\n confirmDelete(actionUrl, productID, productName, uuid);\n });\n\n $('body').on('afterRemoveFromCart', function (e, data) {\n e.preventDefault();\n confirmDelete(data.actionUrl, data.productID, data.productName, data.uuid);\n });\n\n $('.optional-promo').click(function (e) {\n e.preventDefault();\n $('.promo-code-form').toggle();\n });\n\n $('body').on('click', '.cart-delete-confirmation-btn', function (e) {\n e.preventDefault();\n removeLineItem($(this));\n });\n\n $('body').on('change', '.quantity-form input[type=\"text\"]', function () {\n var $qtyInput = $(this).closest('.quantity-form').find('input[type=\"text\"]');\n var preSelectQty = $qtyInput.data('pre-select-qty');\n var quantity = $qtyInput.val();\n var productID = $qtyInput.data('pid');\n var url = $qtyInput.data('action');\n var uuid = $qtyInput.data('uuid');\n\n var urlParams = {\n pid: productID,\n quantity: quantity,\n uuid: uuid\n };\n url = appendToUrl(url, urlParams);\n\n $(this).parents('.card').spinner().start();\n $('body').trigger('cart:beforeUpdate');\n\n $('body').trigger('cart:beforeUpdate');\n\n $.ajax({\n url: url,\n type: 'get',\n context: this,\n dataType: 'json',\n success: function (data) {\n $('.quantity[data-uuid=\"' + uuid + '\"]').val(quantity);\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n updateAvailability(data, uuid);\n validateBasket(data);\n $(this).data('pre-select-qty', quantity);\n\n $('body').trigger('cart:update', data);\n\n $.spinner().stop();\n if (($(this).parents('.product-info').hasClass('bonus-product-line-item') && $('.cart-page').length) || data.shouldRefresh) {\n location.reload();\n }\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $(this).val(parseInt(preSelectQty, 10));\n $.spinner().stop();\n }\n }\n });\n });\n\n $('body').on('change', '.quantity-form-SFL input[type=\"text\"]', function (e) {\n e.preventDefault();\n var $qtyInput = $(this).closest('.quantity-form-SFL').find('input[type=\"text\"]');\n var preSelectQty = $qtyInput.data('pre-select-qty');\n var quantity = $qtyInput.val();\n var productID = $qtyInput.data('pid');\n var url = $qtyInput.data('action');\n var uuid = $qtyInput.data('uuid');\n\n var urlParams = {\n pid: productID,\n quantity: quantity,\n uuid: uuid\n };\n url = appendToUrl(url, urlParams);\n\n $(this).parents('.card').spinner().start();\n $('body').trigger('cart:beforeUpdate');\n\n $('body').trigger('cart:beforeUpdate');\n\n $.ajax({\n url: url,\n type: 'get',\n context: this,\n dataType: 'json',\n success: function (data) {\n $('.quantity[data-uuid=\"' + uuid + '\"]').val(quantity);\n $(this).data('pre-select-qty', quantity);\n data.savedItems.forEach(function (savedItem) {\n $('.line-item-total-price-amount.item-total-' + savedItem.UUID).empty().append(savedItem.priceTotal.price);\n });\n\n $('body').trigger('cart:update', data);\n\n $.spinner().stop();\n if (($(this).parents('.product-info').hasClass('bonus-product-line-item') && $('.cart-page').length) || data.shouldRefresh) {\n location.reload();\n }\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.msg);\n $(this).val(parseInt(preSelectQty, 10));\n $.spinner().stop();\n }\n }\n });\n });\n\n $('.shippingMethods').change(function () {\n var url = $(this).attr('data-actionUrl');\n var urlParams = {\n methodID: $(this).find(':selected').attr('data-shipping-id')\n };\n // url = appendToUrl(url, urlParams);\n\n $('.totals').spinner().start();\n $('body').trigger('cart:beforeShippingMethodSelected');\n $.ajax({\n url: url,\n type: 'post',\n dataType: 'json',\n data: urlParams,\n success: function (data) {\n if (data.error) {\n window.location.href = data.redirectUrl;\n } else {\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n validateBasket(data);\n }\n $('body').trigger('cart:shippingMethodSelected', data);\n $.spinner().stop();\n },\n error: function (err) {\n if (err.redirectUrl) {\n window.location.href = err.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n });\n\n $('body').on('click', '.remove-coupon', function (e) {\n e.preventDefault();\n\n var couponCode = $(this).data('code');\n var uuid = $(this).data('uuid');\n var $deleteConfirmBtn = $('.delete-coupon-confirmation-btn');\n var $productToRemoveSpan = $('.coupon-to-remove');\n\n $deleteConfirmBtn.data('uuid', uuid);\n $deleteConfirmBtn.data('code', couponCode);\n\n $productToRemoveSpan.empty().append(couponCode);\n });\n\n $('body').on('click', '.delete-coupon-confirmation-btn', function (e) {\n e.preventDefault();\n\n var url = $(this).data('action');\n var uuid = $(this).data('uuid');\n var couponCode = $(this).data('code');\n var urlParams = {\n code: couponCode,\n uuid: uuid\n };\n\n url = appendToUrl(url, urlParams);\n\n $('body > .modal-backdrop').remove();\n $('body').trigger('promotion:beforeUpdate');\n\n $.spinner().start();\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n success: function (data) {\n $('.coupon-uuid-' + uuid).remove();\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n validateBasket(data);\n $.spinner().stop();\n $('body').trigger('promotion:success', data);\n },\n error: function (err) {\n $('body').trigger('promotion:error', err);\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n });\n $('body').on('click', '.cart-page .bonus-product-button', function () {\n $.spinner().start();\n $(this).addClass('launched-modal');\n $.ajax({\n url: $(this).data('url'),\n method: 'GET',\n dataType: 'json',\n success: function (data) {\n base.methods.editBonusProducts(data);\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n });\n\n $('body').on('hidden.bs.modal', '#chooseBonusProductModal', function () {\n $('#chooseBonusProductModal').remove();\n $('.modal-backdrop').remove();\n $('body').removeClass('modal-open');\n\n if ($('.cart-page').length) {\n $('.launched-modal .btn-outline-primary').trigger('focus');\n $('.launched-modal').removeClass('launched-modal');\n } else {\n $('.product-detail .add-to-cart').focus();\n }\n });\n\n $('body').on('click', '.cart-page .product-edit .edit, .cart-page .bundle-edit .edit, .cart-page .product-line-item-options .edit', function (e) {\n e.preventDefault();\n\n var editProductUrl = $(this).attr('href');\n getModalHtmlElement();\n fillModalElement(editProductUrl);\n });\n\n $('body').on('shown.bs.modal', '#editProductModal', function () {\n $('#editProductModal').siblings().attr('aria-hidden', 'true');\n $('#editProductModal .close').focus();\n });\n\n $('body').on('hidden.bs.modal', '#editProductModal', function () {\n $('#editProductModal').siblings().attr('aria-hidden', 'false');\n });\n\n $('body').on('keydown', '#editProductModal', function (e) {\n var focusParams = {\n event: e,\n containerSelector: '#editProductModal',\n firstElementSelector: '.close',\n lastElementSelector: '.update-cart-product-global',\n nextToLastElementSelector: '.modal-footer .quantity-select'\n };\n focusHelper.setTabNextFocus(focusParams);\n });\n\n $('body').on('product:updateAddToCart', function (e, response) {\n // update global add to cart (single products, bundles)\n var dialog = $(response.$productContainer)\n .closest('.quick-view-dialog');\n\n $('.update-cart-product-global', dialog).attr('disabled',\n !$('.global-availability', dialog).data('ready-to-order')\n || !$('.global-availability', dialog).data('available')\n );\n });\n\n $('body').on('product:updateAvailability', function (e, response) {\n // bundle individual products\n $('.product-availability', response.$productContainer)\n .data('ready-to-order', response.product.readyToOrder)\n .data('available', response.product.available)\n .find('.availability-msg')\n .empty()\n .html(response.message);\n\n\n var dialog = $(response.$productContainer)\n .closest('.quick-view-dialog');\n\n if ($('.product-availability', dialog).length) {\n // bundle all products\n var allAvailable = $('.product-availability', dialog).toArray()\n .every(function (item) { return $(item).data('available'); });\n\n var allReady = $('.product-availability', dialog).toArray()\n .every(function (item) { return $(item).data('ready-to-order'); });\n\n $('.global-availability', dialog)\n .data('ready-to-order', allReady)\n .data('available', allAvailable);\n\n $('.global-availability .availability-msg', dialog).empty()\n .html(allReady ? response.message : response.resources.info_selectforstock);\n } else {\n // single product\n $('.global-availability', dialog)\n .data('ready-to-order', response.product.readyToOrder)\n .data('available', response.product.available)\n .find('.availability-msg')\n .empty()\n .html(response.message);\n }\n });\n\n $('body').on('product:afterAttributeSelect', function (e, response) {\n if ($('.modal.show .product-quickview .bundle-items').length) {\n $('.modal.show').find(response.container).data('pid', response.data.product.id);\n $('.modal.show').find(response.container).find('.product-id').text(response.data.product.id);\n } else {\n $('.modal.show .product-quickview').data('pid', response.data.product.id);\n }\n });\n\n $('body').on('change', '.quantity-select', function () {\n var selectedQuantity = $(this).val();\n $('.modal.show .update-cart-url').data('selected-quantity', selectedQuantity);\n });\n\n $('body').on('change', '.options-select', function () {\n var selectedOptionValueId = $(this).children('option:selected').data('value-id');\n $('.modal.show .update-cart-url').data('selected-option', selectedOptionValueId);\n });\n\n $('body').on('click', '.update-cart-product-global', function (e) {\n e.preventDefault();\n var updateProductUrl = $(this).closest('.cart-and-ipay').find('.update-cart-url').val();\n var selectedQuantity = $(this).closest('.row-qty-addtocart').find('.quantity-form input[type=\"text\"]').val();\n var selectedOptionValueId = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('selected-option');\n var uuid = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('uuid');\n\n var form = {\n uuid: uuid,\n pid: base.getPidValue($(this)),\n quantity: selectedQuantity,\n selectedOptionValueId: selectedOptionValueId\n };\n\n $(this).parents('.card').spinner().start();\n $('body').trigger('cart:beforeUpdate');\n if (updateProductUrl) {\n $.ajax({\n url: updateProductUrl,\n type: 'post',\n context: this,\n data: form,\n dataType: 'json',\n success: function (data) {\n $('#editProductModal').modal('hide');\n $('.coupons-and-promos').empty().append(data.cartModel.totals.discountsHtml);\n updateCartTotals(data.cartModel);\n updateApproachingDiscounts(data.cartModel.approachingDiscounts);\n updateAvailability(data.cartModel, uuid);\n updateProductDetails(data, uuid);\n\n if (data.uuidToBeDeleted) {\n $('.uuid-' + data.uuidToBeDeleted).remove();\n }\n\n validateBasket(data.cartModel);\n\n $('body').trigger('cart:update', data);\n\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $('#editProductModal').modal('hide');\n $.spinner().stop();\n }\n }\n });\n }\n });\n\n $('body').on('click', '.update-cart-product-SFL', function (e) {\n e.preventDefault();\n var updateProductUrl = $(this).closest('.cart-and-ipay').find('.update-cart-url').val();\n var selectedQuantity = $(this).closest('.row-qty-addtocart').find('.quantity-form input[type=\"text\"]').val();\n var selectedOptionValueId = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('selected-option');\n var uuid = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('uuid');\n var pid = $(this).closest('.modal-content').find('.product-quickview').data('pid');\n\n var form = {\n uuid: uuid,\n pid: pid,\n quantity: selectedQuantity,\n selectedOptionValueId: selectedOptionValueId\n };\n\n $(this).parents('.card').spinner().start();\n $('body').trigger('cart:beforeUpdate');\n if (updateProductUrl) {\n $.ajax({\n url: updateProductUrl,\n type: 'post',\n context: this,\n data: form,\n success: function (data) {\n $('#editProductModal').modal('hide');\n $('.saved-products-list').html(data);\n $('body').trigger('cart:update', data);\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.msg);\n $('#editProductModal').modal('hide');\n $.spinner().stop();\n }\n }\n });\n }\n });\n\n $('body').on('product:beforeAttributeSelect', function () {\n // Unslick the existing images to prepare them for direct js manipulation\n base.carouselUnslick();\n base.carouselUnslickBonus();\n });\n\n $('body').on('product:afterAttributeSelect', function () {\n base.carouselInit();\n base.carouselInitQuickview();\n });\n\n $('body').on('shown.bs.modal', '#editProductModal, #quickViewModal, #chooseBonusProductModal', function () {\n base.carouselInitBonus();\n base.carouselInitQuickview();\n });\n\n $('body').on('setShippingMethodSelection', function (e, basket) {\n var shippingMethodSelected = $('#shippingMethods option:selected');\n shippingMethodSelected.attr('data-shipping-id', basket.shipments[0].shippingMethodId);\n var shippingMethodName = basket.shipments[0].shippingMethods[0].displayName;\n if (basket.shipments[0].shippingMethods[0].estimatedArrivalTime && basket.shipments[0].shippingMethods[0].estimatedArrivalTime.length > 0) {\n shippingMethodName += ' ( ' + basket.shipments[0].shippingMethods[0].estimatedArrivalTime + ' )';\n }\n shippingMethodSelected.text(shippingMethodName);\n });\n\n $('body').on('click', '.add-to-save-for-later', function (e) {\n e.preventDefault();\n var url = $(this).attr('href');\n var quantity = $(this).closest('.product-info').find('.quantity-form input[type=\"text\"]').val();\n url = url + '&quantity=' + quantity;\n var element = $(this);\n $.spinner().stop();\n $.ajax({\n url: url,\n method: 'GET',\n success: function (response) {\n if (!response.error) {\n if (response.LoginRequired) {\n createErrorNotification(response.message);\n } else {\n $('.saved-products-list').html(response);\n var status = {};\n status.message = $('.status-message').data('message');\n status.error = $('.status-message').data('error');\n handleSavedProductStatus(status);\n removeLineItem(element);\n }\n }\n },\n error: function () {\n $.spinner().stop();\n }\n });\n });\n\n $(document).on('click', 'a.saved-list-ATC', function (e) {\n e.preventDefault();\n var addToCartUrl;\n var pid;\n var pidsObj;\n var setPids;\n\n $('body').trigger('product:beforeAddToCart', this);\n\n if ($('.set-items').length && $(this).hasClass('add-to-cart-global')) {\n setPids = [];\n\n $('.product-detail').each(function () {\n if (!$(this).hasClass('product-set-detail')) {\n setPids.push({\n pid: $(this).find('.product-id').text(),\n qty: $(this).find('.quantity-select').val(),\n options: getOptions($(this))\n });\n }\n });\n pidsObj = JSON.stringify(setPids);\n }\n\n pid = $(this).data('pid');\n\n var $productContainer = $(this).closest('.product-detail');\n if (!$productContainer.length) {\n $productContainer = $(this).closest('.quick-view-dialog').find('.product-detail');\n }\n var customInput = $productContainer.find('.customShopInput').val();\n var quantity = $(this).parents('.product-info').find('.quantity-select').val();\n addToCartUrl = $('.add-to-cart-url').val();\n var form = {\n pid: pid,\n pidsObj: pidsObj,\n childProducts: getChildProducts(),\n quantity: quantity,\n customInput: customInput\n };\n\n if (!$('.bundle-item').length) {\n form.options = getOptions($productContainer);\n }\n\n $(this).trigger('updateAddToCartFormData', form);\n $.spinner().start();\n var element = $(this);\n if (addToCartUrl) {\n $.ajax({\n url: addToCartUrl,\n method: 'POST',\n data: form,\n success: function (data) {\n $('body').trigger('product:afterAddToCart', data);\n base.handlePostCartAdd(data);\n if (data.error) {\n $.spinner().stop();\n return;\n }\n $('.coupons-and-promos').empty().append(data.cart.totals.discountsHtml);\n updateCartTotals(data.cart);\n updateApproachingDiscounts(data.cart.approachingDiscounts);\n updateProductLineItems(element);\n $('body').trigger('setShippingMethodSelection', data.cart);\n validateBasket(data.cart);\n },\n error: function () {\n $.spinner().stop();\n }\n });\n }\n });\n\n $('body').on('click', '.saved-list-remove', function (e) {\n e.preventDefault();\n var url = $(this).attr('href');\n $.spinner().start();\n removeSavedItem(url);\n });\n\n shippingStatesInit();\n reinitPromo();\n\n base.selectAttribute();\n base.colorAttribute();\n base.removeBonusProduct();\n base.selectBonusProduct();\n base.enableBonusProductSelection();\n base.showMoreBonusProducts();\n base.addBonusProductsToCart();\n base.focusChooseBonusProductModal();\n base.trapChooseBonusProductModalFocus();\n base.onClosingChooseBonusProductModal();\n};\n","'use strict';\nvar slickConfigs = require('../config/slickConfigs');\nvar util = require('../util/utils');\nvar imagesPerRow = $('#category-tiles-per-row').data('category-items') || 6;\nvar glide = require('org/config/glide');\nvar glideConfigs = require('site/config/glideConfigs');\n\n\n/**\n * Init globally reusable carousels\n */\n\nmodule.exports = {\n heroCarousels: function () {\n // prevents collision with content asset js\n // if ($('.hero-caro').length && !$('#hero-carousel').length) {\n // $('.hero-caro').on('init reInit', function (event, slick) {\n // slickConfigs.reinitPagination(event, slick, 1);\n // }).slick(slickConfigs.hero);\n // util.smartResize(function () {\n // slickConfigs.reinitPagination(event, $('.hero-caro').slick('getSlick'), 1);\n // });\n // }\n },\n blogProductTileCarousels: function () {\n // const $this = this;\n if ($('.product-tile-caro, .promotedProducts').length) {\n glide.sameClassMultiCarousel('.product-tile-caro', glideConfigs.recommendation);\n // many product tile sliders can exist on a page; loop through them to init individually\n // $('.product-tile-caro, .promotedProducts').not('.slick-initialized').each(function () {\n // var $slider = $(this);\n // var defaultConfig = slickConfigs.productTiles;\n // if ($slider.hasClass('recommendation')) {\n // defaultConfig.slide = '.grid-tile';\n // }\n\n // // each slider target only that slider's controls\n // var $sliderControls = $slider.next('.product-carousel-arrows');\n\n // // update slider config to use unique controls and prevent multiple sliders on same page from affecting each other\n // slickConfigs.productTiles.appendDots = $sliderControls.find('.slick-dots-custom');\n // slickConfigs.productTiles.prevArrow = $sliderControls.find('.slick-arrows-custom-prev');\n // slickConfigs.productTiles.nextArrow = $sliderControls.find('.slick-arrows-custom-next');\n\n // $slider.on('init reInit', function (event, slick) {\n // slickConfigs.reinitPagination(event, slick, 4);\n // }).slick(defaultConfig);\n\n // // watch DOM for PI recommendations content\n // if ($slider.hasClass('recommendation')) {\n // $this.observeEinsteinCarousels();\n // }\n\n // util.smartResize(function () {\n // slickConfigs.reinitPagination(event, $slider.slick('getSlick'), 4);\n // });\n // });\n }\n\n if ($('.product-tile-caro-mobile-hero').length) {\n $('.product-tile-caro-mobile-hero').not('.slick-initialized').each(function (idx) {\n var $slider = $(this);\n var sliderID = 'mobileTileSlider' + idx;\n var defaultConfig = slickConfigs.productTilesHeroMobile;\n $slider.attr('id', sliderID);\n var $sliderControls = $(`#${sliderID}`).next('.product-carousel-arrows');\n slickConfigs.productTilesHeroMobile.appendDots = $sliderControls.find('.slick-dots-custom');\n slickConfigs.productTilesHeroMobile.prevArrow = $sliderControls.find('.slick-arrows-custom-prev');\n slickConfigs.productTilesHeroMobile.nextArrow = $sliderControls.find('.slick-arrows-custom-next');\n\n $slider.on('init reInit', function (event, slick) {\n slickConfigs.reinitPagination(event, slick, 4);\n }).slick(slickConfigs.productTilesHeroMobile);\n\n util.smartResize(function () {\n slickConfigs.reinitPagination(event, $slider.slick('getSlick'), 4);\n });\n\n slickConfigs.productTilesHeroMobile = defaultConfig;\n });\n }\n },\n observeEinsteinCarousels: function () {\n // declare constants\n const einsteinSelector = '[id^=\"cq_recomm_slot\"]';\n const einsteinElements = document.querySelectorAll(einsteinSelector);\n const $this = this;\n // callback\n const mutationCallback = (mutationList, observer) => {\n if (mutationList.length) {\n observer.disconnect();\n $this.blogProductTileCarousels();\n }\n };\n // if we have einstein elements\n if (einsteinElements.length > 0) {\n einsteinElements.forEach((el) => {\n var observer = new MutationObserver(mutationCallback);\n observer.observe(el, { childList: true });\n });\n }\n },\n categorySubFolder: function () {\n $('.category-subfolder-tile-caro').on('init reInit', function (event, slick) {\n slickConfigs.reinitPagination(event, slick, imagesPerRow);\n }).slick(slickConfigs.categorySubFolderTiles);\n util.smartResize(function () {\n slickConfigs.reinitPagination(event, $('.category-subfolder-tile-caro').slick('getSlick'), imagesPerRow);\n });\n },\n productRelatedContent: function () {\n $('.product-related-content-tile-caro').slick(slickConfigs.productRelatedContent);\n },\n caroMobileOnly: function () {\n var $caroMobileOnly = $('.caro-mobile-only');\n var unslick = function () {\n if ($caroMobileOnly.hasClass('slick-initialized')) {\n $caroMobileOnly.slick('unslick');\n }\n };\n if ($caroMobileOnly.length) {\n util.smartResize(function () {\n if (util.mediaBreakpointDown('md')) {\n $caroMobileOnly.not('.slick-initialized').slick(slickConfigs.caroMobileOnly);\n } else {\n unslick();\n }\n });\n }\n }\n};\n","'use strict';\n\nvar baseClientSideValidation = require('base/components/clientSideValidation');\n\nvar exportClientSideValidation = $.extend(baseClientSideValidation, {\n invalid: function () {\n $('form input, form select, form textarea').on('invalid', function (e) {\n e.preventDefault();\n this.setCustomValidity('');\n if (!this.validity.valid) {\n var validationMessage = this.validationMessage;\n $(this).addClass('is-invalid');\n $(this).closest('.form-group').addClass('is-invalid');\n $(this).siblings('label').addClass('is-invalid');\n if (this.validity.patternMismatch && $(this).data('pattern-mismatch')) {\n validationMessage = $(this).data('pattern-mismatch');\n }\n if ((this.validity.rangeOverflow || this.validity.rangeUnderflow)\n && $(this).data('range-error')) {\n validationMessage = $(this).data('range-error');\n }\n if ((this.validity.tooLong || this.validity.tooShort)\n && $(this).data('range-error')) {\n validationMessage = $(this).data('range-error');\n }\n if (this.validity.valueMissing && $(this).data('missing-error')) {\n validationMessage = $(this).data('missing-error');\n }\n $(this).parents('.form-group').find('.invalid-feedback')\n .text(validationMessage);\n }\n });\n },\n clearErrorsOnChange: function () {\n $(':input, .form-control').on('change input', function () {\n if ($(this).hasClass('is-invalid')) {\n if ($(this).hasClass('addressType')) {\n $(document).find('input.addressType').removeClass('is-invalid');\n }\n $(this).removeClass('is-invalid');\n $(this).closest('.form-group').removeClass('is-invalid');\n $(this).closest('label').removeClass('is-invalid');\n $(this).closest('.invalid-feedback').remove();\n }\n });\n }\n});\n\nmodule.exports = exportClientSideValidation;\n","'use strict';\n\n/**\n * Renders a modal window that will track the users consenting to accepting site tracking policy\n */\nfunction showConsentModal() {\n if (!$('.tracking-consent').data('caonline')) {\n return;\n }\n var urlContent = $('.tracking-consent').data('url');\n var urlAccept = $('.tracking-consent').data('accept');\n var htmlString = '';\n\n $('body').append(htmlString);\n\n $.ajax({\n url: urlContent,\n type: 'get',\n dataType: 'html',\n success: function (response) {\n $('#consent-tracking').html(response);\n $('#consent-tracking').find('.accept-cookie-btn').attr('data-url', urlAccept);\n\n $('.accept-cookie-btn').on('click', function (e) {\n e.preventDefault();\n var url = $(this).data('url');\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n success: function () {\n $('#consent-tracking').remove();\n $.spinner().stop();\n },\n error: function () {\n $('#consent-tracking').remove();\n $.spinner().stop();\n }\n });\n });\n },\n error: function () {\n $('#consent-tracking').remove();\n }\n });\n}\n\n/**\n * Function to set consent tracking for OneTrust banner\n * @param {boolean} trackingConsent - Tracking Consent.\n */\nfunction oneTrustCookieConsent(trackingConsent) {\n var urlAccept = $('.tracking-consent').data('accept');\n var urlReject = $('.tracking-consent').data('reject');\n var consentURL = (trackingConsent ? urlAccept : urlReject);\n\n try {\n $.ajax({\n url: consentURL,\n type: 'get',\n dataType: 'json'\n });\n } catch (e) { // eslint-disable-line\n }\n}\n\nmodule.exports = function () {\n if ($('.consented').length === 0 && $('.tracking-consent').hasClass('api-true')) {\n showConsentModal();\n }\n\n if ($('.tracking-consent').hasClass('api-true')) {\n $('.tracking-consent').click(function () {\n showConsentModal();\n });\n }\n\n $('body').on('click', '#onetrust-reject-all-handler', function (e) {\n e.preventDefault();\n oneTrustCookieConsent(false);\n });\n\n $('body').on('click', '#onetrust-accept-btn-handler', function (e) {\n e.preventDefault();\n oneTrustCookieConsent(true);\n });\n};\n","'use strict';\n\n/**\n * einsteinObserver\n * Initialize an observer for adding a callback to Einstein DOM element injection.\n * @param {string} selector - the selector to observe for changes\n * @param {function} fn - the callback for when changes are found\n */\nvar einsteinObserver = function (selector, fn) {\n var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;\n var einsteinSelector = '[id^=\"cq_recomm_slot\"]';\n var elements = window.document.querySelectorAll(selector);\n var check = function (mutationList, observer) {\n if (mutationList.length) {\n // if new child detected, disconnect observer and call callback\n observer.disconnect();\n fn.call();\n }\n };\n\n var initializeElement = function (element) {\n var einsteinElement = element.querySelector(einsteinSelector);\n if (einsteinElement === null) {\n // not an einstein recommender slot\n if (element.childElementCount > 0) {\n // if child, call callback immediately\n fn.call();\n }\n } else if (einsteinElement.childElementCount > 0) {\n // if child, call callback immediately\n fn.call();\n } else {\n // if no child, attach observer and wait for child to be added\n var observer = new MutationObserver(check);\n observer.observe(einsteinElement, { childList: true });\n }\n };\n\n // check each element for child\n for (var i = 0; i < elements.length; i++) {\n initializeElement(elements[i]);\n }\n};\n\nmodule.exports = { einsteinObserver: einsteinObserver };\n","'use strict';\n\nvar carousels = require('./carousels');\n\nvar einsteinObserver = require('./einstein').einsteinObserver;\nvar baseProductTileCarousels = carousels.blogProductTileCarousels;\n\nmodule.exports = function () {\n einsteinObserver('.einstein-slot', baseProductTileCarousels);\n};\n","'use strict';\n\nvar util = require('lyonscg/util/utils.js');\nvar formValidation = require('base/components/formValidation');\n\n/**\n * Generates the modal window on the first call.\n *\n */\nfunction getModalHtmlElement() {\n if ($('.modal-email-signup').length !== 0) {\n $('.modal-email-signup').remove();\n }\n\n var htmlString = ''\n + '
'\n + '
'\n + ''\n + '
'\n + '
'\n + ' '\n + '
'\n + ''\n + ''\n + '
'\n + '
'\n + '
';\n $('body').append(htmlString);\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n *\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
').append($.parseHTML(html));\n\n var body = $html.find('.email-modal');\n\n return { body: body };\n}\n\n/**\n * Replaces the content in the modal window for email updates and offers modal.\n * @param {string} modalUrl - url to be used to retrieve a email optin model\n */\nfunction fillModalElement(modalUrl) {\n $('.modal-content').spinner().start();\n\n $.ajax({\n url: modalUrl,\n method: 'GET',\n dataType: 'html',\n success: function (html) {\n var parsedHtml = parseHtml(html);\n\n parsedHtml.body.removeClass('email-modal');\n $('.modal-email-signup .modal-body').empty();\n $('.modal-email-signup .modal-body').html(parsedHtml.body);\n $('.modal-email-signup .modal-footer').html(parsedHtml.footer);\n $('.modal-email-signup').modal('show');\n $('#email-signup-form-email').val($('#newsletterEmailAddress').val());\n $('#newsletterEmailAddress').val('');\n $('#email-signup-form-firstname').focus();\n $('.modal-content').spinner().stop();\n },\n error: function () {\n $('.modal-content').spinner().stop();\n }\n });\n}\n\nmodule.exports = function () {\n $('#newsletterSignup').on('submit', function (e) {\n e.preventDefault();\n\n var modalUrl = $(this).attr('action');\n getModalHtmlElement();\n fillModalElement(modalUrl);\n });\n\n $(document).on('change blur', '#email-signup-form-email, #email-signup-form-emailconfirm', function () {\n var $confirmInput = $('#email-signup-form-emailconfirm');\n if ($('#email-signup-form-email').val() !== $('#email-signup-form-emailconfirm').val()) {\n $('.newsletterSubscribeButton').attr('disabled', 'disabled');\n\n // show error\n $confirmInput.addClass('is-invalid');\n $confirmInput.closest('.form-group').addClass('is-invalid');\n $confirmInput.parents('.form-group').find('.invalid-feedback')\n .text($confirmInput.data('pattern-mismatch'));\n } else {\n // remove error\n $confirmInput.removeClass('is-invalid');\n $confirmInput.closest('.form-group').removeClass('is-invalid');\n $confirmInput.parents('.form-group').find('.invalid-feedback')\n .text('');\n }\n });\n\n $(document).on('change', 'form.newsletterSubscribe input', function () {\n var emptyInput = false;\n $('form.newsletterSubscribe input:not(\"#email-signup-form-firstname\"):not(\"#email-signup-form-lastname\")').each(function () {\n if ($(this).val() === '') {\n emptyInput = true;\n }\n });\n if (emptyInput) {\n $('.newsletterSubscribeButton').attr('disabled', 'disabled');\n } else if ($('form.newsletterSubscribe #email-signup-form-termsconditions').is(':checked')) {\n if ($('#email-signup-form-email').val() === $('#email-signup-form-emailconfirm').val()) {\n $('.newsletterSubscribeButton').removeAttr('disabled');\n }\n } else {\n $('.newsletterSubscribeButton').attr('disabled', 'disabled');\n }\n });\n\n $(document).on('submit', 'form.newsletterSubscribe', function (e) {\n var form = $(this);\n e.preventDefault();\n var url = form.attr('action');\n $('.modal-email-signup').spinner().start();\n $.ajax({\n url: url,\n type: 'post',\n dataType: 'json',\n data: form.serialize(),\n success: function (data) {\n $('.modal-email-signup').spinner().stop();\n if (!data.success) {\n if (data.errorMessage) {\n $('form.newsletterSubscribe').html('
';\n\n $('form.newsletterSubscribe').html(successHTML);\n }\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n }\n\n $('.modal-email-signup').spinner().stop();\n }\n });\n return false;\n });\n\n $('.footer-item h1').on('click', function () {\n if (util.mediaBreakpointDown('md')) {\n $(this).parents('.footer-item').toggleClass('active');\n }\n });\n};\n","'use strict';\n/* Unified Modal creation and handling functionality */\nvar modal = require('org/util/modal');\n\n/**\n * Function to observe the consent popUp is hide so we can shift down the geoLocation popup.\n */\nfunction observeConsentPopup() {\n var observer = new MutationObserver(function (mutations) {\n mutations.forEach(function (mutationRecord) { // eslint-disable-line no-unused-vars\n $('#geo-redirect-modal .modal-dialog .modal-content').css('bottom', '0');\n if (window.matchMedia('(max-width: 767px)').matches) {\n $('#geo-redirect-modal .modal-dialog .modal-content').css({ position: 'absolute', bottom: '65px' });\n }\n });\n });\n var target = document.getElementById('onetrust-banner-sdk');\n observer.observe(target, { attributes: true, attributeFilter: ['style'] });\n}\n\n\nmodule.exports = function () {\n $('document').ready(function () {\n var url = $('#geoPopupURL').data('url');\n\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n data: {},\n success: function (response) {\n if (response.displayModal) {\n modal.create(response.renderedTemplate);\n }\n }\n });\n });\n $('body').on('click', '#geoPopupCloseBtn', function () {\n $('#geo-redirect-modal').modal('hide');\n }).on('change', '#geoCountrySelector', function () {\n let reqURL = $('#reqURL').val();\n let selectedCountryCode = $(this).val();\n let redirectURL;\n $.ajax({\n url: reqURL,\n type: 'post',\n dataType: 'json',\n data: { selectedCountryCode: selectedCountryCode },\n success: (res) => {\n if (res.redirectURL !== '') {\n redirectURL = res.redirectURL;\n window.location.replace(redirectURL);\n } else {\n $('#geo-redirect-modal').modal('hide');\n window.location.reload();\n }\n }\n });\n });\n\n $(window).on('load', function () {\n setTimeout(function () {\n try {\n observeConsentPopup();\n } catch (e) { // eslint-disable-line\n }\n }, 500);\n });\n};\n","'use strict';\n\nvar keyboardAccessibility = require('base/components/keyboardAccessibility');\n\nvar clearSelection = function (element) {\n $(element).closest('.dropdown, .dropdown-item').children('.dropdown-menu, .menu-list').children('.top-category')\n .detach();\n $(element).closest('.dropdown.show, .dropdown-item.show').children('.nav-link').attr('aria-expanded', 'false');\n $(element).closest('.dropdown.show, .dropdown-item.show, .menu-caret.show').siblings('.hide').removeClass('hide');\n $(element).closest('.menu-list').siblings().find('dropdown-item')\n .removeClass('hide');\n $(element).closest('.menu-column').siblings().find('.dropdown-item')\n .removeClass('hide');\n\n if ($(element).closest('.dropdown-menu')\n .children('.nav-menu')\n .children('.close-menu')\n .children('.back')\n .get(0) === $(element).get(0)) {\n $(element).closest('.navbar').children('.nav-menu').removeClass('hide');\n } else {\n $(element).closest('.dropdown-menu').children('.nav-menu').removeClass('hide');\n }\n\n $(element).closest('.dropdown.show, .dropdown-item.show, .menu-caret.show').removeClass('show');\n $(element).closest('li').detach();\n};\n\nmodule.exports = function () {\n var isDesktop = function (element) {\n return $(element).parents('.menu-toggleable-left').css('position') !== 'fixed';\n };\n\n $('.header-banner .close').on('click', function () {\n $('.header-banner').addClass('hide');\n });\n\n keyboardAccessibility('.main-menu .nav-link, .main-menu .dropdown-link',\n {\n 40: function (menuItem) { // down\n if (menuItem.hasClass('nav-item')) { // top level\n $('.navbar-nav .show').removeClass('show')\n .children('.dropdown-menu')\n .removeClass('show');\n menuItem.addClass('show').children('.dropdown-menu').addClass('show');\n $(this).attr('aria-expanded', 'true');\n menuItem.find('ul > li > a')\n .first()\n .focus();\n } else {\n menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');\n $(this).attr('aria-expanded', 'false');\n menuItem.next().children().first().focus();\n }\n },\n 39: function (menuItem) { // right\n if (menuItem.hasClass('nav-item')) { // top level\n menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');\n $(this).attr('aria-expanded', 'false');\n menuItem.next().children().first().focus();\n } else if (menuItem.hasClass('dropdown')) {\n menuItem.addClass('show').children('.dropdown-menu').addClass('show');\n $(this).attr('aria-expanded', 'true');\n menuItem.find('ul > li > a')\n .first()\n .focus();\n }\n },\n 38: function (menuItem) { // up\n if (menuItem.hasClass('nav-item')) { // top level\n menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');\n $(this).attr('aria-expanded', 'false');\n } else if (menuItem.prev().length === 0) {\n menuItem.parent().parent().removeClass('show')\n .children('.nav-link')\n .attr('aria-expanded', 'false');\n menuItem.parent().parent().children().first()\n .focus();\n } else {\n menuItem.prev().children().first().focus();\n }\n },\n 37: function (menuItem) { // left\n if (menuItem.hasClass('nav-item')) { // top level\n menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');\n $(this).attr('aria-expanded', 'false');\n menuItem.prev().children().first().focus();\n } else {\n menuItem.closest('.show').removeClass('show')\n .closest('li.show').removeClass('show')\n .children()\n .first()\n .focus()\n .attr('aria-expanded', 'false');\n }\n },\n 27: function (menuItem) { // escape\n var parentMenu = menuItem.hasClass('show')\n ? menuItem\n : menuItem.closest('li.show');\n parentMenu.children('.show').removeClass('show');\n parentMenu.removeClass('show').children('.nav-link')\n .attr('aria-expanded', 'false');\n parentMenu.children().first().focus();\n }\n },\n function () {\n return $(this).parent();\n }\n );\n\n $('.dropdown:not(.disabled) [data-toggle=\"dropdown\"], a.has-sub-cat')\n .on('click', function (e) {\n if (!isDesktop(this)) {\n $('.modal-background').show();\n // copy parent element into current UL\n var li = $('');\n var link = $(this).clone().removeClass('dropdown-toggle')\n .removeAttr('data-toggle')\n .removeAttr('aria-expanded')\n .attr('aria-haspopup', 'false');\n li.append(link);\n\n var closeMenu = $('');\n closeMenu.append(li);\n closeMenu.append($('.close-menu').first().clone());\n\n if ($(this).hasClass('has-sub-cat')) {\n if (!$(this).closest('.dropdown-item').find('> .menu-list').children('.nav-menu').length) {\n $(this).closest('.dropdown-item').find('> .menu-list')\n .prepend(closeMenu);\n }\n\n $(this).closest('.dropdown-item').find('.hide').removeClass('hide');\n $(this).closest('.dropdown-item').find('.show').removeClass('show');\n\n // copy navigation menu into view\n $(this).closest('.dropdown-item').addClass('show');\n $(this).closest('.dropdown-item').siblings()\n .removeClass('show')\n .addClass('hide');\n $(this).closest('.menu-list').siblings().find('.dropdown-item')\n .removeClass('show')\n .addClass('hide');\n $(this).closest('.menu-column').siblings().find('.dropdown-item')\n .removeClass('show')\n .addClass('hide');\n $(this).closest('.dropdown-menu').children('.nav-menu').addClass('hide');\n } else {\n if (!$(this).parent().children('.dropdown-menu').children('.nav-menu').length) {\n $(this).parent().children('.dropdown-menu')\n .prepend(closeMenu);\n }\n\n $(this).parent().find('.hide').removeClass('hide');\n $(this).parent().find('.show').removeClass('show');\n\n // copy navigation menu into view\n $(this).parent().addClass('show');\n $(this).parent().siblings(':not(.content-item, .account-mobile-nav)')\n .removeClass('show')\n .addClass('hide');\n $(this).closest('.navbar').children('.nav-menu')\n .removeClass('show')\n .addClass('hide');\n }\n\n $(this).attr('aria-expanded', 'true');\n e.preventDefault();\n }\n })\n .on('mouseenter', function () {\n if (isDesktop(this)) {\n var eventElement = this;\n $('.navbar-nav > li').each(function () {\n if (!$.contains(this, eventElement)) {\n $(this).find('.show').each(function () {\n clearSelection(this);\n });\n if ($(this).hasClass('show')) {\n $(this).removeClass('show');\n $(this).children('ul.dropdown-menu').removeClass('show');\n $(this).children('.nav-link').attr('aria-expanded', 'false');\n }\n }\n });\n // need to close all the dropdowns that are not direct parent of current dropdown\n $(this).parent().addClass('show');\n $(this).siblings('.dropdown-menu').addClass('show');\n $(this).siblings('.menu-caret').addClass('show');\n $(this).attr('aria-expanded', 'true');\n }\n })\n .parent()\n .on('mouseleave', function () {\n if (isDesktop(this)) {\n $(this).removeClass('show');\n $(this).children('.dropdown-menu').removeClass('show');\n $(this).children('.menu-caret').removeClass('show');\n $(this).children('.nav-link').attr('aria-expanded', 'false');\n }\n });\n\n $('.navbar>.close-menu>.close-button').on('click', function (e) {\n e.preventDefault();\n $('.menu-toggleable-left').removeClass('in');\n $('.modal-background').hide();\n });\n\n $('.navbar-nav').on('click', '.back', function (e) {\n e.preventDefault();\n clearSelection(this);\n });\n\n $('.navbar-nav').on('click', '.close-button', function (e) {\n e.preventDefault();\n $('.navbar-nav').find('.top-category').detach();\n $('.navbar-nav').find('.nav-menu').detach();\n $('.navbar-nav').find('.show').removeClass('show');\n $('.navbar').find('.hide').removeClass('hide');\n $('.menu-toggleable-left').removeClass('in');\n $('.modal-background').hide();\n });\n\n $('.navbar-toggler').click(function (e) {\n e.preventDefault();\n $('.main-menu').toggleClass('in');\n $('.modal-background').show();\n });\n\n $('.header-search-svg').click(function (e) {\n e.preventDefault();\n $('.search-mobile ').toggle();\n });\n\n keyboardAccessibility('.navbar-header .user',\n {\n 40: function ($popover) { // down\n if ($popover.children('a').first().is(':focus')) {\n $popover.children('a').first().next().focus();\n } else {\n $popover.children('a').first().focus();\n }\n },\n 38: function ($popover) { // up\n if ($popover.children('a').first().is(':focus')) {\n $(this).focus();\n $popover.removeClass('show');\n } else {\n $popover.children('a').first().focus();\n }\n },\n 27: function ($popover) { // escape\n $(this).focus();\n $popover.removeClass('show');\n },\n 9: function ($popover) { // tab\n $popover.removeClass('show');\n }\n },\n function () {\n var $popover = $('.user .popover');\n if (!($popover.hasClass('show'))) {\n $popover.addClass('show');\n }\n return $popover;\n }\n );\n\n $('.navbar-header .user').on('mouseenter focusin', function () {\n if ($('.navbar-header .user .popover').length > 0) {\n $('.navbar-header .user .popover').addClass('show');\n }\n });\n\n $('.navbar-header .user').on('mouseleave', function () {\n $('.navbar-header .user .popover').removeClass('show');\n });\n\n $('.send-emailVerification-mail').on('click', function () {\n var url = $(this).attr('data-actionUrl');\n\n $('body').spinner().start();\n\n $.ajax({\n url: url,\n type: 'GET',\n success: function (data) {\n if (!data.error) {\n $('.send-emailVerification-mail').addClass('hidden');\n $('.sent-emailVerification-mail').removeClass('hidden');\n }\n $.spinner().stop();\n }\n });\n });\n\n $('.header-search-svg').on('click', ()=>{\n if ($('.search-mobile')) {\n if ($('.search-mobile').hasClass('d-none')) {\n $('.search-mobile').removeClass('d-none');\n } else {\n $('.search-mobile').addClass('d-none');\n }\n }\n });\n};\n","'use strict';\n\nvar wishPlugin = require('wishlists/components/miniCart');\nvar cart = require('../cart/cart');\nvar qtyInput = require('../components/quantityInput');\nvar utils = require('lyonscg/util/utils');\n\nvar updateMiniCart = true;\n\nmodule.exports = function () {\n cart();\n wishPlugin.moveToWishlist();\n\n $('.minicart').on('count:update', function (event, count) {\n if (count && $.isNumeric(count.quantityTotal)) {\n $('.minicart .minicart-quantity').text(count.quantityTotal);\n $('.minicart .minicart-link').attr({\n 'aria-label': count.minicartCountOfItems,\n title: count.minicartCountOfItems\n });\n }\n });\n\n $('.minicart').on('mouseenter focusin touchstart', function () {\n if (window.innerWidth > utils.getViewports('md')) {\n if ($('.search:visible').length === 0) {\n return;\n }\n var url = $('.minicart').data('action-url');\n var count = parseInt($('.minicart .minicart-quantity').text(), 10);\n\n if (count !== 0 && $('.minicart .popover.show').length === 0) {\n if (!updateMiniCart) {\n $('.minicart .popover').addClass('show');\n return;\n }\n\n $('.minicart .popover').addClass('show');\n $('.minicart .popover').spinner().start();\n $.get(url, function (data) {\n $('.minicart .popover').empty();\n $('.minicart .popover').append(data);\n updateMiniCart = false;\n $.spinner().stop();\n qtyInput.miniCartInit();\n });\n }\n }\n });\n $('body').on('touchstart click', function (e) {\n if ($('.minicart').has(e.target).length <= 0) {\n $('.minicart .popover').removeClass('show');\n }\n });\n $('.minicart').on('mouseleave focusout', function (event) {\n if ((event.type === 'focusout' && $('.minicart').has(event.target).length > 0)\n || (event.type === 'mouseleave' && $(event.target).is('.minicart .quantity'))\n || $('body').hasClass('modal-open')) {\n event.stopPropagation();\n return;\n }\n $('.minicart .popover').removeClass('show');\n $.spinner().stop();\n });\n\n $('body').on('change', '.minicart .quantity', function () {\n if ($(this).parents('.bonus-product-line-item').length && $('.cart-page').length) {\n location.reload();\n }\n });\n $('body').on('product:afterAddToCart', function () {\n updateMiniCart = true;\n });\n $('body').on('cart:update', function () {\n updateMiniCart = true;\n });\n $('.checkout-btn').removeClass('disabled');\n};\n","'use strict';\n\nvar base = require('org/components/quantityInput');\n\n/**\n * @desc gets the max input value\n * @param {jqueryObject} qtyInput - jquery selector of input field\n * @returns {int} the max value\n */\nfunction getQtyMaxValue(qtyInput) {\n return parseInt(qtyInput.attr('max'), 0);\n}\n\n/**\n * @desc gets the min input value\n * @param {jqueryObject} qtyInput - jquery selector of input field\n * @returns {int} the min value\n */\nfunction getQtyMinValue(qtyInput) {\n return parseInt(qtyInput.attr('min'), 0);\n}\n\n/**\n * @desc warns user if desired quantity is close to product inventory\n * @param {string} pid - product id\n * @param {string} url - url of request\n * @param {int} updatedValue - qty value\n */\nfunction warnNearInventoryLimit(pid, url, updatedValue) {\n $.ajax({\n url: url,\n method: 'GET',\n data: {\n pid: pid,\n qty: updatedValue\n },\n dataType: 'json',\n success: (data) => {\n if (data.outsideInventory) {\n $('body').append('
' + data.msg + '
');\n setTimeout(function () {\n $('.qty-alert').remove();\n }, 5000);\n }\n }\n });\n}\n\n/**\n * @desc quantity increase\n * @param {jqueryObject} qtyInput - jquery selector of input field\n * @param {string} pid - product id\n */\nfunction qtyIncrease(qtyInput, pid) {\n var qtyMax = getQtyMaxValue(qtyInput);\n var updatedValue = parseInt(qtyInput.val(), 10) + 1;\n if (updatedValue <= qtyMax) {\n qtyInput.val(updatedValue);\n var url = qtyInput.parents('.quantity-form').data('action-url');\n if (url !== '') {\n warnNearInventoryLimit(pid, url, updatedValue);\n }\n }\n}\n\n/**\n * @desc quantity decrease\n * @param {jqueryObject} qtyInput - jquery selector of input field\n */\nfunction qtyDecrease(qtyInput) {\n var qtyMin = getQtyMinValue(qtyInput);\n var updatedValue = parseInt(qtyInput.val(), 10) - 1;\n if (updatedValue >= qtyMin) {\n qtyInput.val(updatedValue);\n }\n}\n\nvar exportQuantityInput = $.extend({}, base, {\n wishlistInit: function () {\n var $qtyInput = $('.wishlist-product-info .quantity-form input[type=\"text\"]');\n\n $('body').on('click', '.wishlist-product-info .quantity-form .plus', function () {\n $qtyInput = $(this).siblings('input[type=\"text\"]');\n qtyIncrease($qtyInput);\n });\n\n $('body').on('click', '.wishlist-product-info .quantity-form .minus', function () {\n $qtyInput = $(this).siblings('input[type=\"text\"]');\n qtyDecrease($qtyInput);\n });\n },\n\n bonusInit: function () {\n var $qtyInput = $('.bonus-quantity input[type=\"text\"]');\n\n $('body').on('click', '.bonus-quantity .plus', function () {\n $qtyInput = $(this).siblings('input[type=\"text\"]');\n qtyIncrease($qtyInput);\n });\n\n $('body').on('click', '.bonus-quantity .minus', function () {\n $qtyInput = $(this).siblings('input[type=\"text\"]');\n qtyDecrease($qtyInput);\n });\n }\n});\n\nmodule.exports = exportQuantityInput;\n","'use strict';\n\nvar debounce = require('lodash/debounce');\nvar endpoint = $('.suggestions-wrapper').data('url');\nvar minChars = 1;\nvar UP_KEY = 38;\nvar DOWN_KEY = 40;\nvar DIRECTION_DOWN = 1;\nvar DIRECTION_UP = -1;\n\n/**\n * Retrieves Suggestions element relative to scope\n *\n * @param {Object} scope - Search input field DOM element\n * @return {JQuery} - .suggestions-wrapper element\n */\nfunction getSuggestionsWrapper(scope) {\n return $(scope).siblings('.suggestions-wrapper');\n}\n\n/**\n * Determines whether DOM element is inside the .search-mobile class\n *\n * @param {Object} scope - DOM element, usually the input.search-field element\n * @return {boolean} - Whether DOM element is inside div.search-mobile\n */\nfunction isMobileSearch(scope) {\n return !!$(scope).closest('.search-mobile').length;\n}\n\n/**\n * Remove modal classes needed for mobile suggestions\n *\n */\nfunction clearModals() {\n $('body').removeClass('modal-open');\n $('header').siblings().attr('aria-hidden', 'false');\n $('.suggestions').removeClass('modal');\n}\n\n/**\n * Apply modal classes needed for mobile suggestions\n *\n * @param {Object} scope - Search input field DOM element\n */\nfunction applyModals(scope) {\n if (isMobileSearch(scope)) {\n $('body').addClass('modal-open');\n $('header').siblings().attr('aria-hidden', 'true');\n getSuggestionsWrapper(scope).find('.suggestions').addClass('modal');\n }\n}\n\n/**\n * Tear down Suggestions panel\n */\nfunction tearDownSuggestions() {\n $('input.search-field').val('');\n clearModals();\n $('.search-mobile .suggestions').unbind('scroll');\n $('.suggestions-wrapper').empty();\n}\n\n/**\n * Toggle search field icon from search to close and vice-versa\n *\n * @param {string} action - Action to toggle to\n */\nfunction toggleSuggestionsIcon(action) {\n var mobileSearchIcon = '.search-mobile button.';\n var iconSearch = 'fa-search';\n var iconSearchClose = 'fa-close';\n\n if (action === 'close') {\n $(mobileSearchIcon + iconSearch).removeClass(iconSearch).addClass(iconSearchClose).attr('type', 'button');\n } else {\n $(mobileSearchIcon + iconSearchClose).removeClass(iconSearchClose).addClass(iconSearch).attr('type', 'submit');\n }\n}\n\n/**\n * Determines whether the \"More Content Below\" icon should be displayed\n *\n * @param {Object} scope - DOM element, usually the input.search-field element\n */\nfunction handleMoreContentBelowIcon(scope) {\n if (($(scope).scrollTop() + $(scope).innerHeight()) >= $(scope)[0].scrollHeight) {\n $('.more-below').fadeOut();\n } else {\n $('.more-below').fadeIn();\n }\n}\n\n/**\n * Positions Suggestions panel on page\n *\n * @param {Object} scope - DOM element, usually the input.search-field element\n */\nfunction positionSuggestions(scope) {\n var outerHeight;\n var $scope;\n var $suggestions;\n var arrowHeight;\n var top;\n\n if (isMobileSearch(scope)) {\n $scope = $(scope);\n top = $scope.offset().top;\n outerHeight = $scope.outerHeight();\n $suggestions = getSuggestionsWrapper(scope).find('.suggestions');\n // make room for caret\n arrowHeight = $suggestions.siblings('.tooltip-arrow').outerHeight();\n $suggestions.css('top', top + outerHeight + arrowHeight);\n\n handleMoreContentBelowIcon(scope);\n\n // Unfortunately, we have to bind this dynamically, as the live scroll event was not\n // properly detecting dynamic suggestions element's scroll event\n $suggestions.scroll(function () {\n handleMoreContentBelowIcon(this);\n });\n }\n}\n\n/**\n * Process Ajax response for SearchServices-GetSuggestions\n *\n * @param {Object|string} response - Empty object literal if null response or string with rendered\n * suggestions template contents\n */\nfunction processResponse(response) {\n var $suggestionsWrapper = getSuggestionsWrapper(this).empty();\n\n $.spinner().stop();\n\n if (typeof (response) !== 'object') {\n $suggestionsWrapper.append(response).show();\n $(this).siblings('.reset-button').addClass('d-sm-block');\n positionSuggestions(this);\n\n if (isMobileSearch(this)) {\n toggleSuggestionsIcon('close');\n applyModals(this);\n }\n\n // Trigger screen reader by setting aria-describedby with the new suggestion message.\n var suggestionsList = $('.suggestions .item');\n if ($(suggestionsList).length) {\n $('input.search-field').attr('aria-describedby', 'search-result-count');\n $('div[name=\"simpleSearch\"] form').attr('aria-describedby', 'search-assistive-text');\n } else {\n $('input.search-field').removeAttr('aria-describedby');\n $('div[name=\"simpleSearch\"] form').removeAttr('aria-describedby');\n }\n } else {\n $suggestionsWrapper.hide();\n }\n}\n\n/**\n * Retrieve suggestions\n *\n * @param {Object} scope - Search field DOM element\n */\nfunction getSuggestions(scope) {\n if ($(scope).val().length >= minChars) {\n $.spinner().start();\n $.ajax({\n context: scope,\n url: endpoint + encodeURIComponent($(scope).val()),\n method: 'GET',\n success: processResponse,\n error: function () {\n $.spinner().stop();\n }\n });\n } else {\n toggleSuggestionsIcon('search');\n $(scope).siblings('.reset-button').removeClass('d-sm-block');\n clearModals();\n getSuggestionsWrapper(scope).empty();\n }\n}\n\n/**\n * Handle Search Suggestion Keyboard Arrow Keys\n *\n * @param {Integer} direction takes positive or negative number constant, DIRECTION_UP (-1) or DIRECTION_DOWN (+1)\n */\nfunction handleArrow(direction) {\n // get all li elements in the suggestions list\n var suggestionsList = $('.suggestions .item');\n if (suggestionsList.filter('.selected').length === 0) {\n suggestionsList.first().addClass('selected');\n $('input.search-field').each(function () {\n $(this).attr('aria-activedescendant', suggestionsList.first()[0].id);\n });\n } else {\n suggestionsList.each(function (index) {\n var idx = index + direction;\n if ($(this).hasClass('selected')) {\n $(this).removeClass('selected');\n $(this).removeAttr('aria-selected');\n if (suggestionsList.eq(idx).length !== 0) {\n suggestionsList.eq(idx).addClass('selected');\n suggestionsList.eq(idx).attr('aria-selected', true);\n $(this).removeProp('aria-selected');\n $('input.search-field').each(function () {\n $(this).attr('aria-activedescendant', suggestionsList.eq(idx)[0].id);\n });\n } else {\n suggestionsList.first().addClass('selected');\n suggestionsList.first().attr('aria-selected', true);\n $('input.search-field').each(function () {\n $(this).attr('aria-activedescendant', suggestionsList.first()[0].id);\n });\n }\n return false;\n }\n return true;\n });\n }\n}\n\nmodule.exports = function () {\n $('form[name=\"simpleSearch\"]').submit(function (e) {\n var suggestionsList = $('.suggestions .item');\n if (suggestionsList.filter('.selected').length !== 0) {\n e.preventDefault();\n suggestionsList.filter('.selected').find('a')[0].click();\n }\n });\n\n $('input.search-field').each(function () {\n /**\n * Use debounce to avoid making an Ajax call on every single key press by waiting a few\n * hundred milliseconds before making the request. Without debounce, the user sees the\n * browser blink with every key press.\n */\n var debounceSuggestions = debounce(getSuggestions, 300);\n $(this).on('keyup focus', function (e) {\n // Capture Down/Up Arrow Key Events\n switch (e.which) {\n case DOWN_KEY:\n handleArrow(DIRECTION_DOWN);\n e.preventDefault(); // prevent moving the cursor\n break;\n case UP_KEY:\n handleArrow(DIRECTION_UP);\n e.preventDefault(); // prevent moving the cursor\n break;\n default:\n debounceSuggestions(this, e);\n }\n });\n });\n\n $('body').on('click', function (e) {\n if (!$('.suggestions').has(e.target).length && !$(e.target).hasClass('search-field')) {\n $('.suggestions').hide();\n }\n });\n\n $('body').on('click touchend', '.search-mobile button.fa-close', function (e) {\n e.preventDefault();\n $('.suggestions').hide();\n toggleSuggestionsIcon('search');\n tearDownSuggestions();\n });\n\n $('.site-search .reset-button').on('click', function () {\n $(this).removeClass('d-sm-block');\n });\n\n $('.site-search .fa-search').click(function () {\n if ($('input.search-field').val() !== '') {\n $('input.search-field').attr('value', $('input.search-field').val());\n $('form[name=\"simpleSearch\"]').submit();\n }\n });\n};\n","'use strict';\n\n/**\n * Show a spinner inside a given element\n * @param {element} $target - Element to block by the veil and spinner.\n * Pass body to block the whole page.\n * @param {boolean} veiled - Whether or not the veil should show behind the spinner\n */\nfunction addSpinner($target, veiled) {\n var $veil = $('
');\n $veil.append('
');\n\n if (!veiled) {\n $veil.addClass('hide-veil');\n }\n\n if ($target.get(0).tagName === 'IMG') {\n $target.after($veil);\n $veil.css({ width: $target.width(), height: $target.height() });\n if ($target.parent().css('position') === 'static') {\n $target.parent().css('position', 'relative');\n }\n } else {\n $target.append($veil);\n if ($target.css('position') === 'static') {\n $target.parent().css('position', 'relative');\n $target.parent().addClass('veiled');\n }\n if ($target.get(0).tagName === 'BODY') {\n $veil.find('.spinner').css('position', 'fixed');\n }\n }\n\n $veil.on('click', function (e) {\n e.stopPropagation();\n });\n}\n\n/**\n * Remove existing spinner\n * @param {element} $veil - jQuery pointer to the veil element\n */\nfunction removeSpinner($veil) {\n if ($veil.parent().hasClass('veiled')) {\n $veil.parent().css('position', '');\n $veil.parent().removeClass('veiled');\n }\n $veil.off('click');\n $veil.remove();\n}\n\n// element level spinner:\n$.fn.spinner = function () {\n var $element = $(this);\n var Fn = function () {\n this.start = function (veiled) {\n if ($element.length) {\n var showVeiled = typeof veiled !== 'undefined' ? veiled : true;\n\n addSpinner($element, showVeiled);\n }\n };\n this.stop = function () {\n if ($element.length) {\n var $veil = $('.veil');\n removeSpinner($veil);\n }\n };\n };\n return new Fn();\n};\n\n// page-level spinner:\n$.spinner = function () {\n var Fn = function () {\n this.start = function (veiled) {\n var showVeiled = typeof veiled !== 'undefined' ? veiled : true;\n addSpinner($('body'), showVeiled);\n };\n this.stop = function () {\n removeSpinner($('.veil'));\n };\n };\n return new Fn();\n};\n","'use strict';\nvar utils = require('lyonscg/util/utils');\nvar imagesPerRow = $('#category-tiles-per-row').data('category-items') || 6;\nvar imagesPerRowMobile = $('#category-tiles-per-row').data('category-items-mobile') || 3;\nvar imageslideDesktop = $('.thumb-section').data('slide');\nvar imageslideMobile = $('.thumb-section').data('count');\n\n/**\n * Reusable slick carousel configurations\n * @example - $('.product-carousel').slick(slickConfigs.pdp)\n */\n\nmodule.exports = {\n hero: {\n autoplay: true,\n autoplaySpeed: 5000,\n easing: 'swing',\n infinite: true,\n speed: 800,\n dots: true,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n appendDots: '.hero-slider-custom-navigation .slick-dots-custom',\n prevArrow: $('.hero-slider-custom-navigation .slick-arrows-custom-prev'),\n nextArrow: $('.hero-slider-custom-navigation .slick-arrows-custom-next')\n },\n\n categorySubFolderTiles: {\n infinite: false,\n speed: 300,\n dots: true,\n arrows: true,\n slidesToShow: imagesPerRow,\n slidesToScroll: 1,\n appendDots: '.category-subfolder-tile-caro-arrows .slick-dots-custom',\n prevArrow: $('.category-subfolder-tile-caro-arrows .slick-arrows-custom-prev'),\n nextArrow: $('.category-subfolder-tile-caro-arrows .slick-arrows-custom-next'),\n responsive: [\n {\n breakpoint: utils.getViewports('lg'),\n settings: {\n slidesToShow: imagesPerRow,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('md'),\n settings: {\n slidesToShow: imagesPerRow - 1,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('sm'),\n settings: {\n slidesToShow: imagesPerRowMobile,\n slidesToScroll: 1\n }\n }\n ]\n },\n\n productTiles: {\n infinite: false,\n speed: 300,\n dots: true,\n arrows: true,\n slidesToShow: 4,\n slidesToScroll: 1,\n appendDots: $('.product-carousel-arrows .slick-dots-custom'),\n prevArrow: $('.product-carousel-arrows .slick-arrows-custom-prev'),\n nextArrow: $('.product-carousel-arrows .slick-arrows-custom-next'),\n responsive: [\n {\n breakpoint: utils.getViewports('lg'),\n settings: {\n slidesToShow: 4,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('md'),\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('sm'),\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n }\n ]\n },\n\n productRecommendationTile: {\n infinite: false,\n speed: 300,\n dots: true,\n arrows: true,\n variableWidth: false,\n slidesToShow: 2,\n slidesToScroll: 1,\n appendDots: $('.product-carousel-arrows .slick-dots-custom'),\n prevArrow: $('.product-carousel-arrows .slick-arrows-custom-prev'),\n nextArrow: $('.product-carousel-arrows .slick-arrows-custom-next'),\n responsive: [\n {\n breakpoint: utils.getViewports('lg'),\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n }\n ]\n },\n\n productGiroRecommendationTile: {\n infinite: false,\n speed: 300,\n dots: true,\n arrows: true,\n slidesToShow: 3,\n slidesToScroll: 1,\n appendDots: $('.product-carousel-arrows .slick-dots-custom'),\n prevArrow: $('.product-carousel-arrows .slick-arrows-custom-prev'),\n nextArrow: $('.product-carousel-arrows .slick-arrows-custom-next'),\n responsive: [\n {\n breakpoint: utils.getViewports('lg'),\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('md'),\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n }\n ]\n },\n\n\n // one tile on mobile for product carousel\n productTilesHeroMobile: {\n infinite: false,\n speed: 300,\n dots: true,\n arrows: true,\n slidesToShow: 4,\n slidesToScroll: 1,\n appendDots: $('.landing-page .product-carousel-arrows .slick-dots-custom'),\n prevArrow: $('.landing-page .product-carousel-arrows .slick-arrows-custom-pre'),\n nextArrow: $('.landing-page .product-carousel-arrows .slick-arrows-custom-next'),\n responsive: [\n {\n breakpoint: utils.getViewports('lg'),\n settings: {\n slidesToShow: 4,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('md'),\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('sm'),\n settings: {\n slidesToShow: 1,\n slidesToScroll: 1\n }\n }\n ]\n },\n\n QuickViewHorizontalSlick: {\n infinite: false,\n dots: false,\n arrows: true,\n slidesToShow: 6,\n slidesToScroll: 1,\n centerMode: false,\n focusOnSelect: true,\n accessibility: false,\n asNavFor: '.primary-images .pdp-slideCarousel'\n },\n\n pdpHorizontalSlick: {\n infinite: false,\n dots: false,\n arrows: true,\n slidesToShow: imageslideDesktop,\n slidesToScroll: 1,\n centerMode: false,\n focusOnSelect: true,\n accessibility: false,\n asNavFor: '.primary-images .pdp-slideCarousel',\n responsive: [\n {\n breakpoint: utils.getViewports('sm'),\n settings: {\n slidesToShow: imageslideMobile,\n slidesToScroll: 1,\n arrows: false\n }\n }\n ]\n },\n\n pdpSlick: {\n infinite: false,\n dots: false,\n arrows: false,\n slidesToShow: 1,\n slidesToScroll: 1,\n centerMode: false,\n asNavFor: '.primary-images .scrollThumb'\n },\n\n // pdp: {\n // infinite: true,\n // speed: 400,\n // dots: true,\n // arrows: true,\n // slidesToShow: 1,\n // slidesToScroll: 1,\n // variableWidth: true,\n // centerMode: false,\n // appendDots: '.primary-images .product-carousel-arrows .slick-dots-custom',\n // prevArrow: $('.primary-images .product-carousel-arrows .slick-arrows-custom-prev'),\n // nextArrow: $('.primary-images .product-carousel-arrows .slick-arrows-custom-next')\n // },\n\n bonusModal: {\n infinite: true,\n speed: 400,\n dots: true,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1\n },\n\n quickView: {\n infinite: true,\n speed: 400,\n dots: true,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n appendDots: '.quick-view-dialog .product-carousel-arrows .slick-dots-custom'\n },\n\n video: {\n speed: 400,\n infinite: false,\n dots: true,\n arrows: true,\n mobileFirst: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n appendDots: '.pdp-videos .product-carousel-arrows .slick-dots-custom',\n prevArrow: $('.pdp-videos .product-carousel-arrows .slick-arrows-custom-prev'),\n nextArrow: $('.pdp-videos .product-carousel-arrows .slick-arrows-custom-next'),\n responsive: [\n {\n breakpoint: utils.getViewports('lg'),\n settings: {\n slidesToShow: 1,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('md'),\n settings: {\n slidesToShow: 1,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('sm'),\n settings: {\n slidesToShow: 1,\n slidesToScroll: 1\n }\n }\n ]\n },\n\n tech: {\n speed: 400,\n dots: true,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n slide: '.tech-slide',\n mobileFirst: true,\n appendDots: '#technology .product-carousel-arrows .slick-dots-custom',\n prevArrow: $('#technology .product-carousel-arrows .slick-arrows-custom-prev'),\n nextArrow: $('#technology .product-carousel-arrows .slick-arrows-custom-next'),\n responsive: [\n {\n breakpoint: utils.getViewports('lg'),\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('md'),\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: utils.getViewports('sm'),\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n }\n ]\n },\n reinitPagination: function (event, slick, defaultSlidesToShow) {\n if (slick.length === 0) {\n return;\n }\n var breakpoints = slick.breakpointSettings;\n var currentBreakpoint = slick.activeBreakpoint;\n var slidesToShow = defaultSlidesToShow; // default slides count (desktop) // 4 for product carousel and articles\n if (currentBreakpoint || slick.options.mobileFirst) { // allow for mobileFirst configs (xs is not active)\n slidesToShow = currentBreakpoint ? breakpoints[currentBreakpoint].slidesToShow : slick.options.slidesToShow;\n if (slick.slideCount <= slidesToShow) {\n slick.$dots.hide();\n slick.$nextArrow.hide();\n slick.$prevArrow.hide();\n } else {\n slick.$dots.show();\n slick.$nextArrow.show();\n slick.$prevArrow.show();\n }\n } else if (slick.slideCount <= slidesToShow) {\n slick.$dots.hide();\n slick.$nextArrow.hide();\n slick.$prevArrow.hide();\n } else {\n slick.$dots.show();\n slick.$nextArrow.show();\n slick.$prevArrow.show();\n }\n }\n};\n","require('@babel/polyfill');\nwindow.jQuery = window.$ = require('jquery');\nvar processInclude = require('base/util');\n\n$(document).ready(function () {\n processInclude(require('./components/menu'));\n processInclude(require('base/components/cookie'));\n processInclude(require('./components/consentTracking'));\n processInclude(require('./components/footer'));\n processInclude(require('lyonscg/components/backtotop'));\n processInclude(require('./components/miniCart'));\n processInclude(require('base/components/collapsibleItem'));\n processInclude(require('./components/search'));\n processInclude(require('./components/clientSideValidation'));\n processInclude(require('base/components/countrySelector'));\n processInclude(require('./components/carousels'));\n processInclude(require('lyonscg/components/tooltips'));\n processInclude(require('./components/einsteinObserver'));\n processInclude(require('org/util/formZoom'));\n processInclude(require('./components/geoLookup'));\n});\n\nrequire('lyonscg/thirdparty/bootstrap');\nrequire('actionsports/components/spinner');\nrequire('slick-carousel');\nrequire('lazysizes');\n","'use strict';\n\nvar base = require('lyonscg/product/base');\nvar slickConfigs = require('actionsports/config/slickConfigs');\nvar zoomConfigs = require('lyonscg/config/zoomConfigs');\nvar imagesloaded = require('imagesloaded');\nvar utils = require('../util/utils');\nvar qtyInput = require('../components/quantityInput');\nvar glide = require('org/config/glide');\nvar glideConfigs = require('site/config/glideConfigs');\nvar pinchzoom = require('org/pinchzoom/pinch-zoom');\n\n/**\n * @param {json} product - Product json\n * @param {json} imgTemp - Product json\n */\nfunction updateMainImages(product, imgTemp) {\n var productID = product.id;\n var $varName = $('.product-detail .set-items, .quick-view-dialog .set-items');\n var $carouselImage;\n var $tempDiv;\n\n if ($varName.length) {\n $tempDiv = $('div.set-items').find('div[data-pid=\"' + productID + '\"]');\n $carouselImage = $tempDiv.find$('.product-carousel, .primary-images .glideScrollThumb, .primary-images .pdp-slideCarousel');\n } else {\n $carouselImage = $('.primary-images');\n }\n $carouselImage.html(imgTemp);\n}\n\n/**\n * Retrieves the relevant pid value\n * @param {jquery} $el - DOM container for a given add to cart button\n * @return {string} - value to be used when adding product to cart\n */\nfunction getPidValue($el) {\n var pid;\n\n if ($('#quickViewModal').hasClass('show') && !$('.product-set').length) {\n pid = $($el).closest('.modal-content').find('.product-quickview').data('pid');\n } else if ($('.product-set-detail').length || $('.product-set').length) {\n pid = $($el).closest('.product-detail').find('.product-id').text();\n } else {\n pid = $('.product-detail:not(.bundle-item)').data('pid');\n }\n\n return pid;\n}\n\n/**\n * Disable PDP Zoom\n */\nfunction disableZoom() {\n $('.slide-link').trigger('zoom.destroy');\n}\n\n/**\n * Init PDP Zoom\n */\nfunction initZoom() {\n disableZoom();\n\n var isDesktop = utils.mediaBreakpointUp('lg');\n var $activeSlide = $('.product-carousel .slick-active');\n var $activeBundleSlides = $('.bundle-item .product-carousel .slick-active');\n var $image = $activeSlide.find('.slide-link.zoom-hires');\n var url = $image.attr('href');\n\n if ($image.length > 0 && url && url !== 'null' && isDesktop) {\n // Start spinner while zoom image loads\n $activeSlide.spinner().start(false);\n\n var config = {\n url: url,\n callback: function () {\n // Stop spinner when zoom image loaded\n $activeSlide.spinner().stop();\n }\n };\n config = $.extend({}, zoomConfigs, config);\n\n $image.zoom(config);\n\n // Enable image zoom on bundled products\n if ($activeBundleSlides.length > 0) {\n $activeBundleSlides.each(function () {\n var $bundleImage = $(this).find('.slide-link.zoom-hires');\n var bundleUrl = $bundleImage.attr('href');\n\n var bundleConfig = {\n url: bundleUrl,\n callback: function () {\n // Stop spinner when zoom image loaded\n $(this).spinner().stop();\n }\n };\n\n bundleConfig = $.extend({}, zoomConfigs, bundleConfig);\n\n $(this).zoom(bundleConfig);\n });\n }\n }\n}\n\n/**\n * Init the product carousel using a predefined slick configuration\n */\nfunction carouselInit() {\n // var $carousels = $('.page .product-carousel');\n var $vidCarousel = $('.video-carousel');\n var $techCarousel = $('.technology-carousel');\n var $syncCarousel = $('.primary-images .pdp-slideCarousel');\n var $glideSlide = $('.product-carousel');\n var glideLength = glide.getGlideSlideLength;\n var pinchzoomElement = $('.pinch-zoom-init');\n var isDesktop = utils.mediaBreakpointUp('lg');\n // var $glideSlideThumb = $('.primary-images .scrollThumb');\n\n // if ($syncCarousel.length) {\n // $('.primary-images .pdp-slideCarousel').not('.slick-initialized').slick(slickConfigs.pdpSlick);\n // if ($('.modal-dialog').hasClass('quick-view-dialog')) {\n // $('.quick-view-dialog .scrollThumb').not('.slick-initialized').slick(slickConfigs.QuickViewHorizontalSlick);\n // } else {\n // $('.primary-images .scrollThumb').not('.slick-initialized').slick(slickConfigs.pdpHorizontalSlick);\n // }\n // }\n\n if ($syncCarousel.length && !($syncCarousel.hasClass('glide--carousel')) && $('.quick-view-dialog .pdp-slideCarousel').length === 0) {\n var glideSyncCarousel = glide.carouselInit('.pdp-slideCarousel', glideConfigs.bellPdp).mount();\n var glideSlideThumb = glide.carouselInit('.scrollThumb', glideConfigs.scrollThumb);\n glide.checkGlideDisableLength(glideSlideThumb);\n glideSlideThumb.mount({ glideLength });\n\n glide.thumbCarousel(glideSyncCarousel, glideSlideThumb);\n glide.clickGlide('.scrollThumb', glideSyncCarousel, glideSlideThumb);\n if (pinchzoomElement.length > 0 && pinchzoom && !isDesktop) {\n pinchzoomElement.addClass('pinch-zoom');\n pinchzoom.pinchZoomPdp([glideSyncCarousel, glideSlideThumb], false, true);\n }\n } else if ($glideSlide.length && $('.quick-view-dialog .product-carousel').length === 0) {\n var glideSingleCarousel = glide.carouselInit('.product-carousel', glideConfigs.pdp);\n glide.checkGlideDisableLength(glideSingleCarousel);\n glideSingleCarousel.mount({ glideLength });\n if (pinchzoomElement.length > 0 && pinchzoom && !isDesktop) {\n pinchzoomElement.addClass('pinch-zoom');\n pinchzoom.pinchZoomPdp([glideSingleCarousel], false, true);\n }\n }\n\n // if ($carousels.length) {\n // // many product tile sliders can exist on a page; loop through them to init individually\n // $carousels.not('.slick-initialized').each(function (idx) {\n // imagesloaded($(this)).on('done', function () {\n // var $carousel = $(this.elements);\n // var sliderID = 'prodSlider' + idx;\n // var defaultConfig = slickConfigs.pdp;\n // // assign unique ID to each slider and target only that slider's controls\n // $carousel.attr('id', sliderID);\n // var $sliderControls = $(`#${sliderID}`).next('.product-carousel-arrows');\n // // hide visible components\n // $sliderControls.addClass('d-none');\n\n // // update slider config to use unique controls and prevent multiple sliders on same page from affecting each other\n // slickConfigs.pdp.appendDots = $sliderControls.find('.slick-dots-custom');\n // slickConfigs.pdp.prevArrow = $sliderControls.find('.slick-arrows-custom-prev');\n // slickConfigs.pdp.nextArrow = $sliderControls.find('.slick-arrows-custom-next');\n\n // // destroy and re-init slider after images are loaded\n // if ($carousel.hasClass('slick-initialized')) {\n // $carousel.off('init', initZoom);\n // $carousel.off('afterChange', initZoom);\n // $carousel.slick('unslick');\n // }\n\n // $carousel.not('.slick-initialized').on('init reInit', function (event, slick) {\n // // show slider nav after init\n // $sliderControls.removeClass('d-none');\n // slickConfigs.reinitPagination(event, slick, 1);\n // $carousels.find('.slick-slide').removeAttr('aria-describedby');\n // }).slick(slickConfigs.pdp);\n\n // utils.smartResize(function () {\n // slickConfigs.reinitPagination(event, $carousel.slick('getSlick'), 1);\n // });\n\n // // Reverts config to default to prevent chaining\n // slickConfigs.pdp = defaultConfig;\n // });\n // });\n // }\n if ($vidCarousel.length && !glide.checkGlideMounted('.video-carousel')) {\n glide.sameClassMultiCarousel('.video-carousel', glideConfigs.video);\n // $vidCarousel.not('.slick-initialized').on('init reInit', function (event, slick) {\n // slickConfigs.reinitPagination(event, slick, 1);\n // $vidCarousel.find('.slick-slide').removeAttr('aria-describedby');\n // }).slick(slickConfigs.video);\n\n // utils.smartResize(function () {\n // slickConfigs.reinitPagination(event, $vidCarousel.slick('getSlick'), 1);\n // });\n }\n if ($techCarousel.length && !glide.checkGlideMounted('.technology-carousel')) {\n glide.sameClassMultiCarousel('.technology-carousel', glideConfigs.tech);\n // $techCarousel.not('.slick-initialized').on('init reInit', function (event, slick) {\n // slickConfigs.reinitPagination(event, slick, 3);\n // $techCarousel.find('.slick-slide').removeAttr('aria-describedby');\n // }).slick(slickConfigs.tech);\n\n // utils.smartResize(function () {\n // slickConfigs.reinitPagination(event, $techCarousel.slick('getSlick'), 3);\n // });\n }\n}\n\n/**\n * Init the quickview carousel using a predefined slick configuration\n */\nfunction carouselInitQuickview() {\n var $carouselsQuickview = $('.quick-view-dialog .product-carousel');\n var $carouselSync = $('.quick-view-dialog .pdp-slideCarousel');\n var $glideSlideThumb = $('.primary-images .scrollThumb');\n const glideLength = glide.getGlideSlideLength;\n\n if ($carouselSync.length && $glideSlideThumb.length && !glide.checkGlideMounted('.pdp-slideCarousel')) {\n const glideSyncCarousel = glide.carouselInit('.pdp-slideCarousel', glideConfigs.plpQuickview);\n glide.checkGlideDisableLength(glideSyncCarousel);\n glideSyncCarousel.mount({ glideLength });\n const glideSlideThumb = glide.carouselInit('.scrollThumb', glideConfigs.quickViewScrollThumb);\n glide.checkGlideDisableLength(glideSlideThumb);\n glideSlideThumb.mount({ glideLength });\n glide.thumbCarousel(glideSyncCarousel, glideSlideThumb);\n glide.clickGlide('.scrollThumb', glideSyncCarousel, glideSlideThumb);\n } else if ($carouselsQuickview.length && !glide.checkGlideMounted('.product-carousel')) {\n var currentCarousel = glide.carouselInit('.product-carousel', glideConfigs.plpQuickview);\n glide.checkGlideDisableLength(currentCarousel);\n currentCarousel.mount({ glideLength });\n }\n\n // if ($carouselSync.length) {\n // $('.quick-view-dialog .pdp-slideCarousel').not('.slick-initialized').slick(slickConfigs.pdpSlick);\n // $('.quick-view-dialog .scrollThumb').not('.slick-initialized').slick(slickConfigs.QuickViewHorizontalSlick);\n // }\n if ($carouselsQuickview.length) {\n // many product tile sliders can exist on a page; loop through them to init individually\n $carouselsQuickview.not('.glide--swipeable').each(function (idx) {\n imagesloaded($(this)).on('done', function () {\n var $carousel = $(this.elements);\n var sliderID = 'qvSlider' + idx;\n var defaultConfig = glideConfigs.pdp;\n // assign unique ID to each slider and target only that slider's controls\n $carousel.attr('id', sliderID);\n var quickViewGlide = glide.carouselInit('.' + $carousel.attr('class'), defaultConfig);\n // destroy and re-init slider after images are loaded\n quickViewGlide.on('mount.before', function () {\n if ($carousel.hasClass('glide--swipeable')) {\n $carousel.off('init', initZoom);\n $carousel.off('afterChange', initZoom);\n quickViewGlide.destroy();\n }\n });\n $carousel.not('.glide--swipeable').each(function () {\n quickViewGlide.mount();\n });\n });\n });\n }\n}\n\n/**\n * Init the product carousel using a predefined slick configuration for bonus item\n */\nfunction carouselInitBonus() {\n var $carouselBonus = $('.bonus-product-item');\n if ($carouselBonus.length) {\n $carouselBonus.each(function () {\n var $carouselBonusDiv = $(this).find('.product-carousel');\n if ($carouselBonusDiv.length) {\n imagesloaded($carouselBonusDiv).on('done', function () {\n $carouselBonusDiv.not('.slick-initialized').slick($.extend(slickConfigs.bonusModal, {\n appendDots: $carouselBonusDiv.siblings('.product-carousel-arrows').find('.slick-dots-custom'),\n prevArrow: $carouselBonusDiv.siblings('.product-carousel-arrows').find('.slick-arrows-custom-prev'),\n nextArrow: $carouselBonusDiv.siblings('.product-carousel-arrows').find('.slick-arrows-custom-next')\n }));\n });\n }\n });\n }\n}\n\n/**\n * Init the product carousel with recommendation in add to cart modal\n */\nfunction addedCartRecommendations() {\n if ($('.addedToCartCarouselAS').length) {\n // many product tile sliders can exist on a page; loop through them to init individually\n // $('.addedToCartCarouselAS').not('.slick-initialized').each(function () {\n // var $slider = $(this);\n // var defaultConfig = slickConfigs.productRecommendationTile;\n // if ($slider.hasClass('recommendation')) {\n // defaultConfig.slide = '.grid-tile';\n // }\n\n // // each slider target only that slider's controls\n // var $sliderControls = $slider.next('.product-carousel-arrows');\n\n // // update slider config to use unique controls and prevent multiple sliders on same page from affecting each other\n // slickConfigs.productRecommendationTile.appendDots = $sliderControls.find('.slick-dots-custom');\n // slickConfigs.productRecommendationTile.prevArrow = $sliderControls.find('.slick-arrows-custom-prev');\n // slickConfigs.productRecommendationTile.nextArrow = $sliderControls.find('.slick-arrows-custom-next');\n\n // $slider.slick(defaultConfig);\n\n // utils.smartResize(function () {\n // slickConfigs.reinitPagination(event, $slider.slick('getSlick'), 4);\n // });\n // });\n glide.sameClassMultiCarousel('.addedToCartCarouselAS', glideConfigs.productRecommendationTile);\n }\n\n if ($('.addedToCartCarouselGiro').length) {\n // many product tile sliders can exist on a page; loop through them to init individually\n // $('.addedToCartCarouselGiro').not('.slick-initialized').each(function () {\n // var $slider = $(this);\n // var defaultConfig = slickConfigs.productGiroRecommendationTile;\n // if ($slider.hasClass('recommendation')) {\n // defaultConfig.slide = '.grid-tile';\n // }\n\n // // each slider target only that slider's controls\n // var $sliderControls = $slider.next('.product-carousel-arrows');\n\n // // update slider config to use unique controls and prevent multiple sliders on same page from affecting each other\n // slickConfigs.productGiroRecommendationTile.appendDots = $sliderControls.find('.slick-dots-custom');\n // slickConfigs.productGiroRecommendationTile.prevArrow = $sliderControls.find('.slick-arrows-custom-prev');\n // slickConfigs.productGiroRecommendationTile.nextArrow = $sliderControls.find('.slick-arrows-custom-next');\n\n // $slider.on('init reInit', function (event, slick) {\n // slickConfigs.reinitPagination(event, slick, 4);\n // }).slick(defaultConfig);\n\n // utils.smartResize(function () {\n // slickConfigs.reinitPagination(event, $slider.slick('getSlick'), 4);\n // });\n // });\n glide.sameClassMultiCarousel('.addedToCartCarouselGiro', glideConfigs.productRecommendationTile);\n }\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n *\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
').append($.parseHTML(html));\n\n var body = $html.find('.choice-of-bonus-product');\n var footer = $html.find('.modal-footer').children();\n\n return { body: body, footer: footer };\n}\n\n/**\n * Retrieves url to use when adding a product to the cart\n *\n * @param {Object} data - data object used to fill in dynamic portions of the html\n */\nfunction chooseBonusProducts(data) {\n $('.modal-body').spinner().start(false);\n\n if ($('#chooseBonusProductModal').length !== 0) {\n $('#chooseBonusProductModal').remove();\n }\n var bonusUrl;\n if (data.bonusChoiceRuleBased) {\n bonusUrl = data.showProductsUrlRuleBased;\n } else {\n bonusUrl = data.showProductsUrlListBased;\n }\n\n var htmlString = ''\n + '
';\n $('body').append(htmlString);\n $('.modal-body').spinner().start(false);\n\n $.ajax({\n url: bonusUrl,\n method: 'GET',\n dataType: 'json',\n success: function (response) {\n var parsedHtml = parseHtml(response.renderedTemplate);\n $('#chooseBonusProductModal .modal-body').empty();\n $('#chooseBonusProductModal .enter-message').text(response.enterDialogMessage);\n $('#chooseBonusProductModal .modal-header .close .sr-only').text(response.closeButtonText);\n $('#chooseBonusProductModal .modal-body').html(parsedHtml.body);\n $('#chooseBonusProductModal .modal-footer').html(parsedHtml.footer);\n $('#chooseBonusProductModal').modal('show');\n $.spinner().stop();\n qtyInput.qvInit();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n}\n\n/**\n * Retrieve contextual quantity selector\n * @param {jquery} $el - DOM container for the relevant quantity\n * @return {jquery} - quantity selector DOM container\n */\nfunction getQuantitySelector($el) {\n return $el && $('.set-items').length\n ? $($el).closest('.set-item').find('.quantity-select')\n : $('.quantity-select');\n}\n\n/**\n * Updates DOM using post-option selection Ajax response\n *\n * @param {OptionSelectionResponse} optionsHtml - Ajax response optionsHtml from selecting a product option\n * @param {jQuery} $productContainer - DOM element for current product\n */\nfunction updateOptions(optionsHtml, $productContainer) {\n // Update options\n $productContainer.find('.product-options').empty().html(optionsHtml);\n}\n\n/**\n * Process the attribute values for an attribute that has image swatches\n *\n * @param {Object} attr - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {Object[]} attr.values - Array of attribute value objects\n * @param {string} attr.values.value - Attribute coded value\n * @param {string} attr.values.url - URL to de/select an attribute value of the product\n * @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be\n * selected. If there is no variant that corresponds to a specific combination of attribute\n * values, an attribute may be disabled in the Product Detail Page\n * @param {jQuery} $productContainer - DOM container for a given product\n * @param {Object} msgs - object containing resource messages\n */\nfunction processSwatchValues(attr, $productContainer, msgs) {\n attr.values.forEach(function (attrValue) {\n var $attrValue = $productContainer.find('[data-attr=\"' + attr.id + '\"] [data-attr-value=\"' +\n attrValue.value + '\"]');\n var $swatchButton = $attrValue.parent();\n\n if (attrValue.selected) {\n $attrValue.addClass('selected');\n $attrValue.siblings('.selected-assistive-text').text(msgs.assistiveSelectedText);\n } else {\n $attrValue.removeClass('selected');\n $attrValue.siblings('.selected-assistive-text').empty();\n }\n\n if (attrValue.url) {\n $swatchButton.attr('data-url', attrValue.url).data('url', attrValue.url);\n } else {\n $swatchButton.removeAttr('data-url');\n }\n\n // Disable if not selectable\n $attrValue.removeClass('selectable unselectable');\n\n\n if (attr.attributeId === 'size') {\n var currentSize = $productContainer.find('[data-attr=\"' + attr.id + '\"] [data-attr-value=\"' +\n attrValue.value + '\"]');\n var sizeBtn = currentSize.parent();\n if (typeof attrValue.display !== 'undefined') {\n if (attrValue.display === true) {\n sizeBtn.show();\n } else {\n sizeBtn.hide();\n }\n }\n }\n\n $attrValue.addClass(attrValue.selectable ? 'selectable' : 'unselectable');\n });\n}\n\n/**\n * Process attribute values associated with an attribute that does not have image swatches\n *\n * @param {Object} attr - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {Object[]} attr.values - Array of attribute value objects\n * @param {string} attr.values.value - Attribute coded value\n * @param {string} attr.values.url - URL to de/select an attribute value of the product\n * @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be\n * selected. If there is no variant that corresponds to a specific combination of attribute\n * values, an attribute may be disabled in the Product Detail Page\n * @param {jQuery} $productContainer - DOM container for a given product\n */\nfunction processNonSwatchValues(attr, $productContainer) {\n var $attr = '[data-attr=\"' + attr.id + '\"]';\n var $defaultOption = $productContainer.find($attr + ' .select-' + attr.id + ' option:first');\n $defaultOption.attr('value', attr.resetUrl);\n\n attr.values.forEach(function (attrValue) {\n var $attrValue = $productContainer\n .find($attr + ' [data-attr-value=\"' + attrValue.value + '\"]');\n $attrValue.attr('value', attrValue.url)\n .removeAttr('disabled');\n if (!attrValue.selectable) {\n // $attrValue.attr('disabled', true);\n }\n });\n}\n\n/**\n * Routes the handling of attribute processing depending on whether the attribute has image\n * swatches or not\n *\n * @param {Object} attrs - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {jQuery} $productContainer - DOM element for a given product\n * @param {Object} msgs - object containing resource messages\n */\nfunction updateAttrs(attrs, $productContainer, msgs) {\n // For Vista Action Sports, all variation attributes are swatches\n var attrsWithSwatches = ['color', 'size', 'lensTint', 'side'];\n\n attrs.variationAttributes.forEach(function (attr) {\n if (attrsWithSwatches.indexOf(attr.id) > -1) {\n processSwatchValues(attr, $productContainer, msgs, attrs);\n } else {\n processNonSwatchValues(attr, $productContainer);\n }\n });\n}\n\n/**\n *\n * @param {string} msg - the avialablity message\n * @returns {string} - markup to be rendered\n */\nfunction getAvailabilityMarkup(msg) {\n return '
' + msg + '
';\n}\n\n/**\n * Determine if the current selection is complete, i.e. a selection has been made for all variation attributes.\n * @param {Object} response - ajax response\n * @returns {boolean} - true if the selection is incomplete\n */\nfunction isSelectionIncomplete(response) {\n return response.product.variationAttributes.some(variation => variation.selectedValue === null);\n}\n\n/**\n * Determine if the currently selected attribute combo is valid or not, i.e. whether it represents\n * a potentially orderable sku at all.\n * @param {Object} response - ajax response\n * @returns {boolean} - true if the selected combination does not exist or is not orderable\n */\nfunction isSelectionInvalid(response) {\n if (response.product.readyToOrder) return false;\n return response.product.variationAttributes.some(variation => variation.values.some(v => v.selected && !v.selectable));\n}\n\n/**\n * Updates the availability status in the Product Detail Page\n *\n * @param {Object} response - Ajax response object after an\n * attribute value has been [de]selected\n * @param {jQuery} $productContainer - DOM element for a given product\n */\nfunction updateAvailability(response, $productContainer) {\n var isAvailable = response.product.available;\n var showBackInStockMsg = response.product.productType !== 'master' && response.product.online && response.product.isBackInStockNotificationEligible && !response.product.available;\n var bisPID = response.product.id;\n\n var availabilityValue = '';\n var availabilityMessages = response.product.availability.messages;\n response.product.availability.incompletecombo = isSelectionIncomplete(response);\n response.product.availability.invalidcombo = isSelectionInvalid(response);\n if (!response.product.readyToOrder) {\n if (response.product.availability.invalidcombo) {\n availabilityValue = getAvailabilityMarkup(response.resources.info_invalidcombination);\n } else if (response.product.availability.incompletecombo) {\n availabilityValue = getAvailabilityMarkup(response.resources.info_selectforstock);\n } else {\n availabilityValue = getAvailabilityMarkup(response.resources.info_selectforstock);\n }\n } else if (isAvailable) {\n availabilityValue += getAvailabilityMarkup(response.resources.available);\n } else if (!isAvailable) {\n availabilityValue += getAvailabilityMarkup(response.resources.unavailable);\n response.product.availability.outofstock = true;\n } else {\n availabilityMessages.forEach(function (message) {\n availabilityValue += getAvailabilityMarkup(message);\n });\n }\n\n // handle back in stock notification display\n if (showBackInStockMsg) {\n $('.back-in-stock-notification').removeClass('hidden');\n $('.back-in-stock-notification .bisPID').val(bisPID);\n } else {\n $('.back-in-stock-notification').addClass('hidden');\n }\n\n $($productContainer).trigger('product:updateAvailability', {\n product: response.product,\n $productContainer: $productContainer,\n message: availabilityValue,\n resources: response.resources\n });\n}\n\n/**\n * Generates html for product attributes section\n *\n * @param {array} attributes - list of attributes\n * @return {string} - Compiled HTML\n */\nfunction getAttributesHtml(attributes) {\n if (!attributes) {\n return '';\n }\n\n var html = '';\n\n attributes.forEach(function (attributeGroup) {\n if (attributeGroup.ID === 'mainAttributes') {\n attributeGroup.attributes.forEach(function (attribute) {\n html += '