'\n ].join('\\n');\n};\n\n/**\n * @private\n * @function\n * @description Updates the summary page with the selected bonus product\n */\nfunction updateSummary() {\n var $bonusProductList = $('#bonus-product-list');\n if (!selectedList.length) {\n $bonusProductList.find('li.selected-bonus-item').remove();\n $('.add-to-cart-bonus').attr('disabled', 'disabled');\n } else {\n var ulList = $bonusProductList.find('ul.selected-bonus-items').first();\n var i, len;\n for (i = 0, len = selectedList.length; i < len; i++) {\n var item = selectedList[i];\n var li = selectedItemTemplate(item);\n $(li).appendTo(ulList);\n }\n $('.add-to-cart-bonus').removeAttr('disabled');\n }\n\n // get remaining item count\n var sumItems = 0;\n $('.selected-bonus-item').each(function() {\n sumItems += Number($(this).find('.item-qty .display-value').text());\n });\n var remain = maxItems - sumItems;\n if (remain <= 0) {\n $bonusProductList.find('.select-bonus-item').not('.selected').attr('disabled', 'disabled');\n } else {\n $bonusProductList.find('.select-bonus-item').removeAttr('disabled');\n }\n}\n\nvar $firstLoad = true;\n\nfunction initializeGrid () {\n var $bonusProductList = $('#bonus-product-list'),\n bliData = $bonusProductList.data('line-item-detail');\n maxItems = bliData.maxItems;\n bliUUID = bliData.uuid;\n\n $bonusProductList.on('click', '.select-bonus-item', function (e) {\n e.preventDefault();\n\n if ($(this).hasClass('selected')) {\n var $current = $(this).parents('.bonus-product-item').data('productuuid');\n\n $('.selected-bonus-items').find('[data-uuid=\"' + $current + '\"] .remove-link')\n .trigger('click');\n $(this).removeClass('selected')\n .text(Resources.SELECT_BONUS_PRODUCT)\n .switchClass('btn-primary', 'btn-dark');\n return;\n }\n\n var sumItems = 0;\n $('.selected-bonus-item').each(function() {\n sumItems += Number($(this).find('.item-qty .display-value').text());\n });\n\n if (selectedList.length >= maxItems) {\n $bonusProductList.find('.select-bonus-item').not('.selected').attr('disabled', 'disabled');\n return;\n }\n\n var form = $(this).closest('.bonus-product-form'),\n uuid = form.find('input[name=\"productUUID\"]').val(),\n qtyVal = form.find('input[name=\"Quantity\"], select[name=\"Quantity\"]').val(),\n qty = (isNaN(qtyVal)) ? 1 : (+qtyVal);\n if (qty > maxItems - sumItems) {\n $(this).attr('disabled', 'disabled');\n return;\n }\n\n var product = {\n uuid: uuid,\n pid: form.find('input[name=\"pid\"]').val(),\n qty: qty,\n name: form.find('.product-name').text(),\n attributes: form.find('.product-variations').data('attributes'),\n options: []\n };\n\n var optionSelects = form.find('.product-option');\n\n optionSelects.each(function () {\n product.options.push({\n name: this.name,\n value: $(this).val(),\n display: $(this).children(':selected').first().html()\n });\n });\n\n selectedList.push(product);\n $('#bonus-product-list li').remove('.selected-bonus-item');\n $(this).addClass('selected')\n .text(Resources.SELECTED_BONUS_PRODUCT)\n .switchClass('btn-dark', 'btn-primary');\n updateSummary();\n }).on('click', '.remove-link', function (e) {\n e.preventDefault();\n var container = $(this).closest('.selected-bonus-item');\n if (!container.data('uuid')) { return; }\n\n var uuid = container.data('uuid');\n var i, len = selectedList.length;\n for (i = 0; i < len; i++) {\n if (selectedList[i].uuid === uuid) {\n selectedList.splice(i, 1);\n break;\n }\n }\n $('#bonus-product-list li').remove('.selected-bonus-item');\n updateSummary();\n })\n .on('click', '.add-to-cart-bonus', function (e) {\n e.preventDefault();\n $(this).addClass('clicked');\n var url = util.appendParamsToUrl(Urls.addBonusProduct, {bonusDiscountLineItemUUID: bliUUID});\n var bonusProducts = getBonusProducts();\n if (bonusProducts.bonusproducts[0].product.qty > maxItems) {\n bonusProducts.bonusproducts[0].product.qty = maxItems;\n }\n // make the server call\n $.ajax({\n type: 'POST',\n dataType: 'json',\n cache: false,\n contentType: 'application/json',\n url: url,\n data: JSON.stringify(bonusProducts)\n })\n .done(function () {\n // success\n if (window.pageContext.ns == 'cart') {\n page.refresh();\n } else {\n page.redirect(Urls.cartShow);\n }\n })\n .fail(function (xhr, textStatus) {\n // failed\n if (textStatus === 'parsererror') {\n window.alert(Resources.BAD_RESPONSE);\n } else {\n window.alert(Resources.SERVER_CONNECTION_ERROR);\n }\n })\n .always(function () {\n dialog.close();\n });\n })\n .on('click', '#more-bonus-products', function (e) {\n e.preventDefault();\n var uuid = $('#bonus-product-list').data().lineItemDetail.uuid;\n\n //get the next page of choice of bonus products\n var lineItemDetail = JSON.parse($('#bonus-product-list').attr('data-line-item-detail'));\n lineItemDetail.pageStart = lineItemDetail.pageStart + lineItemDetail.pageSize;\n $('#bonus-product-list').attr('data-line-item-detail', JSON.stringify(lineItemDetail));\n\n var url = util.appendParamsToUrl(Urls.getBonusProducts, {\n bonusDiscountLineItemUUID: uuid,\n format: 'ajax',\n lazyLoad: 'true',\n pageStart: lineItemDetail.pageStart,\n pageSize: $('#bonus-product-list').data().lineItemDetail.pageSize,\n bonusProductsTotal: $('#bonus-product-list').data().lineItemDetail.bpTotal\n });\n\n $.ajax({\n type: 'GET',\n cache: false,\n contentType: 'application/json',\n url: url\n })\n .done(function (data) {\n //add the new page to DOM and remove 'More' link if it is the last page of results\n $('#more-bonus-products').before(data);\n if ((lineItemDetail.pageStart + lineItemDetail.pageSize) >= $('#bonus-product-list').data().lineItemDetail.bpTotal) {\n $('#more-bonus-products').remove();\n }\n })\n .fail(function (xhr, textStatus) {\n if (textStatus === 'parsererror') {\n window.alert(Resources.BAD_RESPONSE);\n } else {\n window.alert(Resources.SERVER_CONNECTION_ERROR);\n }\n });\n });\n\n var $cartItems = $bonusProductList.find('.selected-bonus-item');\n\n $cartItems.each(function () {\n var $selectedItem = $('.bonus-product-item[data-productuuid=\"' + $(this).data('uuid') + '\"]'),\n $selectBonusItem = $selectedItem.find('.bonus-product-form .select-bonus-item');\n if ($firstLoad) {\n $(this).remove();\n $selectBonusItem.trigger('click');\n } else {\n $selectBonusItem.addClass('selected')\n .text(Resources.SELECTED_BONUS_PRODUCT)\n .switchClass('btn-dark', 'btn-primary');\n }\n });\n\n $firstLoad = false;\n selectedListState = selectedList.slice();\n\n var sumItems = 0;\n $('.selected-bonus-item').each(function() {\n sumItems += Number($(this).find('.item-qty .display-value').text());\n });\n\n if (sumItems >= maxItems) {\n $bonusProductList.find('.select-bonus-item').not('.selected').attr('disabled', 'disabled');\n }\n}\n\nvar bonusProductsView = {\n /**\n * @function\n * @description Open the list of bonus products selection dialog\n */\n show: function (url, title) {\n\n dialog.open({\n url: url,\n options: {\n title: title,\n dialogClass: 'bonus-modal mobile-backarrow',\n showMobileBackArrow: true,\n open: function () {\n initializeGrid();\n favouritewidget.initAddToFavourite();\n\n $('.bonus-modal').on('click', '.no-thanks', function () {\n dialog.close();\n });\n },\n close: function () {\n if (!$('.add-to-cart-bonus').hasClass('clicked')) {\n selectedList = selectedListState.slice();\n }\n }\n }\n });\n },\n /**\n * @function\n * @description Open bonus product promo prompt dialog\n */\n loadBonusOption: function () {\n var bonusDiscountContainer = $('.bonus-discount-container');\n if (!bonusDiscountContainer.length) { return; }\n // get the uuid from minicart, then trash html\n\n var uuid = bonusDiscountContainer.data('lineitemid'),\n title = bonusDiscountContainer.data('dialogtitle'),\n url = util.appendParamsToUrl(Urls.getBonusProducts, {\n bonusDiscountLineItemUUID: uuid,\n source: 'bonus',\n format: 'ajax',\n lazyLoad: 'false',\n pageStart: 0,\n pageSize: 10,\n bonusProductsTotal: -1\n });\n\n bonusDiscountContainer.remove();\n bonusProductsView.show(url, title);\n }\n};\n\nmodule.exports = bonusProductsView;\n","'use strict';\n\nvar ajax = require('./ajax'),\n util = require('./util'),\n _ = require('lodash'),\n imagesLoaded = require('imagesloaded');\n\nconst defaultDialogWidth = 'auto';\n\n/**\n * jQuery UI Dialog Bootstrap Adapter\n */\n\n$.extend($.ui.dialog.prototype.options.classes, {\n 'ui-dialog': 'modal-dialog modal-content',\n 'ui-dialog-titlebar': 'modal-header',\n 'ui-dialog-title': 'modal-title',\n 'ui-dialog-titlebar-close': 'close',\n 'ui-dialog-content': 'modal-body',\n 'ui-dialog-buttonpane': 'modal-footer'\n});\n\n/**\n * Extend the actual jQuery UI dialog prototype\n */\n\n$.widget('ui.dialog', $.ui.dialog, {\n // Add new options\n options: {\n overlayTitle: false,\n showTitle: true,\n draggable: false,\n showTitlebar: true,\n showClosebutton: true,\n showBackArrow: false , // show back arrow instead of X close button\n showMobileBackArrow: false, // show back arrow in MOBILE ONLY\n mobilePreventFullscreen: false // set to true to prevent dialog from filling the entire viewport in mobile\n },\n // extend _create method to utilize our new options\n _create: function() {\n this._super();\n if (!this.options.showTitlebar) {\n this.uiDialogTitlebar.hide();\n this.options.showTitle = false;\n } else {\n if (this.options.overlayTitle === true) {\n this.uiDialog.addClass('overlay-titlebar');\n }\n if (this.options.showTitle === false || this.options.title == '' || this.options.title == ' ' || this.options.title == null) {\n this.uiDialogTitlebar.find('.ui-dialog-title').hide();\n }\n if (this.options.showClosebutton === false) {\n this.uiDialogTitlebar.find('.ui-dialog-titlebar-close').hide();\n }\n if (this.options.mobilePreventFullscreen === true) {\n this.uiDialog.addClass('mobile-non-fullscreen');\n }\n\n var closeIcon = 'close';\n var backIcon = 'backarrow';\n\n // Note: this overrides the closeText option\n this.uiDialogTitlebarClose.html(window.Iconography[closeIcon] + window.Iconography[backIcon]);\n if (this.options.showBackArrow === true) {\n this.uiDialog.addClass('mobile-backarrow desktop-backarrow');\n }\n if (this.options.showMobileBackArrow === true) {\n this.uiDialog.addClass('mobile-backarrow');\n }\n\n }\n },\n // allow title to contain HTML markup\n _title: function(title) {\n if (!this.options.title) {\n title.html(' ');\n } else {\n title.html(this.options.title);\n }\n }\n});\n\n// bind click-off to close modal\n$(document).on('click', '.ui-widget-overlay', function () {\n // find all dialogs?\n var $dialog = $('.ui-dialog-content');\n $dialog.each(function() {\n if ($(this).parents('.no-close').length > 0) {\n return;\n }\n $(this).dialog('close');\n dialog.close(); // if the dialog.js object was used to create it.\n });\n});\n\n// generic event handler to close any dialog containing\n// an element decorated with the \"close-button\" class\n$(document).on('click','.close-button', function() {\n // find the dialog that contains this .close-button\n var $dialog = $(this).parents('.ui-dialog-content');\n $dialog.dialog('close');\n dialog.close(); // if the dialog.js object was used to create it.\n});\n\n$(document).on('click','.close-button-rebook', function() {\n // find the dialog that contains this .close-button\n var $dialog = $(this).parents('.ui-dialog-content');\n $dialog.dialog('close');\n dialog.close(); // if the dialog.js object was used to create it.\n location.reload();\n});\n\n/**\n * Create the dialog object for global use\n */\nvar dialog = {\n /**\n * @function\n * @description Appends a dialog to a given container (target)\n * @param {Object} params params.target can be an id selector or an jquery object\n */\n create: function (params) {\n var $target;\n\n $target = util.getTarget(params.target);\n\n if ($target == null) {\n $target = $('#dialog-container');\n }\n\n // if no element found, create one\n if ($target.length === 0) {\n $target = $('
').attr('id', 'dialog-container').addClass('dialog-content').appendTo('body');\n }\n\n // create the dialog\n this.$container = $target;\n const options = _.merge({}, this.settings, params.options || {});\n this.$container.dialog(options);\n\n // Re-center the dialog when the viewport is resized\n $(window).off('resize.dialog').on('resize.dialog', function () {\n // prevent dialog from becoming full screen on viewport resize\n $target.dialog('option', 'width', params.options.width);\n\n if (util.mediaBreakpointUp('md')) {\n $target.dialog('option', 'position', {my: 'center', at: 'center', of: window});\n } else {\n $target.dialog('option', 'position', {my: 'top', at: 'top', of: window});\n }\n });\n\n // Set the position of the dialog correctly on load\n if (!util.mediaBreakpointUp('md')) {\n this.settings.position = {\n my: 'top',\n at: 'top',\n of: window\n };\n }\n },\n /**\n * @function\n * @description Opens a dialog using the given url (params.url) or html (params.html or params.content) or existing div to dialog (params.target)\n * @param {Object} params\n * @param {Object} params.url should contain the url\n * @param {String} params.html contains the html of the dialog content\n * @param {Object} params.target identifies an existing div in the DOM to dialog. The value can be an id selector (string) or an jquery object.\n */\n open: function (params) {\n // close any open dialog\n this.close();\n this.create(params);\n this.replace(params);\n },\n /**\n * @description populate the dialog with html content, then open it\n **/\n openWithContent: function (params) {\n var content, position, callback;\n\n if (!this.$container) { return; }\n if (!util.getTarget(params.target)) {\n content = params.content || params.html;\n if (!content) {\n return;\n }\n // for reusable dialog containers, empty them out first, then inject content\n this.$container.empty().html(content);\n }\n\n if (!this.$container.dialog('isOpen')) {\n this.$container.dialog('open');\n }\n\n if (params.options) {\n position = params.options.position;\n }\n\n if (!position) {\n position = this.settings.position;\n }\n\n imagesLoaded(this.$container).on('done', function () {\n this.$container.dialog('option', 'position', position);\n }.bind(this));\n\n $('#wrapper').addClass('open-modal')\n .attr('aria-hidden', 'true');\n\n $('.ui-dialog').focus();\n callback = (typeof params.callback === 'function') ? params.callback : function () {};\n callback();\n },\n /**\n * @description Replace the content of current dialog\n * @param {object} params\n * @param {string} params.url - If the url property is provided, an ajax call is performed to get the content to replace\n * @param {string} params.html - If no url property is provided, use html provided to replace\n */\n replace: function (params) {\n if (!this.$container) {\n return;\n }\n if (params.url) {\n params.url = util.appendParamToURL(params.url, 'format', 'ajax');\n ajax.load({\n url: params.url,\n data: params.data,\n callback: function (response) {\n params.content = response;\n this.openWithContent(params);\n }.bind(this)\n });\n } else if (params.content || params.html || util.getTarget(params.target) !== null) {\n this.openWithContent(params);\n }\n },\n /**\n * @function\n * @description Closes the dialog\n */\n close: function () {\n if (!this.$container) {\n return;\n }\n this.$container.dialog('close');\n $('#wrapper').removeClass('open-modal')\n .attr('aria-hidden', 'false');\n },\n exists: function () {\n return this.$container && (this.$container.length > 0);\n },\n isActive: function () {\n return this.exists() && (this.$container.children.length > 0);\n },\n settings: {\n autoOpen: false,\n height: 'auto',\n modal: true,\n resizable: false,\n width: defaultDialogWidth,\n close: function () {\n $(this).dialog('close');\n $('#wrapper').removeClass('open-modal')\n .attr('aria-hidden', 'false');\n },\n position: {\n my: 'center',\n at: 'center',\n of: window,\n collision: 'flipfit'\n }\n },\n /**\n * Convenience error dialog\n * @param {String} errorMsg\n * @param {Object} [target] optional jQuery object target if not using default\n */\n error: function (errorMsg, target) {\n var config = {\n content: '
' + errorMsg + '
',\n options: {\n title: Resources.ERROR,\n dialogClass: 'error-modal'\n }\n };\n if (target) {\n config.target = target;\n }\n dialog.open(config);\n }\n};\n\nmodule.exports = dialog;\n","'use strict';\n\nvar util = require('./util');\n\nvar page = {\n title: '',\n type: '',\n params: util.getQueryStringParams(window.location.search.substr(1)),\n redirect: function (newURL) {\n setTimeout(function () {\n window.location.href = newURL;\n }, 0);\n },\n refresh: function () {\n setTimeout(function () {\n window.location.assign(window.location.href);\n }, 500);\n }\n};\n\nmodule.exports = page;\n","'use strict';\n\nvar productTile = require('../product-tile'),\n progress = require('../progress'),\n util = require('../util');\n\n/**\n * @private\n * @function\n * @description replaces breadcrumbs, lefthand nav and product listing with ajax and puts a loading indicator over the product listing\n */\nfunction updateProductListing(url, focusElement) {\n if (!url || url === window.location.href) {\n return;\n }\n var wideTiles = $('.search-result-content').hasClass('wide-tiles') ? true : false;\n progress.show($('.tab-content'));\n $('.tab-pane.active').load(util.appendParamsToUrl(url, {'listv' : wideTiles, format : 'ajax'}), function () {\n productTile.init();\n progress.hide();\n\n // Focus the given element to make sure that focus is retained through the Ajax call\n if (focusElement) {\n var $focusElement = $(focusElement).eq(0);\n $focusElement.parents('.refinement-content').addClass('active');\n $focusElement.focus();\n }\n });\n}\n\nfunction initializeEvents() {\n productTile.init();\n\n var $main = $('.tab-content');\n $main.on('click', '.refinements a, .pagination a, .breadcrumb-refinement-value a, .clear-all-refinements', function (e) {\n e.preventDefault();\n var focusElement = $(this).attr('id');\n\n if (focusElement) {\n focusElement = '#' + focusElement;\n }\n\n var queryParams = util.getQueryStringParams(util.getQueryString(this.href));\n var urlAfter, urlBefore;\n for (var key in queryParams) {\n var value = queryParams[key];\n if (value === 'id') {\n urlBefore = this.href;\n urlAfter = util.removeParamFromURL(urlBefore, key);\n urlAfter = util.removeParamFromURL(urlBefore, key.replace('prefn', 'prefv'));\n }\n }\n updateProductListing(urlAfter, focusElement);\n });\n}\n\nfunction initAddToFavourite () {\n $('body').on('click', '.add-to-favourite', function (e) {\n e.preventDefault();\n const $this = $(this);\n\n if (!$this.data('loggedcustomer')) {\n var $customURL = util.appendParamToURL(Urls.wishlistAdd, 'pid', $this.data('productid'));\n $('.login-container').attr('data-wishListTarget', $customURL);\n if (!$('.user-account').parent('.user-info').hasClass('active')) {\n $('.user-signin').trigger('click');\n }\n util.scrollBrowser('.user-signin');\n return;\n }\n\n var $clicked = ($this.hasClass('clicked')) ? true : false,\n $currentAction = ($clicked) ? Urls.wishlistRemove : Urls.wishlistAdd,\n $url = util.appendParamToURL($currentAction, 'pid', $this.data('productid'));\n\n $.ajax({\n url: $url,\n method: 'GET',\n dataType: 'json'\n }).done(function() {\n if ($clicked) {\n $this.removeClass('clicked')\n .attr('title', Resources.ADD_TO_FAVOURITES)\n .find('i').switchClass('fas', 'far', 100);\n } else {\n $this.addClass('clicked')\n .attr('title', Resources.REMOVE_FROM_FAVOURITES)\n .find('i').switchClass('far', 'fas',100);\n }\n })\n });\n}\n\nexports.initTile = function () {\n initializeEvents();\n};\n\nexports.initAddToFavourite = initAddToFavourite;\n","'use strict';\n\n\nconst progress = require('./progress'),\n promos = require('./promos'),\n favouritewidget = require('./pages/favouritewidget');\n\nfunction gridViewToggle() {\n $('.toggle-grid').on('click', function () {\n if (!$(this).hasClass('active')) {\n $('.search-result-content').toggleClass('wide-tiles');\n $('.toggle-grid').removeClass('active');\n $(this).addClass('active');\n }\n });\n}\n\n$(document).on('toastNotify', function(event, type){\n if(type === 'removed'){\n $('.toastMessageContainer i').removeClass('fa-check-circle').addClass('fa-times-circle');\n $('.toastMessage-text').text($('.toastMessage').data('messageRemoved'));\n } else if (type === 'added'){\n $('.toastMessageContainer i').removeClass('fa-times-circle').addClass('fa-check-circle');\n $('.toastMessage-text').text($('.toastMessage').data('messageAdded'));\n }\n\n $('.toastMessage').addClass('toastVisible');\n setTimeout(function(){\n $('.toastMessage').removeClass('toastVisible');\n }, 2000);\n});\nfunction getCookie(name) {\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${name}=`);\n if (parts.length === 2) return parts.pop().split(';').shift();\n}\n/**\n * @private\n * @function\n * @description Initializes events on the product-tile for the following elements:\n * - swatches\n * - thumbnails\n */\nfunction initializeEvents() {\n gridViewToggle();\n favouritewidget.initAddToFavourite();\n\n $(function() {\n $('.pdp-link .link').each(function() {\n var productTitle = $(this).text();\n var titleLength = productTitle.length;\n if(titleLength > 50) {\n $(this).text($(this).text().substring(0,49)+'...');\n }\n })\n })\n\n /**\n * @listener\n * @desc Listens for the click event on the product tile add-to-cart button\n */\n $('body').off('click.producttile').on('click.producttile', '.tile-add-to-cart', function (e) {\n e.preventDefault();\n if (!getCookie('dw_shippostalcode')) {\n $('#popUpCep').slideDown();\n $('#popUpCep').modal('show'); //MOSTRA O MODAL DE CEP\n } else {\n const productTile = $(this).parents('.product-tile');\n const suppressMinicart = $(this).parents('.promo-modal').length > 0 && !$('.pt_beforeyougo').length;\n addToCart(productTile, suppressMinicart);\n }\n });\n\n /**\n * @listener\n * @desc Listens for the basketupdate event from the mini cart and updates the product tile\n */\n $(document).off('basketupdate.producttile').on('basketupdate.producttile', function (e) {\n if (e.source === 'minicart' && e.quantity === 0) {\n const productTile = $('.product-tile[data-itemid=\"' + e.pid + '\"]');\n\n // Product was removed from the mini cart\n const jqXHR = $.ajax({\n url: window.Urls.hitTile,\n data: {\n pid: e.pid\n }\n });\n\n // Show the progress indicator over the product tile\n progress.show(productTile);\n\n jqXHR.done(function (html) {\n productTile.replaceWith(html);\n // TODO: Re-initialize the product ratings when implemented\n\n $(document).trigger('toastNotify', 'removed');\n });\n\n // Make sure to remove the progress indicator after the Ajax call\n jqXHR.always(function () {\n progress.hide();\n });\n }\n });\n}\n\n/**\n * @function\n * @desc Adds the given product ID to the cart\n * @param {jQuery} productTile - Product tile used to get the product ID\n */\nfunction addToCart(productTile, suppressMinicart) {\n if (productTile){\n const pid = productTile.find('.product-options').attr('data-pid');\n\n const jqXHR = $.ajax({\n method: 'post',\n url: window.Urls.tileAddToCart,\n data: {\n pid: pid,\n Quantity: 1\n }\n });\n\n // Show the progress indicator over the product tile\n progress.show(productTile);\n\n jqXHR.done(function (html) {\n $('.product-tile[data-itemid=\"' + pid + '\"]').replaceWith(html);\n\n // TODO: Re-initialize the product ratings when implemented\n $(document).trigger('toastNotify', 'added');\n\n $(document).trigger({\n type: 'basketupdate',\n pid: pid,\n quantity: 1,\n source: 'tile',\n suppress: suppressMinicart\n\n });\n\n });\n\n // Make sure to remove the progress indicator after the Ajax call\n jqXHR.always(function () {\n progress.hide();\n });\n\n return jqXHR;\n }\n}\n\nexports.init = function () {\n initializeEvents();\n promos.init();\n promos.addPromoMessages();\n};\n\nexports.addToCart = addToCart;\nexports.initializeEvents = initializeEvents;\n","'use strict';\n\nvar $loader = null;\nvar $target = null;\n\n/**\n * @function\n * @description Shows an AJAX-loader on top of a given container\n * @param {Element} container The Element on top of which the AJAX-Loader will be shown\n * @param {Boolean} adjustContainerPosition Default value is true, allowing the target's css 'position' rule to\n temporarily be adjusted to 'relative' while the overlay is active. Passing a value of false disallows this.\n */\nvar show = function (container, adjustContainerPosition) {\n hide(); // just in case, remove any other progress overlays. This isn't built to support multiple.\n\n $target = (!container || $(container).length === 0) ? $('body') : $(container);\n $loader = $loader || $('.loader');\n\n if ($loader.length === 0) {\n $loader = $('').addClass('loader')\n .append($('').addClass('loader-indicator'), $('').addClass('loader-bg'));\n }\n if (adjustContainerPosition !== false) {\n $target.addClass('pos-relative').data('position-adjusted', true);\n }\n return $loader.appendTo($target).show();\n};\n/**\n * @function\n * @description Hides an AJAX-loader\n */\nvar hide = function () {\n if ($loader) {\n $loader.hide();\n }\n if ($target && $target.length && $target.data('position-adjusted')) {\n $target.removeClass('pos-relative');\n }\n};\n\nexports.show = show;\nexports.hide = hide;\n","'use strict';\n\nconst ajax = require('./ajax'),\n dialog = require('./dialog'),\n util = require('./util'),\n page = require('./page'),\n progress = require('./progress');\n\n/**\n * @module\n * @desc Contains the custom promotion functionality for use throughout the site\n */\n\nconst Promotions = {\n\n /**\n * @function\n * @desc Initializes the custom promotion functionality\n */\n init: function () {\n initializeEvents();\n },\n\n /**\n * @function\n * @desc Retrieves the quantities for custom promotions from the basket\n * @param {String} ID - The ID of the promotion to get the quantities from (optional)\n * @returns\n */\n getBasketPromotionsQuantities: function (ID) {\n const productLineItems = window.ProductLineItems;\n const promotions = {};\n let productLineItem,\n lineItemPromos,\n qty;\n\n for (var i in productLineItems) {\n productLineItem = productLineItems[i];\n lineItemPromos = productLineItem.promoInfo;\n\n if (!lineItemPromos) {\n continue;\n }\n\n qty = productLineItem.quantity;\n\n for (var j in lineItemPromos) {\n const promo = lineItemPromos[j];\n\n if (promo.t === 'Combination') {\n if (promotions[promo.ID] == null) {\n promotions[promo.ID] = promo.groupReq.map(function (value, index) {\n return (index === promo.groupNum) ? qty : 0;\n });\n } else {\n promotions[promo.ID][promo.groupNum] += qty;\n }\n } else {\n promotions[promo.ID] = (promotions[promo.ID] == null) ? qty : promotions[promo.ID] + qty;\n }\n }\n }\n\n if (ID) {\n return promotions[ID];\n }\n\n return promotions;\n },\n\n /**\n * @function\n * @description Updates or builds UI for product promo nearness messaging\n */\n addPromoMessages: function() {\n const self = this;\n const promotionsModal = {};\n $('.mini-cart-product, .cart-row, .product-options, .product-offers').each(function(index, element) {\n const UUID = $(element).data('item-uuid');\n\n if (UUID && window.ProductLineItems[UUID]) {\n const promos = window.ProductLineItems[UUID];\n const promosInfo = window.ProductLineItems[UUID].promoInfo;\n\n if (promosInfo && promosInfo.length) {\n promosInfo.forEach(function (promo, promoCount) {\n let newPromo = promo; // eslint-disable-line\n let $offerStatus;\n\n newPromo.qty = promos.quantity;\n\n if ($(element).hasClass('product-options') && $(element).parents('.product-tile').length) {\n $offerStatus = $(element).parents('.product-tile').find('.offer-section').eq(promoCount).find('.offer-status');\n } else if ($(element).hasClass('product-options') && $(element).parents('.product-suggestion').length) {\n $offerStatus = $(element).parents('.product-suggestion').find('.offer-section').eq(promoCount).find('.offer-status');\n } else {\n $offerStatus = $(element).find('.offer-section').eq(promoCount).find('.offer-status');\n }\n\n const $modalOfferStatus = $('.promo-modal-body[data-promo=\"'+ newPromo.ID +'\"] .offer-status, .promo-landing-page[data-promo=\"'+ newPromo.ID +'\"] .offer-status');\n self.setupPromoMessage(newPromo, $offerStatus);\n self.setupPromoMessage(newPromo, $modalOfferStatus);\n\n if (!promotionsModal[newPromo.ID]) {\n promotionsModal[newPromo.ID] = newPromo;\n }\n });\n } else if ($('.promo-modal').length) {\n // Reset the offer status message to it's initial value\n const offerStatus = $('.promo-modal .offer-status').attr('data-initial-message');\n $('.promo-modal .offer-status').text(offerStatus);\n }\n } else if (UUID) {\n const $offerStatus = $(element).find('.offer-status');\n $offerStatus.empty();\n\n if ($('.promo-modal').length) {\n // Reset the offer status message to it's initial value\n const offerStatus = $('.promo-modal .offer-status').attr('data-initial-message');\n $('.promo-modal .offer-status').text(offerStatus);\n }\n }\n });\n\n if ($('.promo-landing-page, .promo-modal').size() > 0) {\n if (Object.keys(promotionsModal).length) {\n var promoID = $('.promo-landing-page, .promo-modal').data('promo');\n for (var k in promotionsModal) {\n const promo = promotionsModal[k];\n if (promoID == promo.ID) {\n self.updatePromoQuantity(promo);\n }\n }\n } else {\n self.updatePromoQuantity();\n }\n }\n },\n\n /**\n * @function\n * @desc Creates the promotion message based on the given data\n * @param {Object} Object containing the promotion information\n * @param {jQuery} jQuery object containing the offer status DOM location\n */\n setupPromoMessage: function setupPromoMessage(promo, $offerStatus) {\n const msgGotIt = window.Resources.YOU_GOT_IT;\n const msgNear = window.Iconography.plus_regular + window.Resources.N_MORE_TO_GET_IT;\n const msgNearAgain = window.Iconography.plus_regular + window.Resources.N_MORE_TO_GET_IT_AGAIN;\n const customPromos = (promo.restrict) ? {[promo.ID] : promo.qty} : this.getBasketPromotionsQuantities();\n\n var msg = '',\n qty = customPromos[promo.ID],\n maxApp = promo.maxApp ? parseInt(promo.maxApp, 10) : 0,\n qualifiedPromos = 0,\n remaining = 0;\n\n if (promo.t === 'Combination') {\n let qualifies = true;\n\n while (qualifies && qualifiedPromos <= maxApp) {\n for (let i = 0; i < promo.groupReq.length; i++) {\n var groupceil = promo.groupReq[i] + (promo.groupReq[i] * qualifiedPromos);\n\n if (qty[i] < groupceil) {\n qualifies = false;\n remaining += groupceil - qty[i];\n }\n }\n\n if (qualifies) {\n qualifiedPromos++;\n } else if (promo.req == remaining) {\n remaining = 0;\n }\n }\n } else {\n var requiredProducts = parseInt(promo.req, 10);\n\n qualifiedPromos = (qty / requiredProducts) >> 0;\n var ceil = (qty % requiredProducts === 0) ? qualifiedPromos * requiredProducts : requiredProducts + (qualifiedPromos * requiredProducts);\n remaining = ceil - qty;\n }\n\n const promoMet = (remaining === 0 || (maxApp > 0 && qualifiedPromos === maxApp));\n\n if (promoMet) {\n msg = msgGotIt;\n $('.offer-status-modal').addClass('finished');\n\n } else if (qualifiedPromos >= 1 && remaining !== 0) {\n msg = msgNearAgain.replace('{0}', remaining);\n $('.offer-status-modal').removeClass('finished');\n } else {\n msg = msgNear.replace('{0}', remaining);\n $('.offer-status-modal').removeClass('finished');\n $('.modal-footer').removeClass('modal-footer-complete');\n }\n\n if (msg !== '') {\n msg = '' + msg + '';\n }\n\n $offerStatus.html(msg);\n },\n\n /**\n * @function\n * @desc Update promotion product quantities\n * @param {Object} Object containing the promotion information (optional)\n */\n updatePromoQuantity: function updatePromoQuantity(promo) {\n if (promo) {\n const promoQuantities = this.getBasketPromotionsQuantities(promo.ID);\n\n let qualifiedPromos = 0, // Number of times the promotion has been qualified for\n qualifiedGroups = 0, // Number of qualified groups in the promotion\n maxQualifiedGroupCount = 1, // Max qualification from all groups in the promotion\n selectedBonusProducts = 0, // Number of bonus products selected in the promotion\n maxBonusProducts = 0; // Max number of bonus products that can be selected\n\n if (promo.t === 'Combination') {\n // Loop through each promotion group to find the maximum qualfied group count. This gives us the correct denominator in our group counts.\n for (let i = 0; i < promo.groupReq.length; i++) {\n const productQty = promoQuantities[i];\n var productQtyRatio = Math.floor(productQty / promo.groupReq[i]);\n //if qty increased by 1 item ratio logic will not work correctly\n if (productQty > promo.groupReq[i] && productQtyRatio == 1) {\n productQtyRatio++;\n }\n\n if (productQtyRatio > maxQualifiedGroupCount) {\n maxQualifiedGroupCount = productQtyRatio;\n }\n }\n\n var qualifiedPromotionCount = null;\n\n // Loop through each promotion group to find the required and current quantities\n for (let i = 0; i < promo.groupReq.length; i++) {\n\n const $promoNavItems = $('.promo-step-list .promo-step-link:eq(' + i + ')', '.promo-modal, .promo-landing-page'),\n $promoStepLink = $promoNavItems,\n $promoStepQty = $promoStepLink.find('.promo-qty'),\n $promoStepReqQty = $promoStepLink.find('.promo-req-qty');\n\n const productQty = promoQuantities[i];\n const promoRequiredQty = maxQualifiedGroupCount * promo.groupReq[i];\n\n $promoStepReqQty.text(promoRequiredQty);\n $promoStepQty.text(productQty);\n\n if (productQty < promoRequiredQty) {\n $promoStepLink.removeClass('completed');\n } else if (productQty >= promoRequiredQty && (promoRequiredQty % productQty === 0)) {\n $promoStepLink.addClass('completed');\n }\n\n var qualifiedPromotions = Math.floor(productQty / promo.groupReq[i]);\n qualifiedGroups += (qualifiedPromotions >= 1) ? 1 : 0;\n if (qualifiedPromotionCount === null) {\n qualifiedPromotionCount = qualifiedPromotions;\n } else if (qualifiedPromotions < qualifiedPromotionCount) {\n qualifiedPromotionCount = qualifiedPromotions;\n }\n }\n\n qualifiedPromos = Math.floor(qualifiedGroups / promo.groupReq.length);\n\n // Update the bonus product quantities based on the current discount line items\n const $promoBonusStepLinks = $('.promo-step-list .bonus-product', '.promo-modal, .promo-landing-page');\n\n for (var j in window.BonusDiscountLineItems) {\n if (window.BonusDiscountLineItems[j].promotionID === promo.ID) {\n selectedBonusProducts += window.BonusDiscountLineItems[j].bonusProducts.length;\n maxBonusProducts += window.BonusDiscountLineItems[j].maxBonusItems;\n }\n }\n\n // Update the select bonus product quantity display\n $promoBonusStepLinks.find('.promo-qty').text(selectedBonusProducts);\n\n // Make sure the max bonus products is at least the minimum value for one promotion qualification\n if (maxBonusProducts === 0) {\n maxBonusProducts = $promoBonusStepLinks.attr('data-required-products');\n }\n\n $promoBonusStepLinks.find('.promo-req-qty').text(maxBonusProducts);\n\n const $promoBonus = $('.promo-step-list').find('.bonus-product-choice, .bonus-product');\n $promoBonus.find('.promo-req-qty').text(maxQualifiedGroupCount * $promoBonus.data('required-products'));\n\n if (qualifiedPromos >= 1) {\n $promoBonusStepLinks.removeClass('disabled').addClass('unlocked');\n $promoBonus.find('.promo-qty').text(qualifiedPromotionCount * $promoBonus.data('required-products'));\n } else {\n $promoBonusStepLinks.addClass('disabled').removeClass('unlocked');\n $promoBonus.find('.promo-qty').text(0);\n }\n }\n } else {\n // Clear all of the states of the promotions if there are none to update\n const $promoNavItems = $('.promo-step-list', '.promo-modal, .promo-landing-page'),\n $promoStepLinks = $promoNavItems.find('.promo-step-link'),\n $promoStepQtys = $promoStepLinks.find('.promo-qty'),\n $promoBonusStep = $promoStepLinks.closest('.bonus-product'),\n $promoBonusStepQtys = $promoBonusStep.find('.promo-qty');\n\n $promoStepLinks.removeClass('completed');\n $promoStepQtys.text(0);\n $promoBonusStepQtys.text(0);\n\n $promoBonusStep.addClass('disabled').removeClass('unlocked');\n }\n }\n};\n\n/**\n * @function\n * @desc Displays promotion in modal\n * @returns\n */\nfunction displayPromoModal () {\n const promoID = $(this).attr('data-promo'),\n promotionName = $(this).data('promo-name'),\n promotionNameTxt = (promotionName && promotionName.length) ? promotionName : window.Resources.PROMO_MODAL_TITLE;\n\n console.log('promotionName', promotionName);\n console.log('promotionNameTxt', promotionNameTxt);\n\n if ($(this).hasClass('disabled')) {\n return;\n }\n\n if (window.User.zip) {\n const step = $(this).data('step');\n\n progress.show();\n\n dialog.open({\n url: window.Urls.promoModal,\n data: {\n promo: promoID,\n step: step\n },\n options: {\n title: promotionNameTxt,\n dialogClass: 'promo-modal',\n buttons: [\n {\n text: window.Resources.PROMO_MODAL_CARRY_ON_BUTTON,\n click: function() {\n dialog.close();\n }\n }\n ],\n close: function() {\n $('#wrapper').removeClass('open-modal')\n .attr('aria-hidden', 'false');\n if ($('#wrapper').hasClass('basket-updated')) {\n page.refresh();\n }\n }\n },\n callback: function () {\n $('.promo-modal').attr('data-promo', promoID)\n .removeClass('deal-na');\n\n if ($('.promo-modal .promo-modal-body').length <= 0) {\n $('.promo-modal').addClass('deal-na');\n }\n\n progress.hide();\n Promotions.addPromoMessages();\n\n $('.ui-dialog-buttonset button').removeClass('ui-button');\n\n // Tab show click handler to update next button\n $('a[data-toggle=\"tab\"]').on('shown.bs.tab', function(e) {\n\n if ($(e.target).hasClass('bonus-product-choice')) {\n\n getBonusProducts($(e.target).attr('href'));\n\n if ($('.ui-dialog-buttonset .add-to-cart-bonus').length <= 0) {\n var atcButton = $('');\n atcButton.addClass('btn btn-primary btn-next add-to-cart-bonus');\n $('.ui-dialog-buttonset').append(atcButton);\n }\n } else if ($(e.target).hasClass('bonus-product')) {\n\n const promotionID = $('.promo-landing-page, .promo-modal').attr('data-promo');\n\n var url = util.appendParamToURL(Urls.getBonusProductsConfirmation, 'promotionID', promotionID);\n\n ajax.load({\n url: url,\n target: $($(e.target).attr('href'))\n });\n }\n });\n\n // Set visable tab based on selected step\n var $currentStep = $('.promo-step-list', '.promo-modal').data('selectedstep');\n\n if ($currentStep >= 0) {\n const elementIndex = $currentStep - 1;\n $('.promo-modal .promo-step-list .promo-step-link:eq(' + elementIndex + ')').tab('show');\n }\n }\n });\n } else {\n const util = require('./util');\n\n if (util.mediaBreakpointUp('md')) {\n $('.header-delivery-details').click();\n util.scrollBrowser(0);\n } else {\n $('.menu-toggle').click();\n $('.left-nav-minicart input[name=\"postcode\"]').focus();\n }\n }\n}\n\nfunction getBonusProducts(target) {\n\n const promoID = $('.promo-landing-page, .promo-modal').attr('data-promo');\n\n var url = util.appendParamsToUrl(Urls.getBonusProducts, {\n promotionID: promoID,\n format: 'ajax',\n lazyLoad: 'false',\n promomodal: 'true',\n pageStart: 0,\n pageSize: 10,\n bonusProductsTotal: -1\n });\n\n var jqXHR = $.ajax({\n type: 'GET',\n cache: false,\n contentType: 'application/json',\n url: url\n });\n\n jqXHR.done(function (data) {\n $(target).html(data);\n\n var bonusProductsView = require('./bonus-products-view');\n bonusProductsView.initPromoModal();\n\n // Position the modal after loading the bonus products\n if (util.mediaBreakpointUp('md')) {\n dialog.$container.dialog('option', 'position', {my: 'center', at: 'center', of: window});\n } else {\n dialog.$container.dialog('option', 'position', {my: 'top', at: 'top', of: window});\n }\n });\n\n jqXHR.fail(function (xhr, textStatus) {\n if (textStatus === 'parsererror') {\n window.alert(Resources.BAD_RESPONSE);\n } else {\n window.alert(Resources.SERVER_CONNECTION_ERROR);\n }\n });\n}\n\n/**\n * @function\n * @desc Initializes the custom promotion event handlers\n * @returns\n */\nfunction initializeEvents() {\n /**\n * @listener\n * @desc Listens for the click event on the offer section and opens a dialog for the promotion\n */\n $('body').on('click', '.offer-section', displayPromoModal);\n\n /**\n * @listener\n * @desc Listens for the click event on promo landing page steps and opens a dialog for the promotion\n */\n $('.promo-landing-page').on('click', '.promo-step-link', displayPromoModal);\n}\n\nmodule.exports = Promotions;\n","'use strict';\n\nvar _ = require('lodash'),\n smallBreakpoint = 320,\n mediumBreakpoint = 480,\n largeBreakpoint = 768,\n desktopBreakpoint = 1024,\n maxBreakpoint = 1280;\n\nvar util = {\n /**\n * @desc Media breakpoints that are used throughout the Javascript\n */\n breakpoints: {\n xs: smallBreakpoint,\n sm: mediumBreakpoint,\n md: largeBreakpoint,\n lg: desktopBreakpoint,\n xl: maxBreakpoint,\n 'mobile-menu': largeBreakpoint\n },\n\n /**\n * @function\n * @description Returns either an object with all of the available breakpoints or a specific viewport based on the given size\n * @param {string} size The viewport to return\n * @param {string} breakpoints A custom breakpoints object\n */\n getViewports: function (size, breakpoints) {\n const bps = typeof breakpoints !== 'undefined' ? breakpoints : this.breakpoints;\n\n if (typeof size !== 'undefined') {\n var viewport = bps[size];\n\n if (viewport) {\n return viewport;\n } else {\n window.console.error('Unexpected viewport size given in util.getViewports');\n throw 'Unexpected viewport size given in util.getViewports';\n }\n } else {\n return breakpoints;\n }\n },\n\n /**\n * @function\n * @description Returns the current viewport name (ex: 'medium') or 'max' if the current window is larger than any defined viewport width\n */\n getCurrentViewport: function () {\n var w = window.innerWidth;\n var viewports = util.getViewports();\n //traverse the object from small up to desktop, and return the first match\n _.each(viewports, function (value, name) {\n if (w <= value) {\n return name;\n }\n });\n return 'max';\n },\n\n /**\n * @function\n * @description appends the parameter with the given name and value to the given url and returns the changed url\n * @param {String} url the url to which the parameter will be added\n * @param {String} name the name of the parameter\n * @param {String} value the value of the parameter\n */\n appendParamToURL: function (url, name, value) {\n // quit if the param already exists\n if (url.indexOf(name + '=') !== -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 */\n removeParamFromURL: function (url, name) {\n if (url.indexOf('?') === -1 || url.indexOf(name + '=') === -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 if (params[i].split('=')[0] !== name) {\n newParams.push(params[i]);\n }\n }\n return domain + '?' + newParams.join('&') + (hash ? '#' + hash : '');\n },\n\n /**\n * @function\n * @description appends the parameters to the given url and returns the changed url\n * @param {String} url the url to which the parameters will be added\n * @param {Object} params\n */\n appendParamsToUrl: function (url, params) {\n var _url = url;\n _.each(params, function (value, name) {\n _url = this.appendParamToURL(_url, name, value);\n }.bind(this));\n return _url;\n },\n /**\n * @function\n * @description extract the query string from URL\n * @param {String} url the url to extra query string from\n **/\n getQueryString: function (url) {\n var qs;\n if (!_.isString(url)) { return; }\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 * @function\n * @description\n * @param {String}\n * @param {String}\n */\n elementInViewport: function (el, offsetToTop) {\n var top = el.offsetTop,\n left = el.offsetLeft,\n width = el.offsetWidth,\n height = el.offsetHeight;\n\n while (el.offsetParent) {\n el = el.offsetParent;\n top += el.offsetTop;\n left += el.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\n /**\n * @function\n * @description Appends the parameter 'format=ajax' to a given path\n * @param {String} path the relative path\n */\n ajaxUrl: function (path) {\n return this.appendParamToURL(path, 'format', 'ajax');\n },\n\n /**\n * @function\n * @description\n * @param {String} url\n */\n toAbsoluteUrl: function (url) {\n if (url.indexOf('http') !== 0 && url.charAt(0) !== '/') {\n url = '/' + url;\n }\n return url;\n },\n /**\n * @function\n * @description Loads css dynamically from given urls\n * @param {Array} urls Array of urls from which css will be dynamically loaded.\n */\n loadDynamicCss: function (urls) {\n var i, 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 */\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 (0 > i--) {\n $(this.loadedCssFiles[i]).remove();\n }\n this.loadedCssFiles = [];\n },\n /**\n * @function\n * @description Extracts all parameters from a given query string into an object\n * @param {String} qs The query string from which the parameters will be extracted\n */\n getQueryStringParams: function (qs) {\n if (!qs || qs.length === 0) { return {}; }\n var params = {},\n 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 fillAddressFields: function (address, $form) {\n for (var field in address) {\n if (field === 'ID' || field === 'UUID' || field === 'key' || field === 'type') {\n continue;\n }\n if (field === 'displayValue') {\n $form.find('input[id$=\"edit_addressId\"]').val(address[field]);\n $form.find('input[name$=\"_customerAddressID\"]').val(address[field]);\n }\n if (field === 'postalCode') {\n $form.find('input[id$=\"edit_zipCode\"]').val(address[field]);\n }\n\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 .addClass('text-entered');\n\n if (field === 'city'){\n $form.find('select[id$=\"edit_city\"]').val(address[field]);\n }\n if (field === 'stateCode'){\n $form.find('select[id$=\"edit_state\"]').val(address[field]);\n }\n \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).addClass('text-entered');\n }\n }\n },\n /**\n * @function\n * @description Updates the number of the remaining character\n * based on the character limit in a text area\n */\n limitCharacters: function () {\n $('form').find('textarea[data-character-limit]').each(function () {\n var characterLimit = $(this).data('character-limit');\n var charCountHtml = String.format(Resources.CHAR_LIMIT_MSG,\n '' + characterLimit + '',\n '' + characterLimit + '');\n var charCountContainer = $(this).next('div.char-count');\n if (charCountContainer.length === 0) {\n charCountContainer = $('
').insertAfter($(this));\n }\n charCountContainer.html(charCountHtml);\n // trigger the keydown event so that any existing character data is calculated\n $(this).change();\n });\n },\n /**\n * @function\n * @description Binds the onclick-event to a delete button on a given container,\n * which opens a confirmation box with a given message\n * @param {String} container The name of element to which the function will be bind\n * @param {String} message The message the will be shown upon a click\n */\n setDeleteConfirmation: function (container, message) {\n $(container).on('click', '.delete', function () {\n return window.confirm(message);\n });\n },\n /**\n * @function\n * @description Scrolls a browser window to a given x point\n * @param {String} The x coordinate\n */\n scrollBrowser: function (xLocation) {\n $('html, body').animate({scrollTop: xLocation}, 500);\n },\n\n /**\n * @function\n * @desc Determines if the device that is being used is mobile\n * @returns {Boolean}\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 * Executes a callback function when the user has stopped resizing the screen.\n *\n * @param {function} callback\n * @var obj timeout\n *\n * @return {function}\n */\n smartResize: 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 */\n mediaBreakpointUp: function (size, breakpoints) {\n const breakpoint = this.getViewports(size, breakpoints);\n const mediaQuery = window.matchMedia('(min-width: '+ breakpoint +'px)');\n return mediaQuery.matches;\n },\n\n /**\n * @function\n * @desc Generates a min-width matchMedia media query based on the given params\n * @param {string} size - Breakpoint to use for the media query\n * @param {object} breakpoints - Override of the util breakpoints object (optional)\n */\n mediaBreakpointDown: function (size, breakpoints) {\n const bps = typeof breakpoints !== 'undefined' ? breakpoints : this.breakpoints;\n const nextSize = this.getNextObjectKey(bps, size);\n\n if (typeof nextSize === 'string') {\n const breakpoint = this.getViewports(nextSize, breakpoints) - 1;\n const mediaQuery = window.matchMedia('(max-width: '+ breakpoint +'px)');\n return mediaQuery.matches;\n } else {\n return true;\n }\n },\n\n /**\n * @function\n * @desc Generates a min-width and max-width matchMedia media queries based on the given params\n * @param {string} sizeMin - Min breakpoint to use for the media query\n * @param {string} sizeMax - Max breakpoint to use for the media query\n * @param {object} breakpoints - Override of the util breakpoints object (optional)\n */\n mediaBreakpointBetween: function (sizeMin, sizeMax, breakpoints) {\n const min = this.mediaBreakpointUp(sizeMin, breakpoints);\n const max = this.mediaBreakpointDown(sizeMax, breakpoints);\n\n return min && max;\n },\n\n /**\n * @function\n * @desc Generates a min-width and max-width matchMedia media query based on the given params\n * @param {string} size - Breakpoint to use for the media query\n * @param {object} breakpoints - Override of the util breakpoints object (optional)\n */\n mediaBreakpointOnly: 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 * @returns {string}|{null}\n */\n getNextObjectKey: function (obj, key) {\n const keys = Object.keys(obj);\n const nextIndex = keys.indexOf(key) + 1;\n\n if (keys.length > nextIndex) {\n return keys[nextIndex];\n } else {\n return null;\n }\n },\n\n /**\n * @function\n * @desc Retrieves the util breakpoints object\n * @returns {object}\n */\n getBreakpoints: function () {\n return this.breakpoints;\n },\n\n\n /**\n * @function\n * @desc Generate a unique number. Useful for tagging unique jquery selectors or namespacing js events\n * @returns {object}\n */\n guid: function () {\n var timestamp = new Date().getUTCMilliseconds();\n return timestamp;\n },\n\n /**\n * @function\n * @desc Detect click, touch, or focus outside an element and execute a callback function\n * @param {jQuery} $parentTrigger - the element or wrapper outside of which to detect a click\n * @param {function} callBack - The callback function to execute on detection of a clickOutside action\n * @returns {string} eventID - the created event name, ex: \"click.outside321 touchstart.outside321 focusin.outsidd321\"\n */\n clickOutside: function ($parentTrigger, callBack) {\n\n // get a unique ID string\n var guid = util.guid();\n\n // assign a specific namespace to a click/touchstart/focus handler on the document,\n // so we can unbind it later without unbinding all click/touchstart/focus document events.\n var clicknamespace = 'click.outside' + guid + ' touchstart.outside'+ guid + ' focusin.outside'+ guid;\n\n // Wrapping this in a zero-time setTimeout allows it to ignore the initial event whose handler triggered this function.\n // otherwise this event could register as an immediate \"click outside\" and trigger this callBack function\n setTimeout(function () {\n $(document).on(clicknamespace, function(e) {\n if ($(e.target).parents().is($parentTrigger) == false) {\n if (typeof callBack === 'function') {\n // execute the callback function, if defined\n callBack();\n }\n // unbind this event after it's served its purpose, so we don't have these stack up\n $(document).off(clicknamespace);\n }\n });\n }, 0);\n\n //return the created event name, in case a different trigger needs to unbind it.\n return clicknamespace;\n },\n\n /**\n * @function\n * @desc Given a valid selector string or jQuery object, returns a jQuery object. Returns null is the given string is not a valid selector.\n * @param {String || jQuery} target - should represent a selector for a jQuery object, or should be a jQuery object itself. Ex: '.myselector' or $('.myselector')\n */\n getTarget: function (target) {\n if (_.isString(target)) {\n return $(target);\n } else if (target instanceof jQuery) {\n return target;\n }\n return null;\n },\n\n /**\n * Determinate is empty object or not\n * @param {Object} incoming object\n * @return {Boolean} true - empty, false - otherwise\n */\n isEmpty: function (obj) {\n for (var key in obj) {\n if (obj.hasOwnProperty(key))\n return false;\n }\n return true;\n }\n};\n\nmodule.exports = util;\n","'use strict';\n\nvar errorClass = 'error';\nvar successClass = 'valid';\nvar naPhone = /^\\(?([2-9][0-8][0-9])\\)?[\\-\\. ]?([2-9][0-9]{2})[\\-\\. ]?([0-9]{4})(\\s*x[0-9]+)?$/;\nvar regex = {\n phone: {\n us: naPhone,\n ca: naPhone,\n fr: /^0[1-6]{1}(([0-9]{2}){4})|((\\s[0-9]{2}){4})|((-[0-9]{2}){4})$/,\n it: /^(([0-9]{2,4})([-\\s\\/]{0,1})([0-9]{4,8}))?$/,\n jp: /^(0\\d{1,4}- ?)?\\d{1,4}-\\d{4}$/,\n cn: /.*/,\n gb: /^((\\(?0\\d{4}\\)?\\s?\\d{3}\\s?\\d{3})|(\\(?0\\d{3}\\)?\\s?\\d{3}\\s?\\d{4})|(\\(?0\\d{2}\\)?\\s?\\d{4}\\s?\\d{4}))(\\s?\\#(\\d{4}|\\d{3}))?$/\n },\n postal: {\n us: /^\\d{5}(-\\d{4})?$/,\n ca: /^[ABCEGHJKLMNPRSTVXY]{1}\\d{1}[A-Z]{1} *\\d{1}[A-Z]{1}\\d{1}$/,\n fr: /^(F-)?((2[A|B])|[0-9]{2})[0-9]{3}$/,\n it: /^([0-9]){5}$/,\n jp: /^([0-9]){3}[-]([0-9]){4}$/,\n cn: /^([0-9]){6}$/,\n gb: /^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9][A-Za-z]?))))\\s?[0-9][A-Za-z]{2})$/,\n xx: /^.{4,10}$/\n },\n notCC: /^(?!(([0-9 -]){13,19})).*$/,\n password: /^(?=.*[a-zA-Z0-9])(?=.*[A-Z])(?=.*[-!$%^&*()_+|~=`{}\\[\\]:\";'<>?,.\\/@]).{8,}$/,\n date: /^(0?[1-9]|[12]\\d|3[01])[\\/](0?[1-9]|1[0-2])[\\/](19|20)\\d{2}$/\n};\n// global form validator settings\nvar settings = {\n errorClass: errorClass,\n errorElement: 'span',\n errorPlacement: function ($error, $element) {\n var $selectStyle = $element.parent('.select-style');\n if ($selectStyle.length) {\n $selectStyle.after($error);\n $selectStyle.removeClass(successClass).addClass(errorClass);\n } else if ($element.attr('type') === 'checkbox' || $element.attr('type') === 'radio' || $element.parents('.floatlabel').length) {\n var $label = $element.next('label');\n\n if ($label.length) {\n $label.after($error);\n } else {\n $element.after($error);\n }\n } else {\n $element.after($error);\n }\n },\n ignore: '.suppress, :hidden',\n onkeyup: false,\n onclick: function (element) {\n return $(element).is('select');\n },\n onfocusout: function (element) {\n if (!this.checkable(element) && !$(element).parents('.login-form').length) {\n this.element(element);\n }\n },\n success: function (error, element) {\n $(error).remove();\n var $selectStyle = $(element).parent('.select-style');\n if ($selectStyle.length) {\n $selectStyle.removeClass(errorClass).addClass(successClass);\n }\n },\n validClass: successClass\n};\n/**\n * @function\n * @description Validates a given phone number against the countries phone regex\n * @param {String} value The phone number which will be validated\n * @param {String} el The input field\n */\nvar validatePhone = function (value, el) {\n var country = $(el).closest('form').find('.country'),\n countryValue;\n if (country.length === 0 || country.val().length === 0 || !regex.phone[country.val().toLowerCase()]) {\n countryValue = window.SessionAttributes.REQUEST_COUNTRY;\n } else {\n countryValue = country.val().toLowerCase();\n }\n\n var rgx = regex.phone[countryValue];\n var isOptional = this.optional(el);\n var isValid = rgx.test($.trim(value));\n\n return isOptional || isValid;\n};\n\n/**\n * @function\n * @description Validates that a credit card owner is not a Credit card number\n * @param {String} value The owner field which will be validated\n * @param {String} el The input field\n */\nvar validateOwner = function (value) {\n var isValid = regex.notCC.test($.trim(value));\n return isValid;\n};\n\n/**\n * function\n * description Validates a given postal code against the countries postal code regex\n * param {String} value The postal code which will be validated\n * param {String} el The input field\n */\nvar validatePostalCode = function (value, el) {\n var postalCode = $(el).closest('form').find('.postal');\n var country = $(el).closest('form').find('.country');\n var rgx;\n var rgxXX = window.SessionAttributes.REQUEST_COUNTRY;\n if (country.length > 0 && !regex.postal[country.val().toLowerCase()]) {\n rgx = regex.postal[rgxXX];\n } else {\n if (country.length > 0) {\n rgx = regex.postal[country.val().toLowerCase()];\n } else {\n rgx = regex.postal[rgxXX];\n }\n }\n var isValid = rgx.test($.trim(value));\n\n if (isValid && (postalCode.length === 0 || postalCode.val().length === 0)) {\n return true;\n }\n\n return isValid;\n};\n\n/**\n * @function\n * @description Validates that a password meets requirments\n * @param {String} value The password to be validated\n */\nvar validatePassword = function (value) {\n var isValid = regex.password.test(value);\n return isValid;\n};\n\n/**\n * @function\n * @description Validates the given credit card number (Visa & MasterCard CC types are allowed.)\n * @param {String} value The credit card number field which will be validated\n * @param {String} el The input field\n */\nvar validateCreditCardNumber = function (value, el) {\n var cardType = $(el).closest('form').find('.cc-types');\n\n if (value != null && value.indexOf('*') >= 0) {return true;}\n\n switch (value.charAt(0)) {\n case '4' :\n cardType.val('Visa');\n break;\n case '2':\n cardType.val('MasterCard');\n break;\n case '5' :\n cardType.val('MasterCard');\n break;\n default:\n break;\n }\n\n var ccConfig,\n parent = $(el).parents('.field-wrapper');\n\n switch (cardType.val()) {\n case 'Visa':\n ccConfig = {lengths: '13,16', prefixes: '4', checkdigit: true}\n parent.attr('data-card-type', 'Visa');\n break;\n case 'MasterCard':\n ccConfig = {lengths: '16', prefixes: '51,52,53,54,55,22,23,24,25,26,27', checkdigit: true}\n parent.attr('data-card-type', 'MasterCard');\n break;\n default:\n break;\n }\n\n var cardNo = value.replace(/[\\s-]/g, '');\n\n if (cardNo.length == 0) { return false; }\n\n var cardexp = /^[0-9]{13,19}$/;\n\n if (!cardexp.exec(cardNo)) { return false; }\n cardNo = cardNo.replace(/\\D/g, '');\n\n if (ccConfig.checkdigit) {\n var checksum = 0,\n j = 1,\n calc;\n for (var i = cardNo.length - 1; i >= 0; i--) {\n calc = Number(cardNo.charAt(i)) * j;\n if (calc > 9) {\n checksum = checksum + 1;\n calc = calc - 10;\n }\n checksum = checksum + calc;\n if (j == 1) { j = 2 } else { j = 1 }\n }\n\n if (checksum % 10 != 0) { return false; } // not mod10\n }\n\n var lengthValid = false,\n prefixValid = false,\n prefix = new Array(),\n lengths = new Array();\n\n prefix = ccConfig.prefixes.split(',');\n for (var l = 0; l < prefix.length; l++) {\n var exp = new RegExp('^' + prefix[l]);\n if (exp.test(cardNo)) prefixValid = true;\n }\n\n if (!prefixValid) { return false; }\n\n lengths = ccConfig.lengths.split(',');\n for (var k = 0; k < lengths.length; k++) {\n if (cardNo.length == lengths[k]) lengthValid = true;\n }\n\n if (!lengthValid) { return false; }\n return true;\n};\n\n/**\n * @function\n * @description Validates a date to be used in a date picker\n * @param {String} value The date to be validated\n */\nvar validateDatePicker = function (value, el) {\n var isValid = regex.date.test(value);\n var isOptional = this.optional(el);\n return isOptional || isValid;\n};\n\n/**\n * @function\n * @description Validates email addresses\n * @param email\n * @returns {Boolean}\n */\nfunction validateEmail(email) {\n var re = /^(([^<>()\\[\\]\\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/;\n return re.test(String(email).toLowerCase());\n}\n\n/**\n * @function\n * @description Validates the given credit card expiration date\n */\nvar validateCreditCardExpiration = function () {\n var currentYear = (new Date()).getFullYear();\n var currentMonth = (new Date()).getMonth() + 1;\n var $expirationYear = $('select[name$=_expiration_year]');\n var $expirationMonth = $('select[name$=_expiration_month]');\n var ccExpirationMonth = parseInt($expirationMonth.val(), 10);\n var ccExpirationYear = parseInt($expirationYear.val(), 10);\n\n var isValid = (isNaN(ccExpirationYear) || isNaN(ccExpirationMonth)) ||\n (ccExpirationYear === currentYear && ccExpirationMonth >= currentMonth) ||\n ccExpirationYear > currentYear;\n\n if (isValid) {\n $expirationMonth.removeClass('error');\n $($expirationMonth, $expirationYear).parent().removeClass('expireError');\n $('.month-year').find('.form-caption.error-message, span.error').removeClass('.error-message').html('').hide();\n\n return true;\n } else {\n $expirationMonth.addClass('error');\n $($expirationMonth, $expirationYear).parent().addClass('expireError');\n $('.month').find('.form-caption').html(Resources.INVALID_EXPIRATION).addClass('error-message').show();\n }\n\n return false;\n\n};\n\n\n/**\n * Add credit card expiration month method to jQuery validation plugin.\n * Text fields must have 'expiration-year' css class to be validated\n */\n$.validator.addMethod('expiration-year', validateCreditCardExpiration, null);\n\n/**\n * Add credit card expiration month method to jQuery validation plugin.\n * Text fields must have 'expiration-month' css class to be validated\n */\n$.validator.addMethod('expiration-month', validateCreditCardExpiration, null);\n\n/**\n * Add phone validation method to jQuery validation plugin.\n * Text fields must have 'phone' css class to be validated as phone\n */\n$.validator.addMethod('phone', validatePhone, Resources.INVALID_PHONE);\n\n/**\n * Add CCOwner validation method to jQuery validation plugin.\n * Text fields must have 'owner' css class to be validated as not a credit card\n */\n$.validator.addMethod('owner', validateOwner, Resources.INVALID_OWNER);\n\n/**\n * Add password validation method to jQuery validation plugin.\n * Text fields must have 'password' css class to be validated as a password\n */\n$.validator.addMethod('newpassword', validatePassword, Resources.INVALID_PASSWORD);\n\n/**\n * Add gift cert amount validation method to jQuery validation plugin.\n * Text fields must have 'gift-cert-amont' css class to be validated\n */\n$.validator.addMethod('gift-cert-amount', function (value, el) {\n var isOptional = this.optional(el);\n var isValid = (!isNaN(value)) && (parseFloat(value) >= 5) && (parseFloat(value) <= 5000);\n return isOptional || isValid;\n}, Resources.GIFT_CERT_AMOUNT_INVALID);\n\n/**\n * Add positive number validation method to jQuery validation plugin.\n * Text fields must have 'positivenumber' css class to be validated as positivenumber\n */\n$.validator.addMethod('positivenumber', function (value) {\n if ($.trim(value).length === 0) { return true; }\n return (!isNaN(value) && Number(value) >= 0);\n}, ''); // '' should be replaced with error message if needed\n\n/**\n * Add postal validation method to jQuery validation plugin.\n * Text fields must have 'postal' css class to be validated as a postal code\n */\n$.validator.addMethod('postal', validatePostalCode, Resources.INVALID_POSTALCODE);\n\n/**\n * Add credit card number validation method to jQuery validation plugin.\n * Text fields must have 'ccard-number' css class to be validated\n */\n$.validator.addMethod('ccard-number', validateCreditCardNumber, Resources.VALIDATE_VISA_MS_CARD);\n\n/**\n * Add date validation method to jQuery validation plugin.\n * Text fields must have 'input-date' css class to be validated as a date\n */\n$.validator.addMethod('input-date', validateDatePicker, Resources.INVALID_DATE);\n\n/**\n * Add email field validation to jQuery validation plugin.\n * Text fields must have 'email' class to be validated as an email\n */\n$.validator.addMethod('email', validateEmail, Resources.VALIDATE_EMAIL);\n\n$.extend($.validator.messages, {\n required: Resources.VALIDATE_REQUIRED,\n remote: Resources.VALIDATE_REMOTE,\n email: Resources.VALIDATE_EMAIL,\n url: Resources.VALIDATE_URL,\n date: Resources.VALIDATE_DATE,\n dateISO: Resources.VALIDATE_DATEISO,\n number: Resources.VALIDATE_NUMBER,\n digits: Resources.VALIDATE_DIGITS,\n creditcard: Resources.VALIDATE_CREDITCARD,\n equalTo: Resources.VALIDATE_EQUALTO,\n maxlength: $.validator.format(Resources.VALIDATE_MAXLENGTH),\n minlength: $.validator.format(Resources.VALIDATE_MINLENGTH),\n rangelength: $.validator.format(Resources.VALIDATE_RANGELENGTH),\n range: $.validator.format(Resources.VALIDATE_RANGE),\n max: $.validator.format(Resources.VALIDATE_MAX),\n min: $.validator.format(Resources.VALIDATE_MIN)\n});\n\nvar validator = {\n regex: regex,\n settings: settings,\n init: function () {\n var self = this;\n $('form:not(.suppress)').each(function () {\n $(this).addClass('jqvalidate').validate(self.settings);\n });\n },\n initForm: function (f) {\n $(f).addClass('jqvalidate').validate(this.settings);\n }\n};\n\nmodule.exports = validator;\n","'use strict';\n\nvar processInclude = require('./util'),\n $ = require('jquery');\n\n$(document).ready(function () {\n processInclude(require('./popupCep/popupCep'));\n});\n","'use strict';\n\nconst ajax = require('grocery/ajax'),\n dialog = require('grocery/dialog'),\n //minicart = require('grocery/minicart'),\n productTile = require('grocery/product-tile'),\n progress = require('grocery/progress'),\n util = require('grocery/util'),\n validator = require('grocery/validator');\n\nvar delivery = {\n init: function () {\n $(document).on('app:openCepModal', () => {\n showPopCepModal()\n });\n initializeEvents();\n initializeDocument();\n initializeMiniCart();\n initializeCart();\n initializeCheckout();\n },\n refreshProductPrices: refreshProductPrices\n};\nvar orderEditExpirationCountdown;\n\n//Get Cookie\nfunction getCookie(name) {\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${name}=`);\n if (parts.length === 2) return parts.pop().split(';').shift();\n}\n\nfunction showPopCepModal() {\n $('#popUpCep #storesByCities').html('');\n $('#popUpCep #storesCities').val('');\n $('#popUpCep .popUpCep__deliveryTabContent').show();\n $('#popUpCep .popUpCep__response').hide();\n\n $.ajax({\n type: 'GET',\n url: $('#popUpCep').data('cities')\n }).done((data) => {\n if (!$('#popUpCep #storesCities').hasClass('loaded')) {\n for (let index = 0; index < data.cityList.length; index++) {\n const element = data.cityList[index];\n $('#popUpCep #storesCities').append(``);\n\n const orderedCities = $('#storesCities option').sort(function (elemA, elemB) {\n return elemA.value < elemB.value ? -1 : 1;\n });\n $('#popUpCep #storesCities').html(orderedCities);\n\n if(index == data.cityList.length - 1) {\n $('#popUpCep #storesCities').addClass('loaded');\n }\n }\n }\n\n $('#popUpCep').modal('show');\n });\n}\n\n/**\n * @function\n * @desc Initializes the delivery events\n */\nfunction initializeEvents() {\n if (!$('.minibanner-label').text()){\n $('.minibanner').hide();\n }\n\n var $container = $('.delivery-popover-container');\n\n // $('#homepage-postcode-input').on('change click mousedown keydown', function(){\n // $('#minicart-postcode-error').hide();\n // });\n\n $(document).off('change', '#popUpCep #storesCities').on('change', '#popUpCep #storesCities', (e) => {\n const value = $(e.currentTarget).val();\n $('#popUpCep .modal-content').spinner().start();\n\n if (value !== '') {\n $.ajax({\n type: 'GET',\n url: $(e.currentTarget).data('stores'),\n data: `city=${value}`,\n }).done((data) => {\n $('#popUpCep #storesByCities').html(data.template);\n }).always(() => {\n $.spinner().stop();\n });\n }\n });\n\n $(document).off('change', '#popUpCep [name=\"popUpCep__storeInput\"]').on('change', '#popUpCep [name=\"popUpCep__storeInput\"]', (e) => {\n const value = $(e.currentTarget).val();\n $('#popUpCep .modal-content').spinner().start();\n\n $.ajax({\n type: 'GET',\n url: $(e.currentTarget).data('url'),\n data: `postcode=${value}`,\n }).done((res) => {\n if(!res.error) {\n $.ajax({\n url: window.Urls.selectShippingMethods,\n type: 'POST',\n dataType: 'json',\n data: {methodID: '7024'},\n }).done(() => {\n window.location.reload();\n }).fail(() => {\n displayServerError();\n }).always(() => {\n $.spinner().stop();\n })\n } else {\n $.spinner().stop();\n }\n }).fail(() => {\n $.spinner().stop();\n });\n });\n\n /**\n * @listener\n * @desc Listens for the submit event on the postcode checker form - minicart\n */\n $('.check-postcode')\n .off('submit.delivery')\n .on('submit.delivery', function (e) {\n e.preventDefault();\n const $form = $(this);\n const jqXHR = checkPostcode($form);\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function (response) {\n if (typeof response === 'object' &&\n response.data.code === 'success') {\n refreshHomeDeliveryTemplate($container);\n refreshProductPrices(true);\n window.location.reload();\n } else if (response.data[0].code === 'error'){\n return;\n } else {\n displayServerError();\n }\n });\n }\n });\n\n\n /**\n * @listener\n * @desc Listens for the submit event on the postcode checker form POPUP-CEP\n */\n $('.check-postcode-popup')\n .off('submit.delivery')\n .on('submit.delivery', function (e) {\n e.preventDefault();\n const $form = $(this);\n const jqXHR = checkPostcode($form);\n $('.delivery-popover-container').spinner().start();\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function (response) {\n if (typeof response === 'object' &&\n response.data.code === 'success') {\n $('.popUpCep__deliveryTabContent').hide();\n $('.popUpCep__response').show();\n refreshHomeDeliveryTemplate($('.popUpCep__response'));\n\n refreshProductPrices(true);\n refreshMiniCartTemplate($('#mini-cart, .left-nav-minicart')\n .find('.mini-cart-delivery-states'));\n } else if (response.data[0].code === 'error') {\n return;\n } else {\n displayServerError();\n }\n });\n }\n });\n\n $(document).on('click', '#popUpCep .change-postal-code', function (ev) {\n ev.preventDefault();\n $('.popUpCep__deliveryTabContent').show();\n $('.popUpCep__response').hide();\n });\n\n /* Modify icon in a click tabs popupcep Modal. */\n $(function () {\n const tabPickup = $('#pickup-tab');\n const tabDelivery = $('#delivery-tab');\n const iconPicukup = $('.fa-shopping-basket');\n const iconDelivery = $('.fa-truck');\n $(document).off('click', '#popUpCep .nav-item').on('click', '#popUpCep .nav-item', function () {\n if (tabPickup.hasClass('active')) {\n iconPicukup.show();\n iconDelivery.hide();\n } else if (tabDelivery.hasClass('active')) {\n iconPicukup.hide();\n iconDelivery.show();\n }\n });\n // For start with pickup icon\n if (tabPickup.hasClass('active')) {\n tabPickup.trigger('click');\n }\n });\n\n /**\n * @listener\n * @desc Listens for the click event on the rebook link\n */\n $container.find('.rebook-slot')\n .on('submit', function (e) {\n e.preventDefault();\n const $form = $(this);\n\n if ($form.valid()) {\n const jqXHR = $.ajax({\n type: 'POST',\n url: $form.attr('action'),\n data: $form.serialize()\n });\n\n jqXHR.done(function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n refreshHomeDeliveryTemplate($container);\n } else {\n showDeliverySlotUnavailable($container,\n window.Urls.headerPostcodeValidation);\n }\n });\n\n jqXHR.fail(function () {\n displayServerError();\n });\n }\n });\n\n /**\n * @listener\n * @desc Listens for the click event on the delivery details section in the\n * header\n */\n $('body')\n .off('click.delivery')\n .on('click.delivery', '.header-delivery-details, .delivery-status-close-button, .continue-browsing-button, .continue-browsing-button', function (e) {\n e.stopPropagation();\n var urlParams;\n var is7023 = $(this).hasClass('delivery-7023');\n var is7024 = $(this).hasClass('delivery-7024');\n if (is7023){\n urlParams = {\n methodID: '7023'\n };\n }\n if (is7024){\n $('#pickup-tab').trigger('click');\n return;\n }\n $('.postcode-validation-form')\n //.spinner().start();\n if($('.page').data('action') === 'Search-Show') {\n $.ajax({\n url: window.Urls.selectShippingMethods,\n type: 'post',\n dataType: 'json',\n data: urlParams,\n success: function () {\n $('.postcode-validation-form')\n //.spinner().stop();\n setTimeout(function(){\n window.location.reload();\n },1000); \n $('.cookie-hint-dialog').show();\n },\n error: function () {\n $('.postcode-validation-form')\n //.spinner().stop();\n displayServerError();\n }\n });\n }\n if($('.page').data('action') !== 'Search-Show') {\n $.ajax({\n url: window.Urls.selectShippingMethods,\n type: 'post',\n dataType: 'json',\n data: urlParams,\n success: function () {\n $('.postcode-validation-form')\n //.spinner().stop();\n window.location.reload();\n $('.cookie-hint-dialog').show();\n },\n error: function () {\n $('.postcode-validation-form')\n //.spinner().stop();\n displayServerError();\n }\n });\n }\n if ($(e.target).parents('.delivery-details').length === 0 ||\n $(e.target).closest(\n '.delivery-status-close-button').length ||\n $(e.target).closest(\n '.continue-browsing-button').length) {\n let $dropdown = $('#popUpCep');\n\n if (!$dropdown.length) {\n $dropdown = $(this)\n .closest('.delivery-details');\n }\n\n if ($dropdown.children().length > 0) {\n if (!$(this).parents(\n '.delivery-popover-container').find(\n '.check-postcode').length) {\n if ($(this).closest('.product-tile').find(\n '.tile-add-to-cart').length) {\n productTile.addToCart($(this).closest(\n '.product-tile'));\n $(this).closest('.product-tile').find(\n '.tile-add-to-cart').off(\n 'click.delivery');\n } else if ($(this).closest(\n '.product-detail').find(\n '#add-to-cart').is(\n ':not(:disabled)')) {\n\n const $addToCartButton = $(this)\n .closest('.product-detail')\n .find('#add-to-cart');\n $addToCartButton\n .addClass('add-to-cart')\n .removeClass(\n 'details-delivery-add-to-cart');\n\n $addToCartButton.off('click.delivery');\n $addToCartButton.trigger('click');\n }\n }\n\n $('#popUpCep').slideUp();\n // $('#popUpCep').hide();\n\n // $('#popUpCep').empty();\n $('.modal-backdrop').hide();\n $('body').removeClass('modal-open');\n } else {\n // $('#popUpCep').each(function () {\n // $(this).empty();\n // });\n const jqXHR = refreshDropDownDeliveryTemplate($dropdown);\n jqXHR.done(function (response) {\n if (response !== null) {\n $('#popUpCep').slideDown();\n $('body').removeClass('modal-open');\n }\n });\n }\n }\n });\n\n /*\n * Listens for access website\n */\n if (!getCookie('dw_shippostalcode') || $('#minicart-postcode-input').length && !$('#popUpCep').hasClass('show')) {\n $(document).trigger('app:openCepModal');\n }\n\n // if($('.home-main.homepage').length || $('.page').data('action') === 'Cart-Show' || $('.page').data('action') === 'Product-Show' || $('.page').data('action') === 'Search-Show'){\n // if (!getCookie('dw_shippostalcode') || $('#minicart-postcode-input').length) {\n // setTimeout(function(){\n // $('#popUpCep').slideDown();\n // $('#popUpCep').modal('show');\n // $('body').css('pointer-events','none');\n // },1000);\n // }else {\n // $('body').css('pointer-events','auto');\n // }\n // }\n\n /**\n * @listener\n * @desc Listens for the click event on the add to cart button in the\n * product tile\n */\n $('body')\n .off('click.deliveryadd')\n .on('click.deliveryadd', '.delivery-add-to-cart', function (e) {\n e.preventDefault();\n const productTile = $(this).parents('.product-tile');\n const $dropdown = productTile.find('.delivery-details');\n\n // Check class in case of ajax update.\n if ($(this).hasClass('delivery-add-to-cart')) {\n showAddToCartDropDown($dropdown);\n $('#dropdown-postcode-input').mask('00000-000');\n }\n });\n\n /**\n * @listener\n * @desc Listens for the click event on modal overlay to close\n * delivery modal\n */\n $('body')\n .off('click.deliveryclose')\n .on('click.deliveryclose', '.modal-background', function (e) {\n e.preventDefault();\n\n // Close the visible delivery details modal\n $('.delivery-details:visible').slideUp();\n $('.delivery-details:visible').empty();\n $('body').removeClass('modal-open');\n refreshProductPrices(false);\n $('.modal-background').hide();\n });\n\n /**\n * @listener\n * @desc Listens for the click event on the add to cart button on the PDP\n */\n $('body')\n .off('click.deliverydetails')\n .on('click.deliverydetails', '.details-delivery-add-to-cart', function (e) {\n e.preventDefault();\n const $addToCartSection = $(this).parents('.product-qty-a2c');\n const $dropdown = $addToCartSection.find('.delivery-details');\n\n // Check class in case of ajax update.\n if ($(this).hasClass('details-delivery-add-to-cart')) {\n showAddToCartDropDown($dropdown);\n $('#dropdown-postcode-input').mask('00000-000');\n }\n });\n}\n\n/**\n * @function\n * @desc Initializes the drop down delivery section and it's events\n * @param $dropdown\n * @returns\n */\nfunction initializeDeliveryDropdown($dropdown) {\n validator.initForm($dropdown.find('form'));\n\n /**\n * @listener\n * @desc Listens for the submit event on the post code form to update the\n * post code\n */\n $('.check-postcode')\n .off('submit.delivery')\n .on('submit.delivery', function (e) {\n e.preventDefault();\n const $form = $(this);\n const jqXHR = checkPostcode($form);\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n // if (response.data.code === 'success' &&\n // $form\n // .closest('.product-tile').length) {\n // $(\n // '.product-tile .delivery-add-to-cart')\n // .addClass(\n // 'tile-add-to-cart')\n // .removeClass(\n // 'delivery-add-to-cart');\n // $('.details-delivery-add-to-cart').addClass('add-to-cart')\n // .removeClass('details-delivery-add-to-cart');\n // }\n\n refreshDropDownDeliveryTemplate($dropdown);\n\n // Refresh the homepage delivery\n // options if they are present\n if ($('#delivery-options-display').length) {\n refreshHomeDeliveryTemplate($('#delivery-options-display'));\n }\n } else {\n displayServerError();\n }\n });\n }\n });\n\n /**\n * @listener\n * @desc Listens for the click event on the book delivery button and adds\n * the product to the cart if conditions are met\n */\n $dropdown.find('.book-delivery-button')\n .off('click.delivery')\n .on('click.delivery', function (e) {\n e.preventDefault();\n const href = this.href;\n\n if ($(this).closest('.product-tile').length) {\n const jqXHR = productTile.addToCart($(this).closest(\n '.product-tile'), true);\n jqXHR.done(function () {\n window.location = href;\n });\n } else if ($(this).closest('.product-detail').find('#add-to-cart').is(':not(:disabled)')) {\n const $addToCartButton = $(this).closest('.product-detail')\n .find('#add-to-cart');\n $addToCartButton.addClass('add-to-cart').removeClass(\n 'details-delivery-add-to-cart');\n $addToCartButton.off('click.delivery');\n $(document).on('add-to-cart-pdp', function () {\n window.location = href;\n });\n $addToCartButton.trigger('click');\n } else {\n window.location = href;\n }\n });\n\n /**\n * @listener\n * @desc Listens for the click event on the post code change and delivery\n * back buttons to bring back the post code form\n */\n $dropdown.find('.change-postal-code-popup, .delivery-back')\n .off('click.delivery')\n .on('click.delivery', function (e) {\n e.preventDefault();\n var $link = $(this);\n $.ajax({\n type: 'POST',\n url: $link.attr('href'),\n success: function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n refreshDropDownDeliveryTemplate($dropdown);\n\n // window.User.zip = null;\n // $('.tile-add-to-cart').addClass(\n // 'delivery-add-to-cart')\n // .removeClass(\n // 'tile-add-to-cart');\n // $('.add-to-cart')\n // .addClass(\n // 'details-delivery-add-to-cart')\n // .removeClass('add-to-cart');\n\n // Refresh the homepage delivery\n // options if they are present\n if ($('#delivery-options-display').length) {\n refreshHomeDeliveryTemplate($('#delivery-options-display'));\n }\n } else {\n displayServerError();\n } \n window.location.reload();\n },\n error: function () {\n displayServerError();\n }\n });\n });\n\n /**\n * @listener\n * @desc Listens for the submit event on the slot rebooking form\n */\n $dropdown.find('.rebook-slot')\n .on('submit', function (e) {\n e.preventDefault();\n var $form = $(this);\n if ($form.valid()) {\n $.ajax({\n type: 'POST',\n url: $form.attr('action'),\n data: $form.serialize(),\n success: function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n refreshDropDownDeliveryTemplate($dropdown);\n } else {\n displayServerError();\n }\n },\n error: function () {\n displayServerError();\n }\n });\n }\n });\n\n /**\n * @listener\n * @desc Listens for the input on the postcode field\n */\n $dropdown.find('input[name=\"postcode\"]').on('change keyup', function () {\n $(this).val($(this).val().toUpperCase());\n });\n\n // Remove any the old dropdown expiration countdown\n if (typeof orderEditExpirationCountdown === 'number') {\n clearInterval(orderEditExpirationCountdown);\n }\n var $orderDeliveryExpirationMessage = $dropdown\n .find('.delivery-expiration-message');\n const expirationDateTime = $orderDeliveryExpirationMessage\n .attr('data-expiration-date');\n if (typeof expirationDateTime !== 'undefined' &&\n expirationDateTime !== 'null') {\n orderEditExpirationCountdown = setOrderEditExpirationCountdown($orderDeliveryExpirationMessage);\n }\n}\n\n/**\n * @function\n * @desc Sets up the mini cart delivery functionality\n * @returns\n */\nfunction initializeMiniCart() {\n const $miniCartContent = $('#mini-cart, .left-nav-minicart');\n\n /**\n * @listener\n * @desc Listens for the submit event on the post code form to update the\n * post code\n */\n $miniCartContent\n .off('submit.delivery')\n .on('submit.delivery', '.check-postcode', function (e) {\n e.preventDefault();\n const $form = $(this);\n const jqXHR = checkPostcode($form);\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n // if (response.data.code === 'success' &&\n // $form\n // .closest('.product-tile').length) {\n // $(\n // '.product-tile .delivery-add-to-cart')\n // .addClass(\n // 'tile-add-to-cart')\n // .removeClass(\n // 'delivery-add-to-cart');\n // }\n\n refreshMiniCartTemplate($miniCartContent\n .find('.mini-cart-delivery-states'));\n\n // Refresh the homepage delivery\n // options if they are present\n if ($('#delivery-options-display').length) {\n refreshHomeDeliveryTemplate($('#delivery-options-display'));\n }\n } else {\n displayServerError();\n }\n });\n }\n });\n\n /**\n * @listener\n * @desc Listens for the click event on the post code change and delivery\n * back buttons to bring back the post code form\n */\n $miniCartContent\n .off('click.delivery')\n .on('click.delivery', '.change-postal-code, .delivery-back', function (e) {\n e.preventDefault();\n var $link = $(this);\n $.ajax({\n type: 'POST',\n url: $link.attr('href'),\n success: function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n refreshHomeDeliveryTemplate($('.delivery-popover-container'));\n refreshMiniCartTemplate($miniCartContent\n .find('.mini-cart-delivery-states'));\n\n // window.User.zip = null;\n // $('.tile-add-to-cart').addClass(\n // 'delivery-add-to-cart')\n // .removeClass(\n // 'tile-add-to-cart');\n // $('.add-to-cart')\n // .addClass(\n // 'details-delivery-add-to-cart')\n // .removeClass('add-to-cart');\n\n // Refresh the homepage delivery\n // options if they are present\n if ($('#delivery-options-display').length) {\n refreshHomeDeliveryTemplate($('#delivery-options-display'));\n }\n } else {\n displayServerError();\n }\n window.location.reload();\n },\n error: function () {\n displayServerError();\n }\n });\n });\n $miniCartContent.on('submit', '.postcode-validation-form.delivery-minicart-container form', function () {\n // If you update the postcode in the minicart form, update the minicart correctly\n if ($('#minicart-postcode-input').val()){\n refreshProductPrices(true);\n }\n });\n\n /**\n * @listener\n * @desc Listens for the click event on the rebook button\n */\n $miniCartContent.on('submit','.rebook-slot', function (e) {\n e.preventDefault();\n const $form = $(this);\n\n if ($form.valid()) {\n const jqXHR = $.ajax({\n type: 'POST',\n url: $form.attr('action'),\n data: $form.serialize()\n });\n\n jqXHR.done(function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n refreshMiniCartTemplate($miniCartContent\n .find('.mini-cart-delivery-states'));\n\n // Refresh the homepage delivery\n // options if they are present\n if ($('#delivery-options-display').length) {\n refreshHomeDeliveryTemplate($('#delivery-options-display'));\n }\n } else {\n showDeliverySlotUnavailable(\n $miniCartContent\n .find('.mini-cart-delivery-states'),\n window.Urls.miniCartDeliveryStates);\n\n if ($('#delivery-options-display').length) {\n showDeliverySlotUnavailable(\n $('#delivery-options-display'),\n window.Urls.headerPostcodeValidation);\n }\n }\n });\n\n jqXHR.fail(function () {\n displayServerError();\n });\n }\n });\n}\n\n/**\n * @function\n * @desc Sets up the cart and review page delivery functionality\n * @returns\n */\nfunction initializeCart() {\n const $cartSection = $('.delivery-summary .card-body');\n\n /**\n * @listener\n * @desc Listens for the click event on the rebook button\n */\n $cartSection.find('.rebook-slot')\n .on('submit',function (e) {\n e.preventDefault();\n const $form = $(this);\n\n if ($form.valid()) {\n const jqXHR = $.ajax({\n type: 'POST',\n url: $form.attr('action'),\n data: $form.serialize()\n });\n\n jqXHR.done(function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n refreshCartTemplate($cartSection);\n } else {\n showDeliverySlotUnavailable($cartSection,\n window.Urls.cartDeliveryStates);\n\n if ($('#delivery-options-display').length) {\n showDeliverySlotUnavailable(\n $('#delivery-options-display'),\n window.Urls.headerPostcodeValidation);\n }\n }\n });\n\n jqXHR.fail(function () {\n displayServerError();\n });\n }\n });\n}\n\n/**\n * @function\n * @desc Sets up the cart and review page delivery functionality\n * @returns\n */\nfunction initializeCheckout() {\n const $checkoutSection = $('.checkout-order-totals-delivery');\n\n /**\n * @listener\n * @desc Listens for the click event on the rebook button\n */\n $checkoutSection.find('.rebook-slot')\n .on('submit', function (e) {\n e.preventDefault();\n const $form = $(this);\n\n if ($form.valid()) {\n const jqXHR = $.ajax({\n type: 'POST',\n url: $form.attr('action'),\n data: $form.serialize()\n });\n\n jqXHR.done(function (response) {\n if (typeof response === 'object' &&\n typeof response.data !== 'undefined') {\n refreshCheckoutTemplate($checkoutSection);\n } else {\n showDeliverySlotUnavailable($checkoutSection,\n window.Urls.checkoutDeliveryStates);\n }\n });\n\n jqXHR.fail(function () {\n displayServerError();\n });\n }\n });\n}\n\n/**\n * @function\n * @desc Initializes any features in the DOM\n * @returns\n */\nfunction initializeDocument() {\n initExpirationCountdown();\n initOrderEditExpirationCountdown();\n initMinicartOrderEditExpirationCountdown();\n initMobileMenuExpirationCountdown();\n}\n\n/**\n * @function\n * @desc Initializes the expiration countdown. Used to init the count down on\n * document load and when an ajax call completes\n * @returns\n */\nfunction initExpirationCountdown() {\n const expirationDateTime = $('.delivery-expiration-message').attr('data-expiration-date');\n if (typeof expirationDateTime !== 'undefined' &&\n expirationDateTime !== 'null') {\n setExpirationCountdown(expirationDateTime);\n }\n}\n\n/**\n * @function\n * @desc Initializes the expiration countdown on orders page and the home page.\n * Used to init the count down on document load and when an ajax call\n * completes\n * @returns\n */\nfunction initOrderEditExpirationCountdown() {\n var $orderDeliveryExpirationMessage = $('#order-delivery-expiration-message');\n if (setOrderEditExpirationCountdown($orderDeliveryExpirationMessage) === null) {\n refreshOrderEditCoundown();\n }\n}\n\n/**\n * @function\n * @desc Initializes the expiration countdown in the minicart. Used to init the\n * count down on document load and when an ajax call completes\n * @returns\n */\nfunction initMinicartOrderEditExpirationCountdown() {\n var $orderDeliveryExpirationMessage = $('#mini-cart .delivery-expiration-message');\n setOrderEditExpirationCountdown($orderDeliveryExpirationMessage);\n}\n\n/**\n * @function \n * @desc Initializes the expiration countdown in the mobile menu. Used to init\n * the count down on document load and when an ajax call completes\n */\nfunction initMobileMenuExpirationCountdown() {\n var $orderDeliveryExpirationMessage = $('.left-nav-minicart').find('.delivery-expiration-message');\n setOrderEditExpirationCountdown($orderDeliveryExpirationMessage);\n}\n\n/**\n * @function\n * @desc Sets up the delivery slot expiration countdown\n * @param {String} dateTime - Date string to use in the expiration timer\n * @param {Object} $orderDeliveryExpirationMessage - The jQuery element with the expiration message\n * @returns\n */\nfunction setOrderEditExpirationCountdown($orderDeliveryExpirationMessage) {\n const expirationDateTime = $orderDeliveryExpirationMessage.attr('data-expiration-date');\n if (typeof expirationDateTime === 'undefined' ||\n expirationDateTime === 'null') {\n return null;\n }\n\n const expirationDate = new Date(expirationDateTime).getTime();\n\n // Update the count down every 1 second\n const x = setInterval(function () {\n\n // Get todays date and time\n const now = new Date().getTime();\n\n // Find the distance between now an the count down date\n var distance = expirationDate - now;\n\n // If the count down is finished, write some text\n if (distance < 0) {\n clearInterval(x);\n refreshOrderEditCoundown();\n } else {\n // Time calculations for hours, minutes and seconds\n var hours, minutes, seconds;\n seconds = Math.floor(distance / 1000);\n minutes = Math.floor(seconds / 60);\n seconds = seconds % 60;\n hours = Math.floor(minutes / 60);\n minutes = minutes % 60;\n seconds = seconds < 10 ? '0' + String(seconds) : String(seconds);\n minutes = minutes < 10 ? '0' + String(minutes) : String(minutes);\n\n // Display the result in the expiration message section\n $orderDeliveryExpirationMessage.find('.hours').text(hours);\n $orderDeliveryExpirationMessage.find('.minutes').text(minutes);\n $orderDeliveryExpirationMessage.find('.seconds').text(seconds);\n }\n }, 1000);\n return x;\n}\n\n/**\n * @function\n * @desc Sets up the delivery slot expiration countdown\n * @param {String}\n * dateTime - Date string to use in the expiration timer\n * @returns\n */\nfunction setExpirationCountdown(dateTime) {\n const expirationDate = new Date(dateTime).getTime();\n\n // Update the count down every 1 second\n const x = setInterval(function () {\n\n // Get todays date and time\n const now = new Date().getTime();\n\n // Find the distance between now an the count down date\n var distance = expirationDate - now;\n\n // If the count down is finished, write some text\n if (distance < 0) {\n clearInterval(x);\n var $container = $('#delivery-options-display');\n if ($container.length) {\n refreshHomeDeliveryTemplate($container);\n }\n } else {\n // Time calculations for minutes and seconds\n var minutes = Math.floor((distance % (1000 * 60 * 60)) /\n (1000 * 60));\n var seconds = Math.floor((distance % (1000 * 60)) / 1000);\n\n // Display the result in the expiration message section\n $('.delivery-expiration-message .minutes').text(minutes);\n $('.delivery-expiration-message .seconds').text(seconds);\n }\n }, 1000);\n}\n\nfunction checkPostcode($form) {\n if (typeof $form !== 'undefined' && $form.valid()) {\n const formData = $form.serialize();\n\n const jqXHR = $.ajax({\n type: 'POST',\n url: $form.attr('action'),\n data: formData\n });\n\n jqXHR.done(function (response) {\n if (response.data && response.data.code === 'success') {\n $('.delivery-add-to-cart').addClass('tile-add-to-cart')\n .removeClass('delivery-add-to-cart');\n $('.details-delivery-add-to-cart').addClass('add-to-cart')\n .removeClass('details-delivery-add-to-cart');\n } else if (response.data[0].code === 'error'){\n $('.invalid-cep-error').removeClass('d-none');\n return;\n }\n });\n\n jqXHR.fail(function () {\n displayServerError();\n });\n\n return jqXHR;\n }\n}\n\n/**\n * @function\n * @desc Refreshes the home page delivery template\n * @param {jQuery}\n * $container - jQuery object of the home page delivery section\n * @returns\n */\nfunction refreshHomeDeliveryTemplate($container) {\n $($container).spinner().start();\n $.ajax({\n url: window.Urls.dropdownPostcodeValidation,\n method: 'GET',\n success: function (data) {\n if(data.success) {\n $('#popUpCep').modal('hide');\n location.reload();\n $.spinner().stop();\n } else {\n $container.html(data);\n delivery.init();\n $.spinner().stop();\n }\n },\n });\n}\n\nfunction refreshOrderEditCoundown() {\n if ($('.edit-not-expired').length > 0) {\n $('.edit-not-expired').hide();\n }\n\n $('.edit-expired').show();\n}\n\n/**\n * @function\n * @desc Refreshes the home page delivery template with an unavailable message\n * @param {jQuery}\n * $container - jQuery object of the home page delivery section\n * @returns\n */\nfunction showDeliverySlotUnavailable($container, resourceLocation) {\n var url = util.appendParamToURL(resourceLocation, Resources.SLOT_NOT_AVAILABLE_STATUS, 'true');\n const jqXHR = ajax.load({\n url: url,\n target: $container\n });\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function () {\n delivery.init();\n });\n }\n\n return jqXHR;\n}\n\n/**\n * @function\n * @desc Refreshes the drop down delivery template\n * @param {jQuery}\n * $dropdown - jQuery object of the drop down to refresh\n * @returns\n */\nfunction refreshDropDownDeliveryTemplate($dropdown) {\n const jqXHR = ajax.load({\n url: Urls.dropdownPostcodeValidation,\n target: $dropdown\n });\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function () {\n initializeDeliveryDropdown($dropdown);\n initExpirationCountdown();\n });\n }\n\n return jqXHR;\n}\n\n/**\n * @function\n * @desc Refreshes the mini cart delivery template\n * @param {jQuery}\n * $miniCartDelivery - jQuery object of the mini cart section to\n * refresh\n * @returns\n */\nfunction refreshMiniCartTemplate($miniCartDelivery) {\n const jqXHR = ajax.load({\n url: Urls.miniCartDeliveryStates,\n target: $miniCartDelivery\n });\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function () {\n initializeMiniCart();\n initExpirationCountdown();\n // $('body').on('click', '#minicart-postcode-input', this.maskApplier);\n $('#minicart-postcode-input').mask('00000-000');\n });\n }\n\n return jqXHR;\n}\n\n/**\n * @function\n * @desc Refreshes the cart delivery template\n * @param {jQuery}\n * $contentSection - jQuery object of the delivery section to refresh\n * @returns\n */\nfunction refreshCartTemplate($deliverySection) {\n const jqXHR = ajax.load({\n url: Urls.cartDeliveryStates,\n target: $deliverySection\n });\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function () {\n initializeCart();\n });\n }\n\n return jqXHR;\n}\n\n/**\n * @function\n * @desc Refreshes the checkout delivery template\n * @param {jQuery}\n * $contentSection - jQuery object of the delivery section to refresh\n * @returns\n */\nfunction refreshCheckoutTemplate($deliverySection) {\n const jqXHR = ajax.load({\n url: Urls.checkoutDeliveryStates,\n target: $deliverySection\n });\n\n if (typeof jqXHR !== 'undefined') {\n jqXHR.done(function () {\n initializeCheckout();\n });\n }\n\n return jqXHR;\n}\n\n/**\n * @function\n * @desc Refreshes the page if it is one that has product prices displayed\n * @returns\n */\nfunction refreshProductPrices(showProgress) {\n var refreshPage = false;\n var elementsToRefresh = [];\n //TODO: will not work until minicart customizations are added\n // Check for product tiles\n if ($('.grid-tile').length > 0) {\n refreshPage = true;\n elementsToRefresh.push('.grid-tile')\n // Check for a PDP form\n }\n if ($('.pdpForm').length > 0) {\n refreshPage = true;\n elementsToRefresh.push('.pdpForm')\n }\n if ($('#pdpMain').length > 0) {\n refreshPage = true;\n elementsToRefresh.push('#pdpMain')\n }\n // Check for the cart page\n if ($('.cart-action-checkout').length > 0) {\n refreshPage = true;\n elementsToRefresh.push('.cart-action-checkout')\n // Check for the book a slot page\n }\n if ($('.delivery-info').length > 0 && $('.delivery-confirmation-info').length === 0) {\n refreshPage = true;\n elementsToRefresh.push('.delivery-info')\n elementsToRefresh.push('.delivery-confirmation-info')\n }\n // Recalculate the cart and reload the minicart\n if (showProgress) {\n progress.show();\n $.ajax({\n url: Urls.miniCart,\n type: 'GET',\n dataType: 'html',\n success: function() {\n //minicart.show(data, !$('.mini-cart-empty').length);\n console.log('product success refresh')\n }\n })\n }\n if (refreshPage) {\n const url = new URL(window.location.href);\n // Although it is possible to defer\n // the nature of the request, an param differs the route cache.\n url.searchParams.append('ajax', true);\n\n $.ajax({\n url: url.toString(),\n type: 'GET',\n dataType: 'html',\n success: function(data) {\n var $response = $(data);\n for (var x = 0; x < elementsToRefresh.length; x++) {\n // if it's grid-tiles you'll need to loop over them\n if (elementsToRefresh[x] === '.grid-tile') {\n var responseTiles = $response.find('.grid-tile');\n var oldTiles = $('.grid-tile')\n if (responseTiles.length === oldTiles.length) {\n for (var j = 0; j < oldTiles.length; j++) {\n $(oldTiles[j]).html(responseTiles[j].innerHTML);\n }\n }\n } else {\n // Just replace the single element\n var newHTML = $response.find(elementsToRefresh[x]).html();\n $(elementsToRefresh[x]).html(newHTML);\n }\n\n $('body').trigger('apply:slick');\n }\n\n $.spinner().stop();\n },\n error: function (err) {\n console.log('error:', err);\n $.spinner().stop();\n }\n })\n }\n}\n\n/**\n * @function\n * @desc Retrieves the delivery drop down and displays it on the page\n * @param $dropdown\n * @returns\n */\nfunction showAddToCartDropDown($dropdown) {\n if ($dropdown.children().length > 0) {\n $dropdown.empty();\n } else {\n $('.delivery-details').each(function () {\n $(this).empty();\n });\n\n const jqXHR = ajax.load({\n url: Urls.dropdownPostcodeValidation,\n target: $dropdown\n });\n\n jqXHR.done(function (response) {\n if (response) {\n initializeDeliveryDropdown($dropdown);\n\n if (!$('.promo-modal').is(':visible') &&\n !$dropdown.parents('.slick-slider').length) {\n // $('.modal-background').show();\n }\n\n $dropdown.show();\n\n $(window).on('resize.delivery', function () { // PDP delivery Modal\n if ($dropdown.parents('.product-actions').length) {\n if($dropdown.parents('.product-actions').find('.details-delivery-add-to-cart').length) {\n if (util.mediaBreakpointUp('md')) {\n $dropdown.position({\n my: 'right bottom',\n at: 'center+30px top-12px',\n of: $('.details-delivery-add-to-cart')\n });\n } else {\n $dropdown.position({\n my: 'right bottom',\n at: 'right top-12px',\n of: $('.details-delivery-add-to-cart')\n });\n }\n }\n } else if ($dropdown.parents('.tile-actions').length) {\n if($dropdown.parents('.tile-actions').find('.delivery-add-to-cart').length) {\n if (util.mediaBreakpointUp('sm')) {\n if($dropdown.hasClass('flipped')) {\n $dropdown.removeClass('flipped');\n }\n\n $dropdown.position({\n my: 'left bottom',\n at: 'center top-12px',\n of: $dropdown.parents('.tile-actions').find('.delivery-add-to-cart'),\n using: function (position, data) {\n if (data.horizontal === 'right') {\n $(this).addClass('flipped');\n } else {\n $(this).removeClass('flipped');\n }\n\n if (data.vertical === 'top') {\n $(this).addClass('flipped-bottom');\n } else {\n $(this).removeClass('flipped-bottom');\n }\n\n $(this).css(position);\n }\n });\n } else {\n $dropdown.addClass('flipped');\n $dropdown.position({\n my: 'right+5px bottom',\n at: 'right; top-12px',\n of: $dropdown.parents('.tile-actions').find('.delivery-add-to-cart'),\n using: function (position, data) {\n if (data.vertical === 'top') {\n $(this).addClass('flipped-bottom');\n } else {\n $(this).removeClass('flipped-bottom');\n }\n\n $(this).css(position);\n }\n });\n }\n }\n }\n });\n\n $(window).trigger('resize', [{deliveryFile: true}]);\n }\n });\n }\n}\n\n/**\n * @function\n * @desc Shows a server error in a dialog box\n * @returns\n */\nfunction displayServerError() {\n const $dialogTarget = $('#dialog-container');\n\n dialog.open({\n content: '
' + Resources.SERVER_ERROR + '
',\n target: $dialogTarget,\n options: {\n title: Resources.ERROR\n }\n });\n}\n\nmodule.exports = delivery;\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","/**\n * EvEmitter v1.1.0\n * Lil' event emitter\n * MIT License\n */\n\n/* jshint unused: true, undef: true, strict: true */\n\n( function( global, factory ) {\n // universal module definition\n /* jshint strict: false */ /* globals define, module, window */\n if ( typeof define == 'function' && define.amd ) {\n // AMD - RequireJS\n define( factory );\n } else if ( typeof module == 'object' && module.exports ) {\n // CommonJS - Browserify, Webpack\n module.exports = factory();\n } else {\n // Browser globals\n global.EvEmitter = factory();\n }\n\n}( typeof window != 'undefined' ? window : this, function() {\n\n\"use strict\";\n\nfunction EvEmitter() {}\n\nvar proto = EvEmitter.prototype;\n\nproto.on = function( eventName, listener ) {\n if ( !eventName || !listener ) {\n return;\n }\n // set events hash\n var events = this._events = this._events || {};\n // set listeners array\n var listeners = events[ eventName ] = events[ eventName ] || [];\n // only add once\n if ( listeners.indexOf( listener ) == -1 ) {\n listeners.push( listener );\n }\n\n return this;\n};\n\nproto.once = function( eventName, listener ) {\n if ( !eventName || !listener ) {\n return;\n }\n // add event\n this.on( eventName, listener );\n // set once flag\n // set onceEvents hash\n var onceEvents = this._onceEvents = this._onceEvents || {};\n // set onceListeners object\n var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};\n // set flag\n onceListeners[ listener ] = true;\n\n return this;\n};\n\nproto.off = function( eventName, listener ) {\n var listeners = this._events && this._events[ eventName ];\n if ( !listeners || !listeners.length ) {\n return;\n }\n var index = listeners.indexOf( listener );\n if ( index != -1 ) {\n listeners.splice( index, 1 );\n }\n\n return this;\n};\n\nproto.emitEvent = function( eventName, args ) {\n var listeners = this._events && this._events[ eventName ];\n if ( !listeners || !listeners.length ) {\n return;\n }\n // copy over to avoid interference if .off() in listener\n listeners = listeners.slice(0);\n args = args || [];\n // once stuff\n var onceListeners = this._onceEvents && this._onceEvents[ eventName ];\n\n for ( var i=0; i < listeners.length; i++ ) {\n var listener = listeners[i]\n var isOnce = onceListeners && onceListeners[ listener ];\n if ( isOnce ) {\n // remove listener\n // remove before trigger to prevent recursion\n this.off( eventName, listener );\n // unset once flag\n delete onceListeners[ listener ];\n }\n // trigger listener\n listener.apply( this, args );\n }\n\n return this;\n};\n\nproto.allOff = function() {\n delete this._events;\n delete this._onceEvents;\n};\n\nreturn EvEmitter;\n\n}));\n","/*!\n * imagesLoaded v4.1.4\n * JavaScript is all like \"You images are done yet or what?\"\n * MIT License\n */\n\n( function( window, factory ) { 'use strict';\n // universal module definition\n\n /*global define: false, module: false, require: false */\n\n if ( typeof define == 'function' && define.amd ) {\n // AMD\n define( [\n 'ev-emitter/ev-emitter'\n ], function( EvEmitter ) {\n return factory( window, EvEmitter );\n });\n } else if ( typeof module == 'object' && module.exports ) {\n // CommonJS\n module.exports = factory(\n window,\n require('ev-emitter')\n );\n } else {\n // browser global\n window.imagesLoaded = factory(\n window,\n window.EvEmitter\n );\n }\n\n})( typeof window !== 'undefined' ? window : this,\n\n// -------------------------- factory -------------------------- //\n\nfunction factory( window, EvEmitter ) {\n\n'use strict';\n\nvar $ = window.jQuery;\nvar console = window.console;\n\n// -------------------------- helpers -------------------------- //\n\n// extend objects\nfunction extend( a, b ) {\n for ( var prop in b ) {\n a[ prop ] = b[ prop ];\n }\n return a;\n}\n\nvar arraySlice = Array.prototype.slice;\n\n// turn element or nodeList into an array\nfunction makeArray( obj ) {\n if ( Array.isArray( obj ) ) {\n // use object if already an array\n return obj;\n }\n\n var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';\n if ( isArrayLike ) {\n // convert nodeList to array\n return arraySlice.call( obj );\n }\n\n // array of single index\n return [ obj ];\n}\n\n// -------------------------- imagesLoaded -------------------------- //\n\n/**\n * @param {Array, Element, NodeList, String} elem\n * @param {Object or Function} options - if function, use as callback\n * @param {Function} onAlways - callback function\n */\nfunction ImagesLoaded( elem, options, onAlways ) {\n // coerce ImagesLoaded() without new, to be new ImagesLoaded()\n if ( !( this instanceof ImagesLoaded ) ) {\n return new ImagesLoaded( elem, options, onAlways );\n }\n // use elem as selector string\n var queryElem = elem;\n if ( typeof elem == 'string' ) {\n queryElem = document.querySelectorAll( elem );\n }\n // bail if bad element\n if ( !queryElem ) {\n console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) );\n return;\n }\n\n this.elements = makeArray( queryElem );\n this.options = extend( {}, this.options );\n // shift arguments if no options set\n if ( typeof options == 'function' ) {\n onAlways = options;\n } else {\n extend( this.options, options );\n }\n\n if ( onAlways ) {\n this.on( 'always', onAlways );\n }\n\n this.getImages();\n\n if ( $ ) {\n // add jQuery Deferred object\n this.jqDeferred = new $.Deferred();\n }\n\n // HACK check async to allow time to bind listeners\n setTimeout( this.check.bind( this ) );\n}\n\nImagesLoaded.prototype = Object.create( EvEmitter.prototype );\n\nImagesLoaded.prototype.options = {};\n\nImagesLoaded.prototype.getImages = function() {\n this.images = [];\n\n // filter & find items if we have an item selector\n this.elements.forEach( this.addElementImages, this );\n};\n\n/**\n * @param {Node} element\n */\nImagesLoaded.prototype.addElementImages = function( elem ) {\n // filter siblings\n if ( elem.nodeName == 'IMG' ) {\n this.addImage( elem );\n }\n // get background image on element\n if ( this.options.background === true ) {\n this.addElementBackgroundImages( elem );\n }\n\n // find children\n // no non-element nodes, #143\n var nodeType = elem.nodeType;\n if ( !nodeType || !elementNodeTypes[ nodeType ] ) {\n return;\n }\n var childImgs = elem.querySelectorAll('img');\n // concat childElems to filterFound array\n for ( var i=0; i < childImgs.length; i++ ) {\n var img = childImgs[i];\n this.addImage( img );\n }\n\n // get child background images\n if ( typeof this.options.background == 'string' ) {\n var children = elem.querySelectorAll( this.options.background );\n for ( i=0; i < children.length; i++ ) {\n var child = children[i];\n this.addElementBackgroundImages( child );\n }\n }\n};\n\nvar elementNodeTypes = {\n 1: true,\n 9: true,\n 11: true\n};\n\nImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {\n var style = getComputedStyle( elem );\n if ( !style ) {\n // Firefox returns null if in a hidden iframe https://bugzil.la/548397\n return;\n }\n // get url inside url(\"...\")\n var reURL = /url\\((['\"])?(.*?)\\1\\)/gi;\n var matches = reURL.exec( style.backgroundImage );\n while ( matches !== null ) {\n var url = matches && matches[2];\n if ( url ) {\n this.addBackground( url, elem );\n }\n matches = reURL.exec( style.backgroundImage );\n }\n};\n\n/**\n * @param {Image} img\n */\nImagesLoaded.prototype.addImage = function( img ) {\n var loadingImage = new LoadingImage( img );\n this.images.push( loadingImage );\n};\n\nImagesLoaded.prototype.addBackground = function( url, elem ) {\n var background = new Background( url, elem );\n this.images.push( background );\n};\n\nImagesLoaded.prototype.check = function() {\n var _this = this;\n this.progressedCount = 0;\n this.hasAnyBroken = false;\n // complete if no images\n if ( !this.images.length ) {\n this.complete();\n return;\n }\n\n function onProgress( image, elem, message ) {\n // HACK - Chrome triggers event before object properties have changed. #83\n setTimeout( function() {\n _this.progress( image, elem, message );\n });\n }\n\n this.images.forEach( function( loadingImage ) {\n loadingImage.once( 'progress', onProgress );\n loadingImage.check();\n });\n};\n\nImagesLoaded.prototype.progress = function( image, elem, message ) {\n this.progressedCount++;\n this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;\n // progress event\n this.emitEvent( 'progress', [ this, image, elem ] );\n if ( this.jqDeferred && this.jqDeferred.notify ) {\n this.jqDeferred.notify( this, image );\n }\n // check if completed\n if ( this.progressedCount == this.images.length ) {\n this.complete();\n }\n\n if ( this.options.debug && console ) {\n console.log( 'progress: ' + message, image, elem );\n }\n};\n\nImagesLoaded.prototype.complete = function() {\n var eventName = this.hasAnyBroken ? 'fail' : 'done';\n this.isComplete = true;\n this.emitEvent( eventName, [ this ] );\n this.emitEvent( 'always', [ this ] );\n if ( this.jqDeferred ) {\n var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';\n this.jqDeferred[ jqMethod ]( this );\n }\n};\n\n// -------------------------- -------------------------- //\n\nfunction LoadingImage( img ) {\n this.img = img;\n}\n\nLoadingImage.prototype = Object.create( EvEmitter.prototype );\n\nLoadingImage.prototype.check = function() {\n // If complete is true and browser supports natural sizes,\n // try to check for image status manually.\n var isComplete = this.getIsImageComplete();\n if ( isComplete ) {\n // report based on naturalWidth\n this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );\n return;\n }\n\n // If none of the checks above matched, simulate loading on detached element.\n this.proxyImage = new Image();\n this.proxyImage.addEventListener( 'load', this );\n this.proxyImage.addEventListener( 'error', this );\n // bind to image as well for Firefox. #191\n this.img.addEventListener( 'load', this );\n this.img.addEventListener( 'error', this );\n this.proxyImage.src = this.img.src;\n};\n\nLoadingImage.prototype.getIsImageComplete = function() {\n // check for non-zero, non-undefined naturalWidth\n // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671\n return this.img.complete && this.img.naturalWidth;\n};\n\nLoadingImage.prototype.confirm = function( isLoaded, message ) {\n this.isLoaded = isLoaded;\n this.emitEvent( 'progress', [ this, this.img, message ] );\n};\n\n// ----- events ----- //\n\n// trigger specified handler for event type\nLoadingImage.prototype.handleEvent = function( event ) {\n var method = 'on' + event.type;\n if ( this[ method ] ) {\n this[ method ]( event );\n }\n};\n\nLoadingImage.prototype.onload = function() {\n this.confirm( true, 'onload' );\n this.unbindEvents();\n};\n\nLoadingImage.prototype.onerror = function() {\n this.confirm( false, 'onerror' );\n this.unbindEvents();\n};\n\nLoadingImage.prototype.unbindEvents = function() {\n this.proxyImage.removeEventListener( 'load', this );\n this.proxyImage.removeEventListener( 'error', this );\n this.img.removeEventListener( 'load', this );\n this.img.removeEventListener( 'error', this );\n};\n\n// -------------------------- Background -------------------------- //\n\nfunction Background( url, element ) {\n this.url = url;\n this.element = element;\n this.img = new Image();\n}\n\n// inherit LoadingImage prototype\nBackground.prototype = Object.create( LoadingImage.prototype );\n\nBackground.prototype.check = function() {\n this.img.addEventListener( 'load', this );\n this.img.addEventListener( 'error', this );\n this.img.src = this.url;\n // check if image is already complete\n var isComplete = this.getIsImageComplete();\n if ( isComplete ) {\n this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );\n this.unbindEvents();\n }\n};\n\nBackground.prototype.unbindEvents = function() {\n this.img.removeEventListener( 'load', this );\n this.img.removeEventListener( 'error', this );\n};\n\nBackground.prototype.confirm = function( isLoaded, message ) {\n this.isLoaded = isLoaded;\n this.emitEvent( 'progress', [ this, this.element, message ] );\n};\n\n// -------------------------- jQuery -------------------------- //\n\nImagesLoaded.makeJQueryPlugin = function( jQuery ) {\n jQuery = jQuery || window.jQuery;\n if ( !jQuery ) {\n return;\n }\n // set local variable\n $ = jQuery;\n // $().imagesLoaded()\n $.fn.imagesLoaded = function( options, callback ) {\n var instance = new ImagesLoaded( this, options, callback );\n return instance.jqDeferred.promise( $(this) );\n };\n};\n// try making plugin\nImagesLoaded.makeJQueryPlugin();\n\n// -------------------------- -------------------------- //\n\nreturn ImagesLoaded;\n\n});\n","/*!\n * jQuery JavaScript Library v3.6.1\n * https://jquery.com/\n *\n * Includes Sizzle.js\n * https://sizzlejs.com/\n *\n * Copyright OpenJS Foundation and other contributors\n * Released under the MIT license\n * https://jquery.org/license\n *\n * Date: 2022-08-26T17:52Z\n */\n( function( global, factory ) {\n\n\t\"use strict\";\n\n\tif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\n\t\t// For CommonJS and CommonJS-like environments where a proper `window`\n\t\t// is present, execute the factory and get jQuery.\n\t\t// For environments that do not have a `window` with a `document`\n\t\t// (such as Node.js), expose a factory as module.exports.\n\t\t// This accentuates the need for the creation of a real `window`.\n\t\t// e.g. var jQuery = require(\"jquery\")(window);\n\t\t// See ticket trac-14549 for more info.\n\t\tmodule.exports = global.document ?\n\t\t\tfactory( global, true ) :\n\t\t\tfunction( w ) {\n\t\t\t\tif ( !w.document ) {\n\t\t\t\t\tthrow new Error( \"jQuery requires a window with a document\" );\n\t\t\t\t}\n\t\t\t\treturn factory( w );\n\t\t\t};\n\t} else {\n\t\tfactory( global );\n\t}\n\n// Pass this if window is not defined yet\n} )( typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n\n// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1\n// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode\n// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common\n// enough that all such attempts are guarded in a try block.\n\"use strict\";\n\nvar arr = [];\n\nvar getProto = Object.getPrototypeOf;\n\nvar slice = arr.slice;\n\nvar flat = arr.flat ? function( array ) {\n\treturn arr.flat.call( array );\n} : function( array ) {\n\treturn arr.concat.apply( [], array );\n};\n\n\nvar push = arr.push;\n\nvar indexOf = arr.indexOf;\n\nvar class2type = {};\n\nvar toString = class2type.toString;\n\nvar hasOwn = class2type.hasOwnProperty;\n\nvar fnToString = hasOwn.toString;\n\nvar ObjectFunctionString = fnToString.call( Object );\n\nvar support = {};\n\nvar isFunction = function isFunction( obj ) {\n\n\t\t// Support: Chrome <=57, Firefox <=52\n\t\t// In some browsers, typeof returns \"function\" for HTML