Adds SFRA 6.0

This commit is contained in:
Isaac Vallee
2021-12-21 10:57:31 -08:00
parent d04eb5dd16
commit 823c7608c3
1257 changed files with 137087 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>app_storefront_base</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.demandware.studio.core.beehiveElementBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.demandware.studio.core.beehiveNature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,14 @@
{
"ecmaVersion": 5,
"plugins": {
"guess-types": {
},
"outline": {
},
"demandware": {
}
}
}

View File

@@ -0,0 +1,5 @@
# Welcome to Storefront Reference Architecture (SFRA)
The Storefront Reference Architecture is fully compliant with standard JavaScript. It uses [Controllers]{@tutorial Controllers} to handle incoming requests. It provides a layer of JSON objects through the [Model-Views]{@tutorial Models}. All scripts are [Common JS modules](http://www.commonjs.org) with defined and documented exports to avoid polluting the global namespace.
This documentation is meant to serve as a reference to quickly look up supported functionality and is fully based on the comments in the code. You can continue to maintain these [JSDoc comments](http://usejsdoc.org/) to generate a similar documentation for your own project.

View File

@@ -0,0 +1,4 @@
## cartridge.properties for cartridge app_storefront_base
#Thu Jun 09 11:30:40 EDT 2016
demandware.cartridges.app_storefront_base.multipleLanguageStorefront=true
demandware.cartridges.app_storefront_base.id=app_storefront_base

View File

@@ -0,0 +1,10 @@
{
"env": {
"jquery": true
},
"rules": {
"global-require": "off",
"no-var": "off",
"prefer-const": "off"
}
}

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./addressBook/addressBook'));
});

View File

@@ -0,0 +1,105 @@
'use strict';
var formValidation = require('../components/formValidation');
var url;
var isDefault;
/**
* Create an alert to display the error message
* @param {Object} message - Error message to display
*/
function createErrorNotification(message) {
var errorHtml = '<div class="alert alert-danger alert-dismissible valid-cart-error ' +
'fade show" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' + message + '</div>';
$('.error-messaging').append(errorHtml);
}
module.exports = {
removeAddress: function () {
$('.remove-address').on('click', function (e) {
e.preventDefault();
isDefault = $(this).data('default');
if (isDefault) {
url = $(this).data('url')
+ '?addressId='
+ $(this).data('id')
+ '&isDefault='
+ isDefault;
} else {
url = $(this).data('url') + '?addressId=' + $(this).data('id');
}
$('.product-to-remove').empty().append($(this).data('id'));
});
},
removeAddressConfirmation: function () {
$('.delete-confirmation-btn').click(function (e) {
e.preventDefault();
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function (data) {
$('#uuid-' + data.UUID).remove();
if (isDefault) {
var addressId = $('.card .address-heading').first().text();
var addressHeading = addressId + ' (' + data.defaultMsg + ')';
$('.card .address-heading').first().text(addressHeading);
$('.card .card-make-default-link').first().remove();
$('.remove-address').data('default', true);
if (data.message) {
var toInsert = '<div><h3>' +
data.message +
'</h3><div>';
$('.addressList').after(toInsert);
}
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
} else {
createErrorNotification(err.responseJSON.errorMessage);
}
$.spinner().stop();
}
});
});
},
submitAddress: function () {
$('form.address-form').submit(function (e) {
var $form = $(this);
e.preventDefault();
url = $form.attr('action');
$form.spinner().start();
$('form.address-form').trigger('address:submit', e);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: $form.serialize(),
success: function (data) {
$form.spinner().stop();
if (!data.success) {
formValidation($form, data);
} else {
location.href = data.redirectUrl;
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
$form.spinner().stop();
}
});
return false;
});
}
};

View File

@@ -0,0 +1,17 @@
'use strict';
$(document).ready(function () {
if (window.resetCampaignBannerSessionToken) {
window.sessionStorage.removeItem('hide_campaign_banner');
}
var campaignBannerStatus = window.sessionStorage.getItem('hide_campaign_banner');
$('.campaign-banner .close').on('click', function () {
$('.campaign-banner').addClass('d-none');
window.sessionStorage.setItem('hide_campaign_banner', '1');
});
if (!campaignBannerStatus || campaignBannerStatus < 0) {
$('.campaign-banner').removeClass('d-none');
}
});

View File

@@ -0,0 +1,174 @@
'use strict';
var debounce = require('lodash/debounce');
/**
* Get display information related to screen size
* @param {jQuery} element - the current carousel that is being used
* @returns {Object} an object with display information
*/
function screenSize(element) {
var result = {
itemsToDisplay: null,
sufficientSlides: true
};
var viewSize = $(window).width();
var extraSmallDisplay = element.data('xs');
var smallDisplay = element.data('sm');
var mediumDisplay = element.data('md');
var numberOfSlides = element.data('number-of-slides');
if (viewSize <= 575.98) {
result.itemsToDisplay = extraSmallDisplay;
} else if ((viewSize >= 576) && (viewSize <= 768.98)) {
result.itemsToDisplay = smallDisplay;
} else if (viewSize >= 769) {
result.itemsToDisplay = mediumDisplay;
}
if (result.itemsToDisplay && numberOfSlides <= result.itemsToDisplay) {
result.sufficientSlides = false;
}
return result;
}
/**
* Makes the next element to be displayed next unreachable for screen readers and keyboard nav
* @param {jQuery} element - the current carousel that is being used
*/
function hiddenSlides(element) {
var carousel;
if (element) {
carousel = element;
} else {
carousel = $('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel');
}
var screenSizeInfo = screenSize(carousel);
var lastDisplayedElement;
var elementToBeDisplayed;
switch (screenSizeInfo.itemsToDisplay) {
case 2:
lastDisplayedElement = carousel.find('.active.carousel-item + .carousel-item');
elementToBeDisplayed = carousel.find('.active.carousel-item + .carousel-item + .carousel-item');
break;
case 3:
lastDisplayedElement = carousel.find('.active.carousel-item + .carousel-item + .carousel-item');
elementToBeDisplayed = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item');
break;
case 4:
lastDisplayedElement = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item');
elementToBeDisplayed = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item');
break;
case 6:
lastDisplayedElement = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item');
elementToBeDisplayed = carousel.find('.active.carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item + .carousel-item');
break;
default:
break;
}
carousel.find('.active.carousel-item').removeAttr('tabindex').removeAttr('aria-hidden');
carousel.find('.active.carousel-item').find('a, button, details, input, textarea, select')
.removeAttr('tabindex')
.removeAttr('aria-hidden');
if (lastDisplayedElement) {
lastDisplayedElement.removeAttr('tabindex').removeAttr('aria-hidden');
lastDisplayedElement.find('a, button, details, input, textarea, select')
.removeAttr('tabindex')
.removeAttr('aria-hidden');
}
if (elementToBeDisplayed) {
elementToBeDisplayed.attr('tabindex', -1).attr('aria-hidden', true);
elementToBeDisplayed.find('a, button, details, input, textarea, select')
.attr('tabindex', -1)
.attr('aria-hidden', true);
}
}
$(document).ready(function () {
hiddenSlides();
$(window).on('resize', debounce(function () {
hiddenSlides();
}, 500));
$('body').on('carousel:setup', function () {
hiddenSlides();
});
$('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel').on('touchstart', function (touchStartEvent) {
var screenSizeInfo = screenSize($(this));
if (screenSizeInfo.sufficientSlides) {
var xClick = touchStartEvent.originalEvent.touches[0].pageX;
$(this).one('touchmove', function (touchMoveEvent) {
var xMove = touchMoveEvent.originalEvent.touches[0].pageX;
if (Math.floor(xClick - xMove) > 5) {
$(this).carousel('next');
} else if (Math.floor(xClick - xMove) < -5) {
$(this).carousel('prev');
}
});
$('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel').on('touchend', function () {
$(this).off('touchmove');
});
}
});
$('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel').on('slide.bs.carousel', function (e) {
var activeCarouselPosition = $(e.relatedTarget).data('position');
$(this).find('.pd-carousel-indicators .active').removeClass('active');
$(this).find(".pd-carousel-indicators [data-position='" + activeCarouselPosition + "']").addClass('active');
var extraSmallDisplay = $(this).data('xs');
var smallDisplay = $(this).data('sm');
var mediumDisplay = $(this).data('md');
var arrayOfSlidesToDisplay = [];
if (!$(this).hasClass('insufficient-xs-slides')) {
arrayOfSlidesToDisplay.push(extraSmallDisplay);
}
if (!$(this).hasClass('insufficient-sm-slides')) {
arrayOfSlidesToDisplay.push(smallDisplay);
}
if (!$(this).hasClass('insufficient-md-slides')) {
arrayOfSlidesToDisplay.push(mediumDisplay);
}
var itemsToDisplay = Math.max.apply(Math, arrayOfSlidesToDisplay);
var elementIndex = $(e.relatedTarget).index();
var numberOfSlides = $('.carousel-item', this).length;
var carouselInner = $(this).find('.carousel-inner');
var carouselItem;
if (elementIndex >= numberOfSlides - (itemsToDisplay - 1)) {
var it = itemsToDisplay - (numberOfSlides - elementIndex);
for (var i = 0; i < it; i++) {
// append slides to end
if (e.direction === 'left') {
carouselItem = $('.carousel-item', this).eq(i);
$(carouselItem).appendTo($(carouselInner));
} else {
carouselItem = $('.carousel-item', this).eq(0);
$(carouselItem).appendTo($(carouselInner));
}
}
}
});
$('.experience-commerce_layouts-carousel .carousel, .experience-einstein-einsteinCarousel .carousel, .experience-einstein-einsteinCarouselCategory .carousel, .experience-einstein-einsteinCarouselProduct .carousel').on('slid.bs.carousel', function () {
hiddenSlides($(this));
});
});

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./cart/cart'));
});

View File

@@ -0,0 +1,766 @@
'use strict';
var base = require('../product/base');
var focusHelper = require('../components/focus');
/**
* appends params to a url
* @param {string} url - Original url
* @param {Object} params - Parameters to append
* @returns {string} result url with appended parameters
*/
function appendToUrl(url, params) {
var newUrl = url;
newUrl += (newUrl.indexOf('?') !== -1 ? '&' : '?') + Object.keys(params).map(function (key) {
return key + '=' + encodeURIComponent(params[key]);
}).join('&');
return newUrl;
}
/**
* Checks whether the basket is valid. if invalid displays error message and disables
* checkout button
* @param {Object} data - AJAX response from the server
*/
function validateBasket(data) {
if (data.valid.error) {
if (data.valid.message) {
var errorHtml = '<div class="alert alert-danger alert-dismissible valid-cart-error ' +
'fade show" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' + data.valid.message + '</div>';
$('.cart-error').append(errorHtml);
} else {
$('.cart').empty().append('<div class="row"> ' +
'<div class="col-12 text-center"> ' +
'<h1>' + data.resources.emptyCartMsg + '</h1> ' +
'</div> ' +
'</div>'
);
$('.number-of-items').empty().append(data.resources.numberOfItems);
$('.minicart-quantity').empty().append(data.numItems);
$('.minicart-link').attr({
'aria-label': data.resources.minicartCountOfItems,
title: data.resources.minicartCountOfItems
});
$('.minicart .popover').empty();
$('.minicart .popover').removeClass('show');
}
$('.checkout-btn').addClass('disabled');
} else {
$('.checkout-btn').removeClass('disabled');
}
}
/**
* re-renders the order totals and the number of items in the cart
* @param {Object} data - AJAX response from the server
*/
function updateCartTotals(data) {
$('.number-of-items').empty().append(data.resources.numberOfItems);
$('.shipping-cost').empty().append(data.totals.totalShippingCost);
$('.tax-total').empty().append(data.totals.totalTax);
$('.grand-total').empty().append(data.totals.grandTotal);
$('.sub-total').empty().append(data.totals.subTotal);
$('.minicart-quantity').empty().append(data.numItems);
$('.minicart-link').attr({
'aria-label': data.resources.minicartCountOfItems,
title: data.resources.minicartCountOfItems
});
if (data.totals.orderLevelDiscountTotal.value > 0) {
$('.order-discount').removeClass('hide-order-discount');
$('.order-discount-total').empty()
.append('- ' + data.totals.orderLevelDiscountTotal.formatted);
} else {
$('.order-discount').addClass('hide-order-discount');
}
if (data.totals.shippingLevelDiscountTotal.value > 0) {
$('.shipping-discount').removeClass('hide-shipping-discount');
$('.shipping-discount-total').empty().append('- ' +
data.totals.shippingLevelDiscountTotal.formatted);
} else {
$('.shipping-discount').addClass('hide-shipping-discount');
}
data.items.forEach(function (item) {
if (data.totals.orderLevelDiscountTotal.value > 0) {
$('.coupons-and-promos').empty().append(data.totals.discountsHtml);
}
if (item.renderedPromotions) {
$('.item-' + item.UUID).empty().append(item.renderedPromotions);
} else {
$('.item-' + item.UUID).empty();
}
$('.uuid-' + item.UUID + ' .unit-price').empty().append(item.renderedPrice);
$('.line-item-price-' + item.UUID + ' .unit-price').empty().append(item.renderedPrice);
$('.item-total-' + item.UUID).empty().append(item.priceTotal.renderedPrice);
});
}
/**
* re-renders the order totals and the number of items in the cart
* @param {Object} message - Error message to display
*/
function createErrorNotification(message) {
var errorHtml = '<div class="alert alert-danger alert-dismissible valid-cart-error ' +
'fade show" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' + message + '</div>';
$('.cart-error').append(errorHtml);
}
/**
* re-renders the approaching discount messages
* @param {Object} approachingDiscounts - updated approaching discounts for the cart
*/
function updateApproachingDiscounts(approachingDiscounts) {
var html = '';
$('.approaching-discounts').empty();
if (approachingDiscounts.length > 0) {
approachingDiscounts.forEach(function (item) {
html += '<div class="single-approaching-discount text-center">'
+ item.discountMsg + '</div>';
});
}
$('.approaching-discounts').append(html);
}
/**
* Updates the availability of a product line item
* @param {Object} data - AJAX response from the server
* @param {string} uuid - The uuid of the product line item to update
*/
function updateAvailability(data, uuid) {
var lineItem;
var messages = '';
for (var i = 0; i < data.items.length; i++) {
if (data.items[i].UUID === uuid) {
lineItem = data.items[i];
break;
}
}
if (lineItem != null) {
$('.availability-' + lineItem.UUID).empty();
if (lineItem.availability) {
if (lineItem.availability.messages) {
lineItem.availability.messages.forEach(function (message) {
messages += '<p class="line-item-attributes">' + message + '</p>';
});
}
if (lineItem.availability.inStockDate) {
messages += '<p class="line-item-attributes line-item-instock-date">'
+ lineItem.availability.inStockDate
+ '</p>';
}
}
$('.availability-' + lineItem.UUID).html(messages);
}
}
/**
* Finds an element in the array that matches search parameter
* @param {array} array - array of items to search
* @param {function} match - function that takes an element and returns a boolean indicating if the match is made
* @returns {Object|null} - returns an element of the array that matched the query.
*/
function findItem(array, match) { // eslint-disable-line no-unused-vars
for (var i = 0, l = array.length; i < l; i++) {
if (match.call(this, array[i])) {
return array[i];
}
}
return null;
}
/**
* Updates details of a product line item
* @param {Object} data - AJAX response from the server
* @param {string} uuid - The uuid of the product line item to update
*/
function updateProductDetails(data, uuid) {
$('.card.product-info.uuid-' + uuid).replaceWith(data.renderedTemplate);
}
/**
* Generates the modal window on the first call.
*
*/
function getModalHtmlElement() {
if ($('#editProductModal').length !== 0) {
$('#editProductModal').remove();
}
var htmlString = '<!-- Modal -->'
+ '<div class="modal fade" id="editProductModal" tabindex="-1" role="dialog">'
+ '<span class="enter-message sr-only" ></span>'
+ '<div class="modal-dialog quick-view-dialog">'
+ '<!-- Modal content-->'
+ '<div class="modal-content">'
+ '<div class="modal-header">'
+ ' <button type="button" class="close pull-right" data-dismiss="modal">'
+ ' <span aria-hidden="true">&times;</span>'
+ ' <span class="sr-only"> </span>'
+ ' </button>'
+ '</div>'
+ '<div class="modal-body"></div>'
+ '<div class="modal-footer"></div>'
+ '</div>'
+ '</div>'
+ '</div>';
$('body').append(htmlString);
}
/**
* Parses the html for a modal window
* @param {string} html - representing the body and footer of the modal window
*
* @return {Object} - Object with properties body and footer.
*/
function parseHtml(html) {
var $html = $('<div>').append($.parseHTML(html));
var body = $html.find('.product-quickview');
var footer = $html.find('.modal-footer').children();
return { body: body, footer: footer };
}
/**
* replaces the content in the modal window for product variation to be edited.
* @param {string} editProductUrl - url to be used to retrieve a new product model
*/
function fillModalElement(editProductUrl) {
$('.modal-body').spinner().start();
$.ajax({
url: editProductUrl,
method: 'GET',
dataType: 'json',
success: function (data) {
var parsedHtml = parseHtml(data.renderedTemplate);
$('#editProductModal .modal-body').empty();
$('#editProductModal .modal-body').html(parsedHtml.body);
$('#editProductModal .modal-footer').html(parsedHtml.footer);
$('#editProductModal .modal-header .close .sr-only').text(data.closeButtonText);
$('#editProductModal .enter-message').text(data.enterDialogMessage);
$('#editProductModal').modal('show');
$('body').trigger('editproductmodal:ready');
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
}
/**
* replace content of modal
* @param {string} actionUrl - url to be used to remove product
* @param {string} productID - pid
* @param {string} productName - product name
* @param {string} uuid - uuid
*/
function confirmDelete(actionUrl, productID, productName, uuid) {
var $deleteConfirmBtn = $('.cart-delete-confirmation-btn');
var $productToRemoveSpan = $('.product-to-remove');
$deleteConfirmBtn.data('pid', productID);
$deleteConfirmBtn.data('action', actionUrl);
$deleteConfirmBtn.data('uuid', uuid);
$productToRemoveSpan.empty().append(productName);
}
module.exports = function () {
$('body').on('click', '.remove-product', function (e) {
e.preventDefault();
var actionUrl = $(this).data('action');
var productID = $(this).data('pid');
var productName = $(this).data('name');
var uuid = $(this).data('uuid');
confirmDelete(actionUrl, productID, productName, uuid);
});
$('body').on('afterRemoveFromCart', function (e, data) {
e.preventDefault();
confirmDelete(data.actionUrl, data.productID, data.productName, data.uuid);
});
$('.optional-promo').click(function (e) {
e.preventDefault();
$('.promo-code-form').toggle();
});
$('body').on('click', '.cart-delete-confirmation-btn', function (e) {
e.preventDefault();
var productID = $(this).data('pid');
var url = $(this).data('action');
var uuid = $(this).data('uuid');
var urlParams = {
pid: productID,
uuid: uuid
};
url = appendToUrl(url, urlParams);
$('body > .modal-backdrop').remove();
$.spinner().start();
$('body').trigger('cart:beforeUpdate');
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function (data) {
if (data.basket.items.length === 0) {
$('.cart').empty().append('<div class="row"> ' +
'<div class="col-12 text-center"> ' +
'<h1>' + data.basket.resources.emptyCartMsg + '</h1> ' +
'</div> ' +
'</div>'
);
$('.number-of-items').empty().append(data.basket.resources.numberOfItems);
$('.minicart-quantity').empty().append(data.basket.numItems);
$('.minicart-link').attr({
'aria-label': data.basket.resources.minicartCountOfItems,
title: data.basket.resources.minicartCountOfItems
});
$('.minicart .popover').empty();
$('.minicart .popover').removeClass('show');
$('body').removeClass('modal-open');
$('html').removeClass('veiled');
} else {
if (data.toBeDeletedUUIDs && data.toBeDeletedUUIDs.length > 0) {
for (var i = 0; i < data.toBeDeletedUUIDs.length; i++) {
$('.uuid-' + data.toBeDeletedUUIDs[i]).remove();
}
}
$('.uuid-' + uuid).remove();
if (!data.basket.hasBonusProduct) {
$('.bonus-product').remove();
}
$('.coupons-and-promos').empty().append(data.basket.totals.discountsHtml);
updateCartTotals(data.basket);
updateApproachingDiscounts(data.basket.approachingDiscounts);
$('body').trigger('setShippingMethodSelection', data.basket);
validateBasket(data.basket);
}
$('body').trigger('cart:update', data);
$.spinner().stop();
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
} else {
createErrorNotification(err.responseJSON.errorMessage);
$.spinner().stop();
}
}
});
});
$('body').on('change', '.quantity-form > .quantity', function () {
var preSelectQty = $(this).data('pre-select-qty');
var quantity = $(this).val();
var productID = $(this).data('pid');
var url = $(this).data('action');
var uuid = $(this).data('uuid');
var urlParams = {
pid: productID,
quantity: quantity,
uuid: uuid
};
url = appendToUrl(url, urlParams);
$(this).parents('.card').spinner().start();
$('body').trigger('cart:beforeUpdate');
$.ajax({
url: url,
type: 'get',
context: this,
dataType: 'json',
success: function (data) {
$('.quantity[data-uuid="' + uuid + '"]').val(quantity);
$('.coupons-and-promos').empty().append(data.totals.discountsHtml);
updateCartTotals(data);
updateApproachingDiscounts(data.approachingDiscounts);
updateAvailability(data, uuid);
validateBasket(data);
$(this).data('pre-select-qty', quantity);
$('body').trigger('cart:update', data);
$.spinner().stop();
if ($(this).parents('.product-info').hasClass('bonus-product-line-item') && $('.cart-page').length) {
location.reload();
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
} else {
createErrorNotification(err.responseJSON.errorMessage);
$(this).val(parseInt(preSelectQty, 10));
$.spinner().stop();
}
}
});
});
$('.shippingMethods').change(function () {
var url = $(this).attr('data-actionUrl');
var urlParams = {
methodID: $(this).find(':selected').attr('data-shipping-id')
};
// url = appendToUrl(url, urlParams);
$('.totals').spinner().start();
$('body').trigger('cart:beforeShippingMethodSelected');
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: urlParams,
success: function (data) {
if (data.error) {
window.location.href = data.redirectUrl;
} else {
$('.coupons-and-promos').empty().append(data.totals.discountsHtml);
updateCartTotals(data);
updateApproachingDiscounts(data.approachingDiscounts);
validateBasket(data);
}
$('body').trigger('cart:shippingMethodSelected', data);
$.spinner().stop();
},
error: function (err) {
if (err.redirectUrl) {
window.location.href = err.redirectUrl;
} else {
createErrorNotification(err.responseJSON.errorMessage);
$.spinner().stop();
}
}
});
});
$('.promo-code-form').submit(function (e) {
e.preventDefault();
$.spinner().start();
$('.coupon-missing-error').hide();
$('.coupon-error-message').empty();
if (!$('.coupon-code-field').val()) {
$('.promo-code-form .form-control').addClass('is-invalid');
$('.promo-code-form .form-control').attr('aria-describedby', 'missingCouponCode');
$('.coupon-missing-error').show();
$.spinner().stop();
return false;
}
var $form = $('.promo-code-form');
$('.promo-code-form .form-control').removeClass('is-invalid');
$('.coupon-error-message').empty();
$('body').trigger('promotion:beforeUpdate');
$.ajax({
url: $form.attr('action'),
type: 'GET',
dataType: 'json',
data: $form.serialize(),
success: function (data) {
if (data.error) {
$('.promo-code-form .form-control').addClass('is-invalid');
$('.promo-code-form .form-control').attr('aria-describedby', 'invalidCouponCode');
$('.coupon-error-message').empty().append(data.errorMessage);
$('body').trigger('promotion:error', data);
} else {
$('.coupons-and-promos').empty().append(data.totals.discountsHtml);
updateCartTotals(data);
updateApproachingDiscounts(data.approachingDiscounts);
validateBasket(data);
$('body').trigger('promotion:success', data);
}
$('.coupon-code-field').val('');
$.spinner().stop();
},
error: function (err) {
$('body').trigger('promotion:error', err);
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
} else {
createErrorNotification(err.errorMessage);
$.spinner().stop();
}
}
});
return false;
});
$('body').on('click', '.remove-coupon', function (e) {
e.preventDefault();
var couponCode = $(this).data('code');
var uuid = $(this).data('uuid');
var $deleteConfirmBtn = $('.delete-coupon-confirmation-btn');
var $productToRemoveSpan = $('.coupon-to-remove');
$deleteConfirmBtn.data('uuid', uuid);
$deleteConfirmBtn.data('code', couponCode);
$productToRemoveSpan.empty().append(couponCode);
});
$('body').on('click', '.delete-coupon-confirmation-btn', function (e) {
e.preventDefault();
var url = $(this).data('action');
var uuid = $(this).data('uuid');
var couponCode = $(this).data('code');
var urlParams = {
code: couponCode,
uuid: uuid
};
url = appendToUrl(url, urlParams);
$('body > .modal-backdrop').remove();
$.spinner().start();
$('body').trigger('promotion:beforeUpdate');
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function (data) {
$('.coupon-uuid-' + uuid).remove();
updateCartTotals(data);
updateApproachingDiscounts(data.approachingDiscounts);
validateBasket(data);
$.spinner().stop();
$('body').trigger('promotion:success', data);
},
error: function (err) {
$('body').trigger('promotion:error', err);
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
} else {
createErrorNotification(err.responseJSON.errorMessage);
$.spinner().stop();
}
}
});
});
$('body').on('click', '.cart-page .bonus-product-button', function () {
$.spinner().start();
$(this).addClass('launched-modal');
$.ajax({
url: $(this).data('url'),
method: 'GET',
dataType: 'json',
success: function (data) {
base.methods.editBonusProducts(data);
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
});
$('body').on('hidden.bs.modal', '#chooseBonusProductModal', function () {
$('#chooseBonusProductModal').remove();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
if ($('.cart-page').length) {
$('.launched-modal .btn-outline-primary').trigger('focus');
$('.launched-modal').removeClass('launched-modal');
} else {
$('.product-detail .add-to-cart').focus();
}
});
$('body').on('click', '.cart-page .product-edit .edit, .cart-page .bundle-edit .edit', function (e) {
e.preventDefault();
var editProductUrl = $(this).attr('href');
getModalHtmlElement();
fillModalElement(editProductUrl);
});
$('body').on('shown.bs.modal', '#editProductModal', function () {
$('#editProductModal').siblings().attr('aria-hidden', 'true');
$('#editProductModal .close').focus();
});
$('body').on('hidden.bs.modal', '#editProductModal', function () {
$('#editProductModal').siblings().attr('aria-hidden', 'false');
});
$('body').on('keydown', '#editProductModal', function (e) {
var focusParams = {
event: e,
containerSelector: '#editProductModal',
firstElementSelector: '.close',
lastElementSelector: '.update-cart-product-global',
nextToLastElementSelector: '.modal-footer .quantity-select'
};
focusHelper.setTabNextFocus(focusParams);
});
$('body').on('product:updateAddToCart', function (e, response) {
// update global add to cart (single products, bundles)
var dialog = $(response.$productContainer)
.closest('.quick-view-dialog');
$('.update-cart-product-global', dialog).attr('disabled',
!$('.global-availability', dialog).data('ready-to-order')
|| !$('.global-availability', dialog).data('available')
);
});
$('body').on('product:updateAvailability', function (e, response) {
// bundle individual products
$('.product-availability', response.$productContainer)
.data('ready-to-order', response.product.readyToOrder)
.data('available', response.product.available)
.find('.availability-msg')
.empty()
.html(response.message);
var dialog = $(response.$productContainer)
.closest('.quick-view-dialog');
if ($('.product-availability', dialog).length) {
// bundle all products
var allAvailable = $('.product-availability', dialog).toArray()
.every(function (item) { return $(item).data('available'); });
var allReady = $('.product-availability', dialog).toArray()
.every(function (item) { return $(item).data('ready-to-order'); });
$('.global-availability', dialog)
.data('ready-to-order', allReady)
.data('available', allAvailable);
$('.global-availability .availability-msg', dialog).empty()
.html(allReady ? response.message : response.resources.info_selectforstock);
} else {
// single product
$('.global-availability', dialog)
.data('ready-to-order', response.product.readyToOrder)
.data('available', response.product.available)
.find('.availability-msg')
.empty()
.html(response.message);
}
});
$('body').on('product:afterAttributeSelect', function (e, response) {
if ($('.modal.show .product-quickview .bundle-items').length) {
$('.modal.show').find(response.container).data('pid', response.data.product.id);
$('.modal.show').find(response.container).find('.product-id').text(response.data.product.id);
} else {
$('.modal.show .product-quickview').data('pid', response.data.product.id);
}
});
$('body').on('change', '.quantity-select', function () {
var selectedQuantity = $(this).val();
$('.modal.show .update-cart-url').data('selected-quantity', selectedQuantity);
});
$('body').on('change', '.options-select', function () {
var selectedOptionValueId = $(this).children('option:selected').data('value-id');
$('.modal.show .update-cart-url').data('selected-option', selectedOptionValueId);
});
$('body').on('click', '.update-cart-product-global', function (e) {
e.preventDefault();
var updateProductUrl = $(this).closest('.cart-and-ipay').find('.update-cart-url').val();
var selectedQuantity = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('selected-quantity');
var selectedOptionValueId = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('selected-option');
var uuid = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('uuid');
var form = {
uuid: uuid,
pid: base.getPidValue($(this)),
quantity: selectedQuantity,
selectedOptionValueId: selectedOptionValueId
};
$(this).parents('.card').spinner().start();
$('body').trigger('cart:beforeUpdate');
if (updateProductUrl) {
$.ajax({
url: updateProductUrl,
type: 'post',
context: this,
data: form,
dataType: 'json',
success: function (data) {
$('#editProductModal').modal('hide');
$('.coupons-and-promos').empty().append(data.cartModel.totals.discountsHtml);
updateCartTotals(data.cartModel);
updateApproachingDiscounts(data.cartModel.approachingDiscounts);
updateAvailability(data.cartModel, uuid);
updateProductDetails(data, uuid);
if (data.uuidToBeDeleted) {
$('.uuid-' + data.uuidToBeDeleted).remove();
}
validateBasket(data.cartModel);
$('body').trigger('cart:update', data);
$.spinner().stop();
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
} else {
createErrorNotification(err.responseJSON.errorMessage);
$.spinner().stop();
}
}
});
}
});
base.selectAttribute();
base.colorAttribute();
base.removeBonusProduct();
base.selectBonusProduct();
base.enableBonusProductSelection();
base.showMoreBonusProducts();
base.addBonusProductsToCart();
base.focusChooseBonusProductModal();
base.trapChooseBonusProductModalFocus();
base.onClosingChooseBonusProductModal();
};

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./checkout/checkout'));
});

View File

@@ -0,0 +1,187 @@
'use strict';
/**
* Populate the Billing Address Summary View
* @param {string} parentSelector - the top level DOM selector for a unique address summary
* @param {Object} address - the address data
*/
function populateAddressSummary(parentSelector, address) {
$.each(address, function (attr) {
var val = address[attr];
$('.' + attr, parentSelector).text(val || '');
});
}
/**
* returns a formed <option /> element
* @param {Object} shipping - the shipping object (shipment model)
* @param {boolean} selected - current shipping is selected (for PLI)
* @param {order} order - the Order model
* @param {Object} [options] - options
* @returns {Object} - the jQuery / DOMElement
*/
function optionValueForAddress(shipping, selected, order, options) {
var safeOptions = options || {};
var isBilling = safeOptions.type && safeOptions.type === 'billing';
var className = safeOptions.className || '';
var isSelected = selected;
var isNew = !shipping;
if (typeof shipping === 'string') {
return $('<option class="' + className + '" disabled>' + shipping + '</option>');
}
var safeShipping = shipping || {};
var shippingAddress = safeShipping.shippingAddress || {};
if (isBilling && isNew && !order.billing.matchingAddressId) {
shippingAddress = order.billing.billingAddress.address || {};
isNew = false;
isSelected = true;
safeShipping.UUID = 'manual-entry';
}
var uuid = safeShipping.UUID ? safeShipping.UUID : 'new';
var optionEl = $('<option class="' + className + '" />');
optionEl.val(uuid);
var title;
if (isNew) {
title = order.resources.addNewAddress;
} else {
title = [];
if (shippingAddress.firstName) {
title.push(shippingAddress.firstName);
}
if (shippingAddress.lastName) {
title.push(shippingAddress.lastName);
}
if (shippingAddress.address1) {
title.push(shippingAddress.address1);
}
if (shippingAddress.address2) {
title.push(shippingAddress.address2);
}
if (shippingAddress.city) {
if (shippingAddress.state) {
title.push(shippingAddress.city + ',');
} else {
title.push(shippingAddress.city);
}
}
if (shippingAddress.stateCode) {
title.push(shippingAddress.stateCode);
}
if (shippingAddress.postalCode) {
title.push(shippingAddress.postalCode);
}
if (!isBilling && safeShipping.selectedShippingMethod) {
title.push('-');
title.push(safeShipping.selectedShippingMethod.displayName);
}
if (title.length > 2) {
title = title.join(' ');
} else {
title = order.resources.newAddress;
}
}
optionEl.text(title);
var keyMap = {
'data-first-name': 'firstName',
'data-last-name': 'lastName',
'data-address1': 'address1',
'data-address2': 'address2',
'data-city': 'city',
'data-state-code': 'stateCode',
'data-postal-code': 'postalCode',
'data-country-code': 'countryCode',
'data-phone': 'phone'
};
$.each(keyMap, function (key) {
var mappedKey = keyMap[key];
var mappedValue = shippingAddress[mappedKey];
// In case of country code
if (mappedValue && typeof mappedValue === 'object') {
mappedValue = mappedValue.value;
}
optionEl.attr(key, mappedValue || '');
});
var giftObj = {
'data-is-gift': 'isGift',
'data-gift-message': 'giftMessage'
};
$.each(giftObj, function (key) {
var mappedKey = giftObj[key];
var mappedValue = safeShipping[mappedKey];
optionEl.attr(key, mappedValue || '');
});
if (isSelected) {
optionEl.attr('selected', true);
}
return optionEl;
}
/**
* returns address properties from a UI form
* @param {Form} form - the Form element
* @returns {Object} - a JSON object with all values
*/
function getAddressFieldsFromUI(form) {
var address = {
firstName: $('input[name$=_firstName]', form).val(),
lastName: $('input[name$=_lastName]', form).val(),
address1: $('input[name$=_address1]', form).val(),
address2: $('input[name$=_address2]', form).val(),
city: $('input[name$=_city]', form).val(),
postalCode: $('input[name$=_postalCode]', form).val(),
stateCode: $('select[name$=_stateCode],input[name$=_stateCode]', form).val(),
countryCode: $('select[name$=_country]', form).val(),
phone: $('input[name$=_phone]', form).val()
};
return address;
}
module.exports = {
methods: {
populateAddressSummary: populateAddressSummary,
optionValueForAddress: optionValueForAddress,
getAddressFieldsFromUI: getAddressFieldsFromUI
},
showDetails: function () {
$('.btn-show-details').on('click', function () {
var form = $(this).closest('form');
form.attr('data-address-mode', 'details');
form.find('.multi-ship-address-actions').removeClass('d-none');
form.find('.multi-ship-action-buttons .col-12.btn-save-multi-ship').addClass('d-none');
});
},
addNewAddress: function () {
$('.btn-add-new').on('click', function () {
var $el = $(this);
if ($el.parents('#dwfrm_billing').length > 0) {
// Handle billing address case
$('body').trigger('checkout:clearBillingForm');
var $option = $($el.parents('form').find('.addressSelector option')[0]);
$option.attr('value', 'new');
var $newTitle = $('#dwfrm_billing input[name=localizedNewAddressTitle]').val();
$option.text($newTitle);
$option.prop('selected', 'selected');
$el.parents('[data-address-mode]').attr('data-address-mode', 'new');
} else {
// Handle shipping address case
var $newEl = $el.parents('form').find('.addressSelector option[value=new]');
$newEl.prop('selected', 'selected');
$newEl.parent().trigger('change');
}
});
}
};

View File

@@ -0,0 +1,327 @@
'use strict';
var addressHelpers = require('./address');
var cleave = require('../components/cleave');
/**
* updates the billing address selector within billing forms
* @param {Object} order - the order model
* @param {Object} customer - the customer model
*/
function updateBillingAddressSelector(order, customer) {
var shippings = order.shipping;
var form = $('form[name$=billing]')[0];
var $billingAddressSelector = $('.addressSelector', form);
var hasSelectedAddress = false;
if ($billingAddressSelector && $billingAddressSelector.length === 1) {
$billingAddressSelector.empty();
// Add New Address option
$billingAddressSelector.append(addressHelpers.methods.optionValueForAddress(
null,
false,
order,
{ type: 'billing' }));
// Separator -
$billingAddressSelector.append(addressHelpers.methods.optionValueForAddress(
order.resources.shippingAddresses, false, order, {
// className: 'multi-shipping',
type: 'billing'
}
));
shippings.forEach(function (aShipping) {
var isSelected = order.billing.matchingAddressId === aShipping.UUID;
hasSelectedAddress = hasSelectedAddress || isSelected;
// Shipping Address option
$billingAddressSelector.append(
addressHelpers.methods.optionValueForAddress(aShipping, isSelected, order,
{
// className: 'multi-shipping',
type: 'billing'
}
)
);
});
if (customer.addresses && customer.addresses.length > 0) {
$billingAddressSelector.append(addressHelpers.methods.optionValueForAddress(
order.resources.accountAddresses, false, order));
customer.addresses.forEach(function (address) {
var isSelected = order.billing.matchingAddressId === address.ID;
hasSelectedAddress = hasSelectedAddress || isSelected;
// Customer Address option
$billingAddressSelector.append(
addressHelpers.methods.optionValueForAddress({
UUID: 'ab_' + address.ID,
shippingAddress: address
}, isSelected, order, { type: 'billing' })
);
});
}
}
if (hasSelectedAddress
|| (!order.billing.matchingAddressId && order.billing.billingAddress.address)) {
// show
$(form).attr('data-address-mode', 'edit');
} else {
$(form).attr('data-address-mode', 'new');
}
$billingAddressSelector.show();
}
/**
* Updates the billing address form values within payment forms without any payment instrument validation
* @param {Object} order - the order model
*/
function updateBillingAddress(order) {
var billing = order.billing;
if (!billing.billingAddress || !billing.billingAddress.address) return;
var form = $('form[name=dwfrm_billing]');
if (!form) return;
$('input[name$=_firstName]', form).val(billing.billingAddress.address.firstName);
$('input[name$=_lastName]', form).val(billing.billingAddress.address.lastName);
$('input[name$=_address1]', form).val(billing.billingAddress.address.address1);
$('input[name$=_address2]', form).val(billing.billingAddress.address.address2);
$('input[name$=_city]', form).val(billing.billingAddress.address.city);
$('input[name$=_postalCode]', form).val(billing.billingAddress.address.postalCode);
$('select[name$=_stateCode],input[name$=_stateCode]', form)
.val(billing.billingAddress.address.stateCode);
$('select[name$=_country]', form).val(billing.billingAddress.address.countryCode.value);
$('input[name$=_phone]', form).val(billing.billingAddress.address.phone);
$('input[name$=_email]', form).val(order.orderEmail);
}
/**
* Validate and update payment instrument form fields
* @param {Object} order - the order model
*/
function validateAndUpdateBillingPaymentInstrument(order) {
var billing = order.billing;
if (!billing.payment || !billing.payment.selectedPaymentInstruments
|| billing.payment.selectedPaymentInstruments.length <= 0) return;
var form = $('form[name=dwfrm_billing]');
if (!form) return;
var instrument = billing.payment.selectedPaymentInstruments[0];
$('select[name$=expirationMonth]', form).val(instrument.expirationMonth);
$('select[name$=expirationYear]', form).val(instrument.expirationYear);
// Force security code and card number clear
$('input[name$=securityCode]', form).val('');
$('input[name$=cardNumber]').data('cleave').setRawValue('');
}
/**
* Updates the billing address form values within payment forms
* @param {Object} order - the order model
*/
function updateBillingAddressFormValues(order) {
module.exports.methods.updateBillingAddress(order);
module.exports.methods.validateAndUpdateBillingPaymentInstrument(order);
}
/**
* clears the billing address form values
*/
function clearBillingAddressFormValues() {
updateBillingAddressFormValues({
billing: {
billingAddress: {
address: {
countryCode: {}
}
}
}
});
}
/**
* update billing address summary and contact information
* @param {Object} order - checkout model to use as basis of new truth
*/
function updateBillingAddressSummary(order) {
// update billing address summary
addressHelpers.methods.populateAddressSummary('.billing .address-summary',
order.billing.billingAddress.address);
// update billing parts of order summary
$('.order-summary-email').text(order.orderEmail);
if (order.billing.billingAddress.address) {
$('.order-summary-phone').text(order.billing.billingAddress.address.phone);
}
}
/**
* Updates the billing information in checkout, based on the supplied order model
* @param {Object} order - checkout model to use as basis of new truth
* @param {Object} customer - customer model to use as basis of new truth
* @param {Object} [options] - options
*/
function updateBillingInformation(order, customer) {
updateBillingAddressSelector(order, customer);
// update billing address form
updateBillingAddressFormValues(order);
// update billing address summary and billing parts of order summary
updateBillingAddressSummary(order);
}
/**
* Updates the payment information in checkout, based on the supplied order model
* @param {Object} order - checkout model to use as basis of new truth
*/
function updatePaymentInformation(order) {
// update payment details
var $paymentSummary = $('.payment-details');
var htmlToAppend = '';
if (order.billing.payment && order.billing.payment.selectedPaymentInstruments
&& order.billing.payment.selectedPaymentInstruments.length > 0) {
htmlToAppend += '<span>' + order.resources.cardType + ' '
+ order.billing.payment.selectedPaymentInstruments[0].type
+ '</span><div>'
+ order.billing.payment.selectedPaymentInstruments[0].maskedCreditCardNumber
+ '</div><div><span>'
+ order.resources.cardEnding + ' '
+ order.billing.payment.selectedPaymentInstruments[0].expirationMonth
+ '/' + order.billing.payment.selectedPaymentInstruments[0].expirationYear
+ '</span></div>';
}
$paymentSummary.empty().append(htmlToAppend);
}
/**
* clears the credit card form
*/
function clearCreditCardForm() {
$('input[name$="_cardNumber"]').data('cleave').setRawValue('');
$('select[name$="_expirationMonth"]').val('');
$('select[name$="_expirationYear"]').val('');
$('input[name$="_securityCode"]').val('');
}
module.exports = {
methods: {
updateBillingAddressSelector: updateBillingAddressSelector,
updateBillingAddressFormValues: updateBillingAddressFormValues,
clearBillingAddressFormValues: clearBillingAddressFormValues,
updateBillingInformation: updateBillingInformation,
updatePaymentInformation: updatePaymentInformation,
clearCreditCardForm: clearCreditCardForm,
updateBillingAddress: updateBillingAddress,
validateAndUpdateBillingPaymentInstrument: validateAndUpdateBillingPaymentInstrument,
updateBillingAddressSummary: updateBillingAddressSummary
},
showBillingDetails: function () {
$('.btn-show-billing-details').on('click', function () {
$(this).parents('[data-address-mode]').attr('data-address-mode', 'new');
});
},
hideBillingDetails: function () {
$('.btn-hide-billing-details').on('click', function () {
$(this).parents('[data-address-mode]').attr('data-address-mode', 'shipment');
});
},
selectBillingAddress: function () {
$('.payment-form .addressSelector').on('change', function () {
var form = $(this).parents('form')[0];
var selectedOption = $('option:selected', this);
var optionID = selectedOption[0].value;
if (optionID === 'new') {
// Show Address
$(form).attr('data-address-mode', 'new');
} else {
// Hide Address
$(form).attr('data-address-mode', 'shipment');
}
// Copy fields
var attrs = selectedOption.data();
var element;
Object.keys(attrs).forEach(function (attr) {
element = attr === 'countryCode' ? 'country' : attr;
if (element === 'cardNumber') {
$('.cardNumber').data('cleave').setRawValue(attrs[attr]);
} else {
$('[name$=' + element + ']', form).val(attrs[attr]);
}
});
});
},
handleCreditCardNumber: function () {
cleave.handleCreditCardNumber('.cardNumber', '#cardType');
},
santitizeForm: function () {
$('body').on('checkout:serializeBilling', function (e, data) {
var serializedForm = cleave.serializeData(data.form);
data.callback(serializedForm);
});
},
selectSavedPaymentInstrument: function () {
$(document).on('click', '.saved-payment-instrument', function (e) {
e.preventDefault();
$('.saved-payment-security-code').val('');
$('.saved-payment-instrument').removeClass('selected-payment');
$(this).addClass('selected-payment');
$('.saved-payment-instrument .card-image').removeClass('checkout-hidden');
$('.saved-payment-instrument .security-code-input').addClass('checkout-hidden');
$('.saved-payment-instrument.selected-payment' +
' .card-image').addClass('checkout-hidden');
$('.saved-payment-instrument.selected-payment ' +
'.security-code-input').removeClass('checkout-hidden');
});
},
addNewPaymentInstrument: function () {
$('.btn.add-payment').on('click', function (e) {
e.preventDefault();
$('.payment-information').data('is-new-payment', true);
clearCreditCardForm();
$('.credit-card-form').removeClass('checkout-hidden');
$('.user-payment-instruments').addClass('checkout-hidden');
});
},
cancelNewPayment: function () {
$('.cancel-new-payment').on('click', function (e) {
e.preventDefault();
$('.payment-information').data('is-new-payment', false);
clearCreditCardForm();
$('.user-payment-instruments').removeClass('checkout-hidden');
$('.credit-card-form').addClass('checkout-hidden');
});
},
clearBillingForm: function () {
$('body').on('checkout:clearBillingForm', function () {
clearBillingAddressFormValues();
});
},
paymentTabs: function () {
$('.payment-options .nav-item').on('click', function (e) {
e.preventDefault();
var methodID = $(this).data('method-id');
$('.payment-information').data('payment-method-id', methodID);
});
}
};

View File

@@ -0,0 +1,633 @@
'use strict';
var customerHelpers = require('./customer');
var addressHelpers = require('./address');
var shippingHelpers = require('./shipping');
var billingHelpers = require('./billing');
var summaryHelpers = require('./summary');
var formHelpers = require('./formErrors');
var scrollAnimate = require('../components/scrollAnimate');
/**
* Create the jQuery Checkout Plugin.
*
* This jQuery plugin will be registered on the dom element in checkout.isml with the
* id of "checkout-main".
*
* The checkout plugin will handle the different state the user interface is in as the user
* progresses through the varying forms such as shipping and payment.
*
* Billing info and payment info are used a bit synonymously in this code.
*
*/
(function ($) {
$.fn.checkout = function () { // eslint-disable-line
var plugin = this;
//
// Collect form data from user input
//
var formData = {
// Customer Data
customer: {},
// Shipping Address
shipping: {},
// Billing Address
billing: {},
// Payment
payment: {},
// Gift Codes
giftCode: {}
};
//
// The different states/stages of checkout
//
var checkoutStages = [
'customer',
'shipping',
'payment',
'placeOrder',
'submitted'
];
/**
* Updates the URL to determine stage
* @param {number} currentStage - The current stage the user is currently on in the checkout
*/
function updateUrl(currentStage) {
history.pushState(
checkoutStages[currentStage],
document.title,
location.pathname
+ '?stage='
+ checkoutStages[currentStage]
+ '#'
+ checkoutStages[currentStage]
);
}
//
// Local member methods of the Checkout plugin
//
var members = {
// initialize the currentStage variable for the first time
currentStage: 0,
/**
* Set or update the checkout stage (AKA the shipping, billing, payment, etc... steps)
* @returns {Object} a promise
*/
updateStage: function () {
var stage = checkoutStages[members.currentStage];
var defer = $.Deferred(); // eslint-disable-line
if (stage === 'customer') {
//
// Clear Previous Errors
//
customerHelpers.methods.clearErrors();
//
// Submit the Customer Form
//
var customerFormSelector = customerHelpers.methods.isGuestFormActive() ? customerHelpers.vars.GUEST_FORM : customerHelpers.vars.REGISTERED_FORM;
var customerForm = $(customerFormSelector);
$.ajax({
url: customerForm.attr('action'),
type: 'post',
data: customerForm.serialize(),
success: function (data) {
if (data.redirectUrl) {
window.location.href = data.redirectUrl;
} else {
customerHelpers.methods.customerFormResponse(defer, data);
}
},
error: function (err) {
if (err.responseJSON && err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
// Server error submitting form
defer.reject(err.responseJSON);
}
});
return defer;
} else if (stage === 'shipping') {
//
// Clear Previous Errors
//
formHelpers.clearPreviousErrors('.shipping-form');
//
// Submit the Shipping Address Form
//
var isMultiShip = $('#checkout-main').hasClass('multi-ship');
var formSelector = isMultiShip ?
'.multi-shipping .active form' : '.single-shipping .shipping-form';
var form = $(formSelector);
if (isMultiShip && form.length === 0) {
// disable the next:Payment button here
$('body').trigger('checkout:disableButton', '.next-step-button button');
// in case the multi ship form is already submitted
var url = $('#checkout-main').attr('data-checkout-get-url');
$.ajax({
url: url,
method: 'GET',
success: function (data) {
// enable the next:Payment button here
$('body').trigger('checkout:enableButton', '.next-step-button button');
if (!data.error) {
$('body').trigger('checkout:updateCheckoutView',
{ order: data.order, customer: data.customer });
defer.resolve();
} else if (data.message && $('.shipping-error .alert-danger').length < 1) {
var errorMsg = data.message;
var errorHtml = '<div class="alert alert-danger alert-dismissible valid-cart-error ' +
'fade show" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' + errorMsg + '</div>';
$('.shipping-error').append(errorHtml);
scrollAnimate($('.shipping-error'));
defer.reject();
} else if (data.redirectUrl) {
window.location.href = data.redirectUrl;
}
},
error: function () {
// enable the next:Payment button here
$('body').trigger('checkout:enableButton', '.next-step-button button');
// Server error submitting form
defer.reject();
}
});
} else {
var shippingFormData = form.serialize();
$('body').trigger('checkout:serializeShipping', {
form: form,
data: shippingFormData,
callback: function (data) {
shippingFormData = data;
}
});
// disable the next:Payment button here
$('body').trigger('checkout:disableButton', '.next-step-button button');
$.ajax({
url: form.attr('action'),
type: 'post',
data: shippingFormData,
success: function (data) {
// enable the next:Payment button here
$('body').trigger('checkout:enableButton', '.next-step-button button');
shippingHelpers.methods.shippingFormResponse(defer, data);
},
error: function (err) {
// enable the next:Payment button here
$('body').trigger('checkout:enableButton', '.next-step-button button');
if (err.responseJSON && err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
// Server error submitting form
defer.reject(err.responseJSON);
}
});
}
return defer;
} else if (stage === 'payment') {
//
// Submit the Billing Address Form
//
formHelpers.clearPreviousErrors('.payment-form');
var billingAddressForm = $('#dwfrm_billing .billing-address-block :input').serialize();
$('body').trigger('checkout:serializeBilling', {
form: $('#dwfrm_billing .billing-address-block'),
data: billingAddressForm,
callback: function (data) {
if (data) {
billingAddressForm = data;
}
}
});
var contactInfoForm = $('#dwfrm_billing .contact-info-block :input').serialize();
$('body').trigger('checkout:serializeBilling', {
form: $('#dwfrm_billing .contact-info-block'),
data: contactInfoForm,
callback: function (data) {
if (data) {
contactInfoForm = data;
}
}
});
var activeTabId = $('.tab-pane.active').attr('id');
var paymentInfoSelector = '#dwfrm_billing .' + activeTabId + ' .payment-form-fields :input';
var paymentInfoForm = $(paymentInfoSelector).serialize();
$('body').trigger('checkout:serializeBilling', {
form: $(paymentInfoSelector),
data: paymentInfoForm,
callback: function (data) {
if (data) {
paymentInfoForm = data;
}
}
});
var paymentForm = billingAddressForm + '&' + contactInfoForm + '&' + paymentInfoForm;
if ($('.data-checkout-stage').data('customer-type') === 'registered') {
// if payment method is credit card
if ($('.payment-information').data('payment-method-id') === 'CREDIT_CARD') {
if (!($('.payment-information').data('is-new-payment'))) {
var cvvCode = $('.saved-payment-instrument.' +
'selected-payment .saved-payment-security-code').val();
if (cvvCode === '') {
var cvvElement = $('.saved-payment-instrument.' +
'selected-payment ' +
'.form-control');
cvvElement.addClass('is-invalid');
scrollAnimate(cvvElement);
defer.reject();
return defer;
}
var $savedPaymentInstrument = $('.saved-payment-instrument' +
'.selected-payment'
);
paymentForm += '&storedPaymentUUID=' +
$savedPaymentInstrument.data('uuid');
paymentForm += '&securityCode=' + cvvCode;
}
}
}
// disable the next:Place Order button here
$('body').trigger('checkout:disableButton', '.next-step-button button');
$.ajax({
url: $('#dwfrm_billing').attr('action'),
method: 'POST',
data: paymentForm,
success: function (data) {
// enable the next:Place Order button here
$('body').trigger('checkout:enableButton', '.next-step-button button');
// look for field validation errors
if (data.error) {
if (data.fieldErrors.length) {
data.fieldErrors.forEach(function (error) {
if (Object.keys(error).length) {
formHelpers.loadFormErrors('.payment-form', error);
}
});
}
if (data.serverErrors.length) {
data.serverErrors.forEach(function (error) {
$('.error-message').show();
$('.error-message-text').text(error);
scrollAnimate($('.error-message'));
});
}
if (data.cartError) {
window.location.href = data.redirectUrl;
}
defer.reject();
} else {
//
// Populate the Address Summary
//
$('body').trigger('checkout:updateCheckoutView',
{ order: data.order, customer: data.customer });
if (data.renderedPaymentInstruments) {
$('.stored-payments').empty().html(
data.renderedPaymentInstruments
);
}
if (data.customer.registeredUser
&& data.customer.customerPaymentInstruments.length
) {
$('.cancel-new-payment').removeClass('checkout-hidden');
}
scrollAnimate();
defer.resolve(data);
}
},
error: function (err) {
// enable the next:Place Order button here
$('body').trigger('checkout:enableButton', '.next-step-button button');
if (err.responseJSON && err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
}
});
return defer;
} else if (stage === 'placeOrder') {
// disable the placeOrder button here
$('body').trigger('checkout:disableButton', '.next-step-button button');
$.ajax({
url: $('.place-order').data('action'),
method: 'POST',
success: function (data) {
// enable the placeOrder button here
$('body').trigger('checkout:enableButton', '.next-step-button button');
if (data.error) {
if (data.cartError) {
window.location.href = data.redirectUrl;
defer.reject();
} else {
// go to appropriate stage and display error message
defer.reject(data);
}
} else {
var redirect = $('<form>')
.appendTo(document.body)
.attr({
method: 'POST',
action: data.continueUrl
});
$('<input>')
.appendTo(redirect)
.attr({
name: 'orderID',
value: data.orderID
});
$('<input>')
.appendTo(redirect)
.attr({
name: 'orderToken',
value: data.orderToken
});
redirect.submit();
defer.resolve(data);
}
},
error: function () {
// enable the placeOrder button here
$('body').trigger('checkout:enableButton', $('.next-step-button button'));
}
});
return defer;
}
var p = $('<div>').promise(); // eslint-disable-line
setTimeout(function () {
p.done(); // eslint-disable-line
}, 500);
return p; // eslint-disable-line
},
/**
* Initialize the checkout stage.
*
* TODO: update this to allow stage to be set from server?
*/
initialize: function () {
// set the initial state of checkout
members.currentStage = checkoutStages
.indexOf($('.data-checkout-stage').data('checkout-stage'));
$(plugin).attr('data-checkout-stage', checkoutStages[members.currentStage]);
$('body').on('click', '.submit-customer-login', function (e) {
e.preventDefault();
members.nextStage();
});
$('body').on('click', '.submit-customer', function (e) {
e.preventDefault();
members.nextStage();
});
//
// Handle Payment option selection
//
$('input[name$="paymentMethod"]', plugin).on('change', function () {
$('.credit-card-form').toggle($(this).val() === 'CREDIT_CARD');
});
//
// Handle Next State button click
//
$(plugin).on('click', '.next-step-button button', function () {
members.nextStage();
});
//
// Handle Edit buttons on shipping and payment summary cards
//
$('.customer-summary .edit-button', plugin).on('click', function () {
members.gotoStage('customer');
});
$('.shipping-summary .edit-button', plugin).on('click', function () {
if (!$('#checkout-main').hasClass('multi-ship')) {
$('body').trigger('shipping:selectSingleShipping');
}
members.gotoStage('shipping');
});
$('.payment-summary .edit-button', plugin).on('click', function () {
members.gotoStage('payment');
});
//
// remember stage (e.g. shipping)
//
updateUrl(members.currentStage);
//
// Listen for foward/back button press and move to correct checkout-stage
//
$(window).on('popstate', function (e) {
//
// Back button when event state less than current state in ordered
// checkoutStages array.
//
if (e.state === null ||
checkoutStages.indexOf(e.state) < members.currentStage) {
members.handlePrevStage(false);
} else if (checkoutStages.indexOf(e.state) > members.currentStage) {
// Forward button pressed
members.handleNextStage(false);
}
});
//
// Set the form data
//
plugin.data('formData', formData);
},
/**
* The next checkout state step updates the css for showing correct buttons etc...
*/
nextStage: function () {
var promise = members.updateStage();
promise.done(function () {
// Update UI with new stage
$('.error-message').hide();
members.handleNextStage(true);
});
promise.fail(function (data) {
// show errors
if (data) {
if (data.errorStage) {
members.gotoStage(data.errorStage.stage);
if (data.errorStage.step === 'billingAddress') {
var $billingAddressSameAsShipping = $(
'input[name$="_shippingAddressUseAsBillingAddress"]'
);
if ($billingAddressSameAsShipping.is(':checked')) {
$billingAddressSameAsShipping.prop('checked', false);
}
}
}
if (data.errorMessage) {
$('.error-message').show();
$('.error-message-text').text(data.errorMessage);
}
}
});
},
/**
* The next checkout state step updates the css for showing correct buttons etc...
*
* @param {boolean} bPushState - boolean when true pushes state using the history api.
*/
handleNextStage: function (bPushState) {
if (members.currentStage < checkoutStages.length - 1) {
// move stage forward
members.currentStage++;
//
// show new stage in url (e.g.payment)
//
if (bPushState) {
updateUrl(members.currentStage);
}
}
// Set the next stage on the DOM
$(plugin).attr('data-checkout-stage', checkoutStages[members.currentStage]);
},
/**
* Previous State
*/
handlePrevStage: function () {
if (members.currentStage > 0) {
// move state back
members.currentStage--;
updateUrl(members.currentStage);
}
$(plugin).attr('data-checkout-stage', checkoutStages[members.currentStage]);
},
/**
* Use window history to go to a checkout stage
* @param {string} stageName - the checkout state to goto
*/
gotoStage: function (stageName) {
members.currentStage = checkoutStages.indexOf(stageName);
updateUrl(members.currentStage);
$(plugin).attr('data-checkout-stage', checkoutStages[members.currentStage]);
}
};
//
// Initialize the checkout
//
members.initialize();
return this;
};
}(jQuery));
var exports = {
initialize: function () {
$('#checkout-main').checkout();
},
updateCheckoutView: function () {
$('body').on('checkout:updateCheckoutView', function (e, data) {
if (data.csrfToken) {
$("input[name*='csrf_token']").val(data.csrfToken);
}
customerHelpers.methods.updateCustomerInformation(data.customer, data.order);
shippingHelpers.methods.updateMultiShipInformation(data.order);
summaryHelpers.updateTotals(data.order.totals);
data.order.shipping.forEach(function (shipping) {
shippingHelpers.methods.updateShippingInformation(
shipping,
data.order,
data.customer,
data.options
);
});
billingHelpers.methods.updateBillingInformation(
data.order,
data.customer,
data.options
);
billingHelpers.methods.updatePaymentInformation(data.order, data.options);
summaryHelpers.updateOrderProductSummaryInformation(data.order, data.options);
});
},
disableButton: function () {
$('body').on('checkout:disableButton', function (e, button) {
$(button).prop('disabled', true);
});
},
enableButton: function () {
$('body').on('checkout:enableButton', function (e, button) {
$(button).prop('disabled', false);
});
}
};
[customerHelpers, billingHelpers, shippingHelpers, addressHelpers].forEach(function (library) {
Object.keys(library).forEach(function (item) {
if (typeof library[item] === 'object') {
exports[item] = $.extend({}, exports[item], library[item]);
} else {
exports[item] = library[item];
}
});
});
module.exports = exports;

View File

@@ -0,0 +1,143 @@
'use strict';
var formHelpers = require('./formErrors');
var scrollAnimate = require('../components/scrollAnimate');
var createErrorNotification = require('../components/errorNotification');
var GUEST_FORM = '#guest-customer';
var REGISTERED_FORM = '#registered-customer';
var ERROR_SECTION = '.customer-error';
/**
* @returns {boolean} If guest is active, registered is not visible
*/
function isGuestFormActive() {
return $(REGISTERED_FORM).hasClass('d-none');
}
/**
* Clear any previous errors in the customer form.
*/
function clearErrors() {
$(ERROR_SECTION).children().remove();
formHelpers.clearPreviousErrors('.customer-information-block');
}
/**
* @param {Object} customerData - data includes checkout related customer information
* @param {Object} orderData - data includes checkout related order information
*/
function updateCustomerInformation(customerData, orderData) {
var $container = $('.customer-summary');
var $summaryDetails = $container.find('.summary-details');
var email = customerData.profile && customerData.profile.email ? customerData.profile.email : orderData.orderEmail;
$summaryDetails.find('.customer-summary-email').text(email);
if (customerData.registeredUser) {
$container.find('.edit-button').hide();
} else {
$container.find('.edit-button').show();
}
}
/**
* Handle response from the server for valid or invalid form fields.
* @param {Object} defer - the deferred object which will resolve on success or reject.
* @param {Object} data - the response data with the invalid form fields or
* valid model data.
*/
function customerFormResponse(defer, data) {
var parentForm = isGuestFormActive() ? GUEST_FORM : REGISTERED_FORM;
var formSelector = '.customer-section ' + parentForm;
// highlight fields with errors
if (data.error) {
if (data.fieldErrors.length) {
data.fieldErrors.forEach(function (error) {
if (Object.keys(error).length) {
formHelpers.loadFormErrors(formSelector, error);
}
});
}
if (data.customerErrorMessage) {
createErrorNotification(ERROR_SECTION, data.customerErrorMessage);
}
if (data.fieldErrors.length || data.customerErrorMessage || (data.serverErrors && data.serverErrors.length)) {
defer.reject(data);
}
if (data.cartError) {
window.location.href = data.redirectUrl;
defer.reject();
}
} else {
// Populate the Address Summary
$('body').trigger('checkout:updateCheckoutView', {
order: data.order,
customer: data.customer,
csrfToken: data.csrfToken
});
scrollAnimate($('.shipping-form'));
defer.resolve(data);
}
}
/**
*
* @param {boolean} registered - wether a registered login block will be used
*/
function chooseLoginBlock(registered) {
$(ERROR_SECTION).find('.alert').remove();
$('#password').val('');
if (registered) {
$(REGISTERED_FORM).removeClass('d-none');
$(GUEST_FORM).addClass('d-none');
$('#email').val($('#email-guest').val());
} else {
$(REGISTERED_FORM).addClass('d-none');
$(GUEST_FORM).removeClass('d-none');
$('#email').val('');
}
}
module.exports = {
/**
* Listeners for customer form
*/
initListeners: function () {
// 1. password
var customerLogin = '.js-login-customer';
var cancelLogin = '.js-cancel-login';
var registered;
if (customerLogin.length !== 0) {
$('body').on('click', customerLogin, function (e) {
registered = true;
e.preventDefault();
chooseLoginBlock(registered);
});
}
if (cancelLogin.length !== 0) {
$('body').on('click', cancelLogin, function (e) {
registered = false;
e.preventDefault();
chooseLoginBlock(registered);
});
}
},
methods: {
clearErrors: clearErrors,
updateCustomerInformation: updateCustomerInformation,
customerFormResponse: customerFormResponse,
isGuestFormActive: isGuestFormActive
},
vars: {
GUEST_FORM: GUEST_FORM,
REGISTERED_FORM: REGISTERED_FORM
}
};

View File

@@ -0,0 +1,34 @@
'use strict';
var scrollAnimate = require('../components/scrollAnimate');
/**
* Display error messages and highlight form fields with errors.
* @param {string} parentSelector - the form which contains the fields
* @param {Object} fieldErrors - the fields with errors
*/
function loadFormErrors(parentSelector, fieldErrors) { // eslint-disable-line
// Display error messages and highlight form fields with errors.
$.each(fieldErrors, function (attr) {
$('*[name=' + attr + ']', parentSelector)
.addClass('is-invalid')
.siblings('.invalid-feedback')
.html(fieldErrors[attr]);
});
// Animate to top of form that has errors
scrollAnimate($(parentSelector));
}
/**
* Clear the form errors.
* @param {string} parentSelector - the parent form selector.
*/
function clearPreviousErrors(parentSelector) {
$(parentSelector).find('.form-control.is-invalid').removeClass('is-invalid');
$('.error-message').hide();
}
module.exports = {
loadFormErrors: loadFormErrors,
clearPreviousErrors: clearPreviousErrors
};

View File

@@ -0,0 +1,146 @@
'use strict';
/**
* updates the totals summary
* @param {Array} totals - the totals data
*/
function updateTotals(totals) {
$('.shipping-total-cost').text(totals.totalShippingCost);
$('.tax-total').text(totals.totalTax);
$('.sub-total').text(totals.subTotal);
$('.grand-total-sum').text(totals.grandTotal);
if (totals.orderLevelDiscountTotal.value > 0) {
$('.order-discount').removeClass('hide-order-discount');
$('.order-discount-total').text('- ' + totals.orderLevelDiscountTotal.formatted);
} else {
$('.order-discount').addClass('hide-order-discount');
}
if (totals.shippingLevelDiscountTotal.value > 0) {
$('.shipping-discount').removeClass('hide-shipping-discount');
$('.shipping-discount-total').text('- ' +
totals.shippingLevelDiscountTotal.formatted);
} else {
$('.shipping-discount').addClass('hide-shipping-discount');
}
}
/**
* updates the order product shipping summary for an order model
* @param {Object} order - the order model
*/
function updateOrderProductSummaryInformation(order) {
var $productSummary = $('<div />');
order.shipping.forEach(function (shipping) {
shipping.productLineItems.items.forEach(function (lineItem) {
var pli = $('[data-product-line-item=' + lineItem.UUID + ']');
$productSummary.append(pli);
});
var address = shipping.shippingAddress || {};
var selectedMethod = shipping.selectedShippingMethod;
var nameLine = address.firstName ? address.firstName + ' ' : '';
if (address.lastName) nameLine += address.lastName;
var address1Line = address.address1;
var address2Line = address.address2;
var phoneLine = address.phone;
var shippingCost = selectedMethod ? selectedMethod.shippingCost : '';
var methodNameLine = selectedMethod ? selectedMethod.displayName : '';
var methodArrivalTime = selectedMethod && selectedMethod.estimatedArrivalTime
? '( ' + selectedMethod.estimatedArrivalTime + ' )'
: '';
var tmpl = $('#pli-shipping-summary-template').clone();
if (shipping.productLineItems.items && shipping.productLineItems.items.length > 1) {
$('h5 > span').text(' - ' + shipping.productLineItems.items.length + ' '
+ order.resources.items);
} else {
$('h5 > span').text('');
}
var stateRequiredAttr = $('#shippingState').attr('required');
var isRequired = stateRequiredAttr !== undefined && stateRequiredAttr !== false;
var stateExists = (shipping.shippingAddress && shipping.shippingAddress.stateCode)
? shipping.shippingAddress.stateCode
: false;
var stateBoolean = false;
if ((isRequired && stateExists) || (!isRequired)) {
stateBoolean = true;
}
var shippingForm = $('.multi-shipping input[name="shipmentUUID"][value="' + shipping.UUID + '"]').parent();
if (shipping.shippingAddress
&& shipping.shippingAddress.firstName
&& shipping.shippingAddress.address1
&& shipping.shippingAddress.city
&& stateBoolean
&& shipping.shippingAddress.countryCode
&& (shipping.shippingAddress.phone || shipping.productLineItems.items[0].fromStoreId)) {
$('.ship-to-name', tmpl).text(nameLine);
$('.ship-to-address1', tmpl).text(address1Line);
$('.ship-to-address2', tmpl).text(address2Line);
$('.ship-to-city', tmpl).text(address.city);
if (address.stateCode) {
$('.ship-to-st', tmpl).text(address.stateCode);
}
$('.ship-to-zip', tmpl).text(address.postalCode);
$('.ship-to-phone', tmpl).text(phoneLine);
if (!address2Line) {
$('.ship-to-address2', tmpl).hide();
}
if (!phoneLine) {
$('.ship-to-phone', tmpl).hide();
}
shippingForm.find('.ship-to-message').text('');
} else {
shippingForm.find('.ship-to-message').text(order.resources.addressIncomplete);
}
if (shipping.isGift) {
$('.gift-message-summary', tmpl).text(shipping.giftMessage);
} else {
$('.gift-summary', tmpl).addClass('d-none');
}
// checking h5 title shipping to or pickup
var $shippingAddressLabel = $('.shipping-header-text', tmpl);
$('body').trigger('shipping:updateAddressLabelText',
{ selectedShippingMethod: selectedMethod, resources: order.resources, shippingAddressLabel: $shippingAddressLabel });
if (shipping.selectedShippingMethod) {
$('.display-name', tmpl).text(methodNameLine);
$('.arrival-time', tmpl).text(methodArrivalTime);
$('.price', tmpl).text(shippingCost);
}
var $shippingSummary = $('<div class="multi-shipping" data-shipment-summary="'
+ shipping.UUID + '" />');
$shippingSummary.html(tmpl.html());
$productSummary.append($shippingSummary);
});
$('.product-summary-block').html($productSummary.html());
// Also update the line item prices, as they might have been altered
$('.grand-total-price').text(order.totals.subTotal);
order.items.items.forEach(function (item) {
if (item.priceTotal && item.priceTotal.renderedPrice) {
$('.item-total-' + item.UUID).empty().append(item.priceTotal.renderedPrice);
}
});
}
module.exports = {
updateTotals: updateTotals,
updateOrderProductSummaryInformation: updateOrderProductSummaryInformation
};

View File

@@ -0,0 +1,33 @@
'use strict';
var formValidation = require('./components/formValidation');
$(document).ready(function () {
$('form.checkout-registration').submit(function (e) {
var form = $(this);
e.preventDefault();
var url = form.attr('action');
form.spinner().start();
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: form.serialize(),
success: function (data) {
form.spinner().stop();
if (!data.success) {
formValidation(form, data);
} else {
location.href = data.redirectUrl;
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
form.spinner().stop();
}
});
return false;
});
});

View File

@@ -0,0 +1,45 @@
'use strict';
var Cleave = require('cleave.js').default;
module.exports = {
handleCreditCardNumber: function (cardFieldSelector, cardTypeSelector) {
var cleave = new Cleave(cardFieldSelector, {
creditCard: true,
onCreditCardTypeChanged: function (type) {
var creditCardTypes = {
visa: 'Visa',
mastercard: 'Master Card',
amex: 'Amex',
discover: 'Discover',
unknown: 'Unknown'
};
var cardType = creditCardTypes[Object.keys(creditCardTypes).indexOf(type) > -1
? type
: 'unknown'];
$(cardTypeSelector).val(cardType);
$('.card-number-wrapper').attr('data-type', type);
if (type === 'visa' || type === 'mastercard' || type === 'discover') {
$('#securityCode').attr('maxlength', 3);
} else {
$('#securityCode').attr('maxlength', 4);
}
}
});
$(cardFieldSelector).data('cleave', cleave);
},
serializeData: function (form) {
var serializedArray = form.serializeArray();
serializedArray.forEach(function (item) {
if (item.name.indexOf('cardNumber') > -1) {
item.value = $('#cardNumber').data('cleave').getRawValue(); // eslint-disable-line
}
});
return $.param(serializedArray);
}
};

View File

@@ -0,0 +1,83 @@
'use strict';
/**
* Validate whole form. Requires `this` to be set to form object
* @param {jQuery.event} event - Event to be canceled if form is invalid.
* @returns {boolean} - Flag to indicate if form is valid
*/
function validateForm(event) {
var valid = true;
if (this.checkValidity && !this.checkValidity()) {
// safari
valid = false;
if (event) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
}
$(this).find('input, select').each(function () {
if (!this.validity.valid) {
$(this).trigger('invalid', this.validity);
}
});
}
return valid;
}
/**
* Remove all validation. Should be called every time before revalidating form
* @param {element} form - Form to be cleared
* @returns {void}
*/
function clearForm(form) {
$(form).find('.form-control.is-invalid').removeClass('is-invalid');
}
module.exports = {
invalid: function () {
$('form input, form select').on('invalid', function (e) {
e.preventDefault();
this.setCustomValidity('');
if (!this.validity.valid) {
var validationMessage = this.validationMessage;
$(this).addClass('is-invalid');
if (this.validity.patternMismatch && $(this).data('pattern-mismatch')) {
validationMessage = $(this).data('pattern-mismatch');
}
if ((this.validity.rangeOverflow || this.validity.rangeUnderflow)
&& $(this).data('range-error')) {
validationMessage = $(this).data('range-error');
}
if ((this.validity.tooLong || this.validity.tooShort)
&& $(this).data('range-error')) {
validationMessage = $(this).data('range-error');
}
if (this.validity.valueMissing && $(this).data('missing-error')) {
validationMessage = $(this).data('missing-error');
}
$(this).parents('.form-group').find('.invalid-feedback')
.text(validationMessage);
}
});
},
submit: function () {
$('form').on('submit', function (e) {
return validateForm.call(this, e);
});
},
buttonClick: function () {
$('form button[type="submit"], form input[type="submit"]').on('click', function () {
// clear all errors when trying to submit the form
clearForm($(this).parents('form'));
});
},
functions: {
validateForm: function (form, event) {
validateForm.call($(form), event || null);
},
clearForm: clearForm
}
};

View File

@@ -0,0 +1,18 @@
'use strict';
module.exports = function () {
var sizes = ['xs', 'sm', 'md', 'lg', 'xl'];
sizes.forEach(function (size) {
var selector = '.collapsible-' + size + ' .title';
$('body').on('click', selector, function (e) {
e.preventDefault();
$(this).parents('.collapsible-' + size).toggleClass('active');
if ($(this).parents('.collapsible-' + size).hasClass('active')) {
$(this).attr('aria-expanded', true);
} else {
$(this).attr('aria-expanded', false);
}
});
});
};

View File

@@ -0,0 +1,107 @@
'use strict';
var focusHelper = require('../components/focus');
/**
* Renders a modal window that will track the users consenting to accepting site tracking policy
*/
function showConsentModal() {
if (!$('.tracking-consent').data('caonline')) {
return;
}
var urlContent = $('.tracking-consent').data('url');
var urlAccept = $('.tracking-consent').data('accept');
var urlReject = $('.tracking-consent').data('reject');
var textYes = $('.tracking-consent').data('accepttext');
var textNo = $('.tracking-consent').data('rejecttext');
var textHeader = $('.tracking-consent').data('heading');
var htmlString = '<!-- Modal -->'
+ '<div class="modal show" id="consent-tracking" aria-modal="true" role="dialog" style="display: block;">'
+ '<div class="modal-dialog">'
+ '<!-- Modal content-->'
+ '<div class="modal-content">'
+ '<div class="modal-header">'
+ textHeader
+ '</div>'
+ '<div class="modal-body"></div>'
+ '<div class="modal-footer">'
+ '<div class="button-wrapper">'
+ '<button class="affirm btn btn-primary" data-url="' + urlAccept + '" autofocus data-dismiss="modal">'
+ textYes
+ '</button>'
+ '<button class="decline btn btn-primary" data-url="' + urlReject + '" data-dismiss="modal" >'
+ textNo
+ '</button>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>';
$.spinner().start();
$('body').append(htmlString);
$.ajax({
url: urlContent,
type: 'get',
dataType: 'html',
success: function (response) {
$('.modal-body').html(response);
$('#consent-tracking').modal('show');
},
error: function () {
$('#consent-tracking').remove();
}
});
$('#consent-tracking .button-wrapper button').click(function (e) {
e.preventDefault();
var url = $(this).data('url');
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function () {
$('#consent-tracking').remove();
$.spinner().stop();
},
error: function () {
$('#consent-tracking').remove();
$.spinner().stop();
}
});
});
}
module.exports = function () {
if ($('.consented').length === 0 && $('.tracking-consent').hasClass('api-true')) {
showConsentModal();
}
if ($('.tracking-consent').hasClass('api-true')) {
$('.tracking-consent').click(function () {
showConsentModal();
});
}
$('body').on('shown.bs.modal', '#consent-tracking', function () {
$('#consent-tracking').siblings().attr('aria-hidden', 'true');
$('#consent-tracking .close').focus();
});
$('body').on('hidden.bs.modal', '#consent-tracking', function () {
$('#consent-tracking').siblings().attr('aria-hidden', 'false');
});
$('body').on('keydown', '#consent-tracking', function (e) {
var focusParams = {
event: e,
containerSelector: '#consent-tracking',
firstElementSelector: '.affirm',
lastElementSelector: '.decline',
nextToLastElementSelector: '.affirm'
};
focusHelper.setTabNextFocus(focusParams);
});
};

View File

@@ -0,0 +1,44 @@
'use strict';
/**
* Get cookie value by cookie name from browser
* @param {string} cookieName - name of the cookie
* @returns {string} cookie value of the found cookie name
*/
function getCookie(cookieName) {
var name = cookieName + '=';
var decodedCookie = decodeURIComponent(document.cookie);
var cookieArray = decodedCookie.split(';');
for (var i = 0; i < cookieArray.length; i++) {
var cookieItem = cookieArray[i];
while (cookieItem.charAt(0) === ' ') {
cookieItem = cookieItem.substring(1);
}
if (cookieItem.indexOf(name) === 0) {
return cookieItem.substring(name.length, cookieItem.length);
}
}
return '';
}
module.exports = function () {
if ($('.valid-cookie-warning').length > 0) {
var previousSessionID = window.localStorage.getItem('previousSid');
var currentSessionID = getCookie('sid');
if (!previousSessionID && currentSessionID) {
// When a user first time visit the home page,
// set the previousSessionID to currentSessionID
// and Show the cookie alert
previousSessionID = currentSessionID;
window.localStorage.setItem('previousSid', previousSessionID);
$('.cookie-warning-messaging').show();
} else if (previousSessionID && previousSessionID === currentSessionID) {
// Hide the cookie alert if user is in the same session
$('.cookie-warning-messaging').hide();
} else {
// Clear the previousSessionID from localStorage
// when user session is changed or expired
window.localStorage.removeItem('previousSid');
}
}
};

View File

@@ -0,0 +1,72 @@
'use strict';
var keyboardAccessibility = require('./keyboardAccessibility');
module.exports = function () {
$('.country-selector a').click(function (e) {
e.preventDefault();
var action = $('.page').data('action');
var localeCode = $(this).data('locale');
var localeCurrencyCode = $(this).data('currencycode');
var queryString = $('.page').data('querystring');
var url = $('.country-selector').data('url');
$.ajax({
url: url,
type: 'get',
dataType: 'json',
data: {
code: localeCode,
queryString: queryString,
CurrencyCode: localeCurrencyCode,
action: action
},
success: function (response) {
$.spinner().stop();
if (response && response.redirectUrl) {
window.location.href = response.redirectUrl;
}
},
error: function () {
$.spinner().stop();
}
});
});
keyboardAccessibility('.navbar-header .country-selector',
{
40: function ($countryOptions) { // down
if ($(this).is(':focus')) {
$countryOptions.first().focus();
} else {
$(':focus').next().focus();
}
},
38: function ($countryOptions) { // up
if ($countryOptions.first().is(':focus') || $(this).is(':focus')) {
$(this).focus();
$(this).removeClass('show');
} else {
$(':focus').prev().focus();
}
},
27: function () { // escape
$(this).focus();
$(this).removeClass('show').children('.dropdown-menu').removeClass('show');
},
9: function () { // tab
$(this).removeClass('show').children('.dropdown-menu').removeClass('show');
}
},
function () {
if (!($(this).hasClass('show'))) {
$(this).addClass('show');
}
return $(this).find('.dropdown-country-selector').children('a');
}
);
$('.navbar-header .country-selector').on('focusin', function () {
$(this).addClass('show').children('.dropdown-menu').addClass('show');
});
};

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = function (element, message) {
var errorHtml = '<div class="alert alert-danger alert-dismissible ' +
'fade show" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' + message + '</div>';
$(element).append(errorHtml);
};

View File

@@ -0,0 +1,35 @@
'use strict';
module.exports = {
setTabNextFocus: function (focusParams) {
var KEYCODE_TAB = 9;
var isTabPressed = (focusParams.event.key === 'Tab' || focusParams.event.keyCode === KEYCODE_TAB);
if (!isTabPressed) {
return;
}
var firstFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.firstElementSelector);
var lastFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.lastElementSelector);
if ($(focusParams.containerSelector + ' ' + focusParams.lastElementSelector).is(':disabled')) {
lastFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.nextToLastElementSelector);
if ($('.product-quickview.product-set').length > 0) {
var linkElements = $(focusParams.containerSelector + ' a#fa-link.share-icons');
lastFocusableEl = linkElements[linkElements.length - 1];
}
}
if (focusParams.event.shiftKey) /* shift + tab */ {
if ($(':focus').is(firstFocusableEl)) {
lastFocusableEl.focus();
focusParams.event.preventDefault();
}
} else /* tab */ {
if ($(':focus').is(lastFocusableEl)) { // eslint-disable-line
firstFocusableEl.focus();
focusParams.event.preventDefault();
}
}
}
};

View File

@@ -0,0 +1,60 @@
'use strict';
var scrollAnimate = require('./scrollAnimate');
/**
* appends params to a url
* @param {string} data - data returned from the server's ajax call
* @param {Object} button - button that was clicked for email sign-up
*/
function displayMessage(data, button) {
$.spinner().stop();
var status;
if (data.success) {
status = 'alert-success';
} else {
status = 'alert-danger';
}
if ($('.email-signup-message').length === 0) {
$('body').append(
'<div class="email-signup-message"></div>'
);
}
$('.email-signup-message')
.append('<div class="email-signup-alert text-center ' + status + '">' + data.msg + '</div>');
setTimeout(function () {
$('.email-signup-message').remove();
button.removeAttr('disabled');
}, 3000);
}
module.exports = function () {
$('.back-to-top').click(function () {
scrollAnimate();
});
$('.subscribe-email').on('click', function (e) {
e.preventDefault();
var url = $(this).data('href');
var button = $(this);
var emailId = $('input[name=hpEmailSignUp]').val();
$.spinner().start();
$(this).attr('disabled', true);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: {
emailId: emailId
},
success: function (data) {
displayMessage(data, button);
},
error: function (err) {
displayMessage(err, button);
}
});
});
};

View File

@@ -0,0 +1,43 @@
'use strict';
/**
* Remove all validation. Should be called every time before revalidating form
* @param {element} form - Form to be cleared
* @returns {void}
*/
function clearFormErrors(form) {
$(form).find('.form-control.is-invalid').removeClass('is-invalid');
}
module.exports = function (formElement, payload) {
// clear form validation first
clearFormErrors(formElement);
$('.alert', formElement).remove();
if (typeof payload === 'object' && payload.fields) {
Object.keys(payload.fields).forEach(function (key) {
if (payload.fields[key]) {
var feedbackElement = $(formElement).find('[name="' + key + '"]')
.parent()
.children('.invalid-feedback');
if (feedbackElement.length > 0) {
if (Array.isArray(payload[key])) {
feedbackElement.html(payload.fields[key].join('<br/>'));
} else {
feedbackElement.html(payload.fields[key]);
}
feedbackElement.siblings('.form-control').addClass('is-invalid');
}
}
});
}
if (payload && payload.error) {
var form = $(formElement).prop('tagName') === 'FORM'
? $(formElement)
: $(formElement).parents('form');
form.prepend('<div class="alert alert-danger" role="alert">'
+ payload.error.join('<br/>') + '</div>');
}
};

View File

@@ -0,0 +1,15 @@
'use strict';
module.exports = function (selector, keyFunctions, preFunction) {
$(selector).on('keydown', function (e) {
var key = e.which;
var supportedKeyCodes = [37, 38, 39, 40, 27];
if (supportedKeyCodes.indexOf(key) >= 0) {
e.preventDefault();
}
var returnedScope = preFunction.call(this);
if (keyFunctions[key]) {
keyFunctions[key].call(this, returnedScope);
}
});
};

View File

@@ -0,0 +1,252 @@
'use strict';
var keyboardAccessibility = require('./keyboardAccessibility');
var clearSelection = function (element) {
$(element).closest('.dropdown').children('.dropdown-menu').children('.top-category')
.detach();
$(element).closest('.dropdown.show').children('.nav-link').attr('aria-expanded', 'false');
$(element).closest('.dropdown.show').children('.dropdown-menu').attr('aria-hidden', 'true');
$(element).closest('.dropdown.show').removeClass('show');
$('div.menu-group > ul.nav.navbar-nav > li.nav-item > a').attr('aria-hidden', 'false');
$(element).closest('li').detach();
};
module.exports = function () {
var isDesktop = function (element) {
return $(element).parents('.menu-toggleable-left').css('position') !== 'fixed';
};
var headerBannerStatus = window.sessionStorage.getItem('hide_header_banner');
$('.header-banner .close').on('click', function () {
$('.header-banner').addClass('d-none');
window.sessionStorage.setItem('hide_header_banner', '1');
});
if (!headerBannerStatus || headerBannerStatus < 0) {
$('.header-banner').removeClass('d-none');
}
keyboardAccessibility('.main-menu .nav-link, .main-menu .dropdown-link',
{
40: function (menuItem) { // down
if (menuItem.hasClass('nav-item')) { // top level
$('.navbar-nav .show').removeClass('show')
.children('.dropdown-menu')
.removeClass('show');
menuItem.addClass('show').children('.dropdown-menu').addClass('show');
menuItem.find('ul > li > a')
.first()
.focus();
} else {
menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');
if (!(menuItem.next().length > 0)) { // if this is the last menuItem
menuItem.parent().parent().find('li > a') // set focus to the first menuitem
.first()
.focus();
} else {
menuItem.next().children().first().focus();
}
}
},
39: function (menuItem) { // right
if (menuItem.hasClass('nav-item')) { // top level
menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');
$(this).attr('aria-expanded', 'false');
menuItem.next().children().first().focus();
} else if (menuItem.hasClass('dropdown')) {
menuItem.addClass('show').children('.dropdown-menu').addClass('show');
$(this).attr('aria-expanded', 'true');
menuItem.find('ul > li > a')
.first()
.focus();
}
},
38: function (menuItem) { // up
if (menuItem.hasClass('nav-item')) { // top level
menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');
} else if (menuItem.prev().length === 0) { // first menuItem
menuItem.parent().parent().removeClass('show')
.children('.nav-link')
.attr('aria-expanded', 'false');
menuItem.parent().children().last().children() // set the focus to the last menuItem
.first()
.focus();
} else {
menuItem.prev().children().first().focus();
}
},
37: function (menuItem) { // left
if (menuItem.hasClass('nav-item')) { // top level
menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');
$(this).attr('aria-expanded', 'false');
menuItem.prev().children().first().focus();
} else {
menuItem.closest('.show').removeClass('show')
.closest('li.show').removeClass('show')
.children()
.first()
.focus()
.attr('aria-expanded', 'false');
}
},
27: function (menuItem) { // escape
var parentMenu = menuItem.hasClass('show')
? menuItem
: menuItem.closest('li.show');
parentMenu.children('.show').removeClass('show');
parentMenu.removeClass('show').children('.nav-link')
.attr('aria-expanded', 'false');
parentMenu.children().first().focus();
}
},
function () {
return $(this).parent();
}
);
$('.dropdown:not(.disabled) [data-toggle="dropdown"]')
.on('click', function (e) {
if (!isDesktop(this)) {
$('.modal-background').show();
// copy parent element into current UL
var li = $('<li class="dropdown-item top-category" role="button"></li>');
var link = $(this).clone().removeClass('dropdown-toggle')
.removeAttr('data-toggle')
.removeAttr('aria-expanded')
.attr('aria-haspopup', 'false');
li.append(link);
var closeMenu = $('<li class="nav-menu"></li>');
closeMenu.append($('.close-menu').first().clone());
$(this).parent().children('.dropdown-menu')
.prepend(li)
.prepend(closeMenu)
.attr('aria-hidden', 'false');
// copy navigation menu into view
$(this).parent().addClass('show');
$(this).attr('aria-expanded', 'true');
$(link).focus();
$('div.menu-group > ul.nav.navbar-nav > li.nav-item > a').attr('aria-hidden', 'true');
e.preventDefault();
}
})
.on('mouseenter', function () {
if (isDesktop(this)) {
var eventElement = this;
$('.navbar-nav > li').each(function () {
if (!$.contains(this, eventElement)) {
$(this).find('.show').each(function () {
clearSelection(this);
});
if ($(this).hasClass('show')) {
$(this).removeClass('show');
$(this).children('ul.dropdown-menu').removeClass('show');
$(this).children('.nav-link').attr('aria-expanded', 'false');
}
}
});
// need to close all the dropdowns that are not direct parent of current dropdown
$(this).parent().addClass('show');
$(this).siblings('.dropdown-menu').addClass('show');
$(this).attr('aria-expanded', 'true');
}
})
.parent()
.on('mouseleave', function () {
if (isDesktop(this)) {
$(this).removeClass('show');
$(this).children('.dropdown-menu').removeClass('show');
$(this).children('.nav-link').attr('aria-expanded', 'false');
}
});
$('.navbar>.close-menu>.close-button').on('click', function (e) {
e.preventDefault();
$('.menu-toggleable-left').removeClass('in');
$('.modal-background').hide();
$('.navbar-toggler').focus();
$('.main-menu').attr('aria-hidden', 'true');
$('.main-menu').siblings().attr('aria-hidden', 'false');
$('header').siblings().attr('aria-hidden', 'false');
});
$('.navbar-nav').on('click', '.back', function (e) {
e.preventDefault();
clearSelection(this);
});
$('.navbar-nav').on('click', '.close-button', function (e) {
e.preventDefault();
$('.navbar-nav').find('.top-category').detach();
$('.navbar-nav').find('.nav-menu').detach();
$('.navbar-nav').find('.show').removeClass('show');
$('.menu-toggleable-left').removeClass('in');
$('.main-menu').siblings().attr('aria-hidden', 'false');
$('header').siblings().attr('aria-hidden', 'false');
$('.modal-background').hide();
});
$('.navbar-toggler').click(function (e) {
e.preventDefault();
$('.main-menu').toggleClass('in');
$('.modal-background').show();
$('.main-menu').removeClass('d-none');
$('.main-menu').attr('aria-hidden', 'false');
$('.main-menu').siblings().attr('aria-hidden', 'true');
$('header').siblings().attr('aria-hidden', 'true');
$('.main-menu .nav.navbar-nav .nav-link').first().focus();
});
keyboardAccessibility('.navbar-header .user',
{
40: function ($popover) { // down
if ($popover.children('a').first().is(':focus')) {
$popover.next().children().first().focus();
} else {
$popover.children('a').first().focus();
}
},
38: function ($popover) { // up
if ($popover.children('a').first().is(':focus')) {
$(this).focus();
$popover.removeClass('show');
} else {
$popover.children('a').first().focus();
}
},
27: function () { // escape
$('.navbar-header .user .popover').removeClass('show');
$('.user').attr('aria-expanded', 'false');
},
9: function () { // tab
$('.navbar-header .user .popover').removeClass('show');
$('.user').attr('aria-expanded', 'false');
}
},
function () {
var $popover = $('.user .popover li.nav-item');
return $popover;
}
);
$('.navbar-header .user').on('mouseenter focusin', function () {
if ($('.navbar-header .user .popover').length > 0) {
$('.navbar-header .user .popover').addClass('show');
$('.user').attr('aria-expanded', 'true');
}
});
$('.navbar-header .user').on('mouseleave', function () {
$('.navbar-header .user .popover').removeClass('show');
$('.user').attr('aria-expanded', 'false');
});
$('body').on('click', '#myaccount', function () {
event.preventDefault();
});
};

View File

@@ -0,0 +1,68 @@
'use strict';
var cart = require('../cart/cart');
var updateMiniCart = true;
module.exports = function () {
cart();
$('.minicart').on('count:update', function (event, count) {
if (count && $.isNumeric(count.quantityTotal)) {
$('.minicart .minicart-quantity').text(count.quantityTotal);
$('.minicart .minicart-link').attr({
'aria-label': count.minicartCountOfItems,
title: count.minicartCountOfItems
});
}
});
$('.minicart').on('mouseenter focusin touchstart', function () {
if ($('.search:visible').length === 0) {
return;
}
var url = $('.minicart').data('action-url');
var count = parseInt($('.minicart .minicart-quantity').text(), 10);
if (count !== 0 && $('.minicart .popover.show').length === 0) {
if (!updateMiniCart) {
$('.minicart .popover').addClass('show');
return;
}
$('.minicart .popover').addClass('show');
$('.minicart .popover').spinner().start();
$.get(url, function (data) {
$('.minicart .popover').empty();
$('.minicart .popover').append(data);
updateMiniCart = false;
$.spinner().stop();
});
}
});
$('body').on('touchstart click', function (e) {
if ($('.minicart').has(e.target).length <= 0) {
$('.minicart .popover').removeClass('show');
}
});
$('.minicart').on('mouseleave focusout', function (event) {
if ((event.type === 'focusout' && $('.minicart').has(event.target).length > 0)
|| (event.type === 'mouseleave' && $(event.target).is('.minicart .quantity'))
|| $('body').hasClass('modal-open')) {
event.stopPropagation();
return;
}
$('.minicart .popover').removeClass('show');
});
$('body').on('change', '.minicart .quantity', function () {
if ($(this).parents('.bonus-product-line-item').length && $('.cart-page').length) {
location.reload();
}
});
$('body').on('product:afterAddToCart', function () {
updateMiniCart = true;
});
$('body').on('cart:update', function () {
updateMiniCart = true;
});
};

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = function (element) {
var position = element && element.length ? element.offset().top : 0;
$('html, body').animate({
scrollTop: position
}, 500);
if (!element) {
$('.logo-home').focus();
}
};

View File

@@ -0,0 +1,269 @@
'use strict';
var debounce = require('lodash/debounce');
var endpoint = $('.suggestions-wrapper').data('url');
var minChars = 1;
var UP_KEY = 38;
var DOWN_KEY = 40;
var DIRECTION_DOWN = 1;
var DIRECTION_UP = -1;
/**
* Retrieves Suggestions element relative to scope
*
* @param {Object} scope - Search input field DOM element
* @return {JQuery} - .suggestions-wrapper element
*/
function getSuggestionsWrapper(scope) {
return $(scope).siblings('.suggestions-wrapper');
}
/**
* Determines whether DOM element is inside the .search-mobile class
*
* @param {Object} scope - DOM element, usually the input.search-field element
* @return {boolean} - Whether DOM element is inside div.search-mobile
*/
function isMobileSearch(scope) {
return !!$(scope).closest('.search-mobile').length;
}
/**
* Remove modal classes needed for mobile suggestions
*
*/
function clearModals() {
$('body').removeClass('modal-open');
$('header').siblings().attr('aria-hidden', 'false');
$('.suggestions').removeClass('modal');
}
/**
* Apply modal classes needed for mobile suggestions
*
* @param {Object} scope - Search input field DOM element
*/
function applyModals(scope) {
if (isMobileSearch(scope)) {
$('body').addClass('modal-open');
$('header').siblings().attr('aria-hidden', 'true');
getSuggestionsWrapper(scope).find('.suggestions').addClass('modal');
}
}
/**
* Tear down Suggestions panel
*/
function tearDownSuggestions() {
$('input.search-field').val('');
clearModals();
$('.search-mobile .suggestions').unbind('scroll');
$('.suggestions-wrapper').empty();
}
/**
* Toggle search field icon from search to close and vice-versa
*
* @param {string} action - Action to toggle to
*/
function toggleSuggestionsIcon(action) {
var mobileSearchIcon = '.search-mobile button.';
var iconSearch = 'fa-search';
var iconSearchClose = 'fa-close';
if (action === 'close') {
$(mobileSearchIcon + iconSearch).removeClass(iconSearch).addClass(iconSearchClose).attr('type', 'button');
} else {
$(mobileSearchIcon + iconSearchClose).removeClass(iconSearchClose).addClass(iconSearch).attr('type', 'submit');
}
}
/**
* Determines whether the "More Content Below" icon should be displayed
*
* @param {Object} scope - DOM element, usually the input.search-field element
*/
function handleMoreContentBelowIcon(scope) {
if (($(scope).scrollTop() + $(scope).innerHeight()) >= $(scope)[0].scrollHeight) {
$('.more-below').fadeOut();
} else {
$('.more-below').fadeIn();
}
}
/**
* Positions Suggestions panel on page
*
* @param {Object} scope - DOM element, usually the input.search-field element
*/
function positionSuggestions(scope) {
var outerHeight;
var $scope;
var $suggestions;
var top;
if (isMobileSearch(scope)) {
$scope = $(scope);
top = $scope.offset().top;
outerHeight = $scope.outerHeight();
$suggestions = getSuggestionsWrapper(scope).find('.suggestions');
$suggestions.css('top', top + outerHeight);
handleMoreContentBelowIcon(scope);
// Unfortunately, we have to bind this dynamically, as the live scroll event was not
// properly detecting dynamic suggestions element's scroll event
$suggestions.scroll(function () {
handleMoreContentBelowIcon(this);
});
}
}
/**
* Process Ajax response for SearchServices-GetSuggestions
*
* @param {Object|string} response - Empty object literal if null response or string with rendered
* suggestions template contents
*/
function processResponse(response) {
var $suggestionsWrapper = getSuggestionsWrapper(this).empty();
$.spinner().stop();
if (typeof (response) !== 'object') {
$suggestionsWrapper.append(response).show();
$(this).siblings('.reset-button').addClass('d-sm-block');
positionSuggestions(this);
if (isMobileSearch(this)) {
toggleSuggestionsIcon('close');
applyModals(this);
}
// Trigger screen reader by setting aria-describedby with the new suggestion message.
var suggestionsList = $('.suggestions .item');
if ($(suggestionsList).length) {
$('input.search-field').attr('aria-describedby', 'search-result-count');
} else {
$('input.search-field').removeAttr('aria-describedby');
}
} else {
$suggestionsWrapper.hide();
}
}
/**
* Retrieve suggestions
*
* @param {Object} scope - Search field DOM element
*/
function getSuggestions(scope) {
if ($(scope).val().length >= minChars) {
$.spinner().start();
$.ajax({
context: scope,
url: endpoint + encodeURIComponent($(scope).val()),
method: 'GET',
success: processResponse,
error: function () {
$.spinner().stop();
}
});
} else {
toggleSuggestionsIcon('search');
$(scope).siblings('.reset-button').removeClass('d-sm-block');
clearModals();
getSuggestionsWrapper(scope).empty();
}
}
/**
* Handle Search Suggestion Keyboard Arrow Keys
*
* @param {Integer} direction takes positive or negative number constant, DIRECTION_UP (-1) or DIRECTION_DOWN (+1)
*/
function handleArrow(direction) {
// get all li elements in the suggestions list
var suggestionsList = $('.suggestions .item');
if (suggestionsList.filter('.selected').length === 0) {
suggestionsList.first().addClass('selected');
$('input.search-field').each(function () {
$(this).attr('aria-activedescendant', suggestionsList.first()[0].id);
});
} else {
suggestionsList.each(function (index) {
var idx = index + direction;
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
$(this).removeAttr('aria-selected');
if (suggestionsList.eq(idx).length !== 0) {
suggestionsList.eq(idx).addClass('selected');
suggestionsList.eq(idx).attr('aria-selected', true);
$(this).removeProp('aria-selected');
$('input.search-field').each(function () {
$(this).attr('aria-activedescendant', suggestionsList.eq(idx)[0].id);
});
} else {
suggestionsList.first().addClass('selected');
suggestionsList.first().attr('aria-selected', true);
$('input.search-field').each(function () {
$(this).attr('aria-activedescendant', suggestionsList.first()[0].id);
});
}
return false;
}
return true;
});
}
}
module.exports = function () {
$('form[name="simpleSearch"]').submit(function (e) {
var suggestionsList = $('.suggestions .item');
if (suggestionsList.filter('.selected').length !== 0) {
e.preventDefault();
suggestionsList.filter('.selected').find('a')[0].click();
}
});
$('input.search-field').each(function () {
/**
* Use debounce to avoid making an Ajax call on every single key press by waiting a few
* hundred milliseconds before making the request. Without debounce, the user sees the
* browser blink with every key press.
*/
var debounceSuggestions = debounce(getSuggestions, 300);
$(this).on('keyup focus', function (e) {
// Capture Down/Up Arrow Key Events
switch (e.which) {
case DOWN_KEY:
handleArrow(DIRECTION_DOWN);
e.preventDefault(); // prevent moving the cursor
break;
case UP_KEY:
handleArrow(DIRECTION_UP);
e.preventDefault(); // prevent moving the cursor
break;
default:
debounceSuggestions(this, e);
}
});
});
$('body').on('click', function (e) {
if (!$('.suggestions').has(e.target).length && !$(e.target).hasClass('search-field')) {
$('.suggestions').hide();
}
});
$('body').on('click touchend', '.search-mobile button.fa-close', function (e) {
e.preventDefault();
$('.suggestions').hide();
toggleSuggestionsIcon('search');
tearDownSuggestions();
});
$('.site-search .reset-button').on('click', function () {
$(this).removeClass('d-sm-block');
});
};

View File

@@ -0,0 +1,75 @@
'use strict';
/**
* Show a spinner inside a given element
* @param {element} $target - Element to block by the veil and spinner.
* Pass body to block the whole page.
*/
function addSpinner($target) {
var $veil = $('<div class="veil"><div class="underlay"></div></div>');
$veil.append('<div class="spinner"><div class="dot1"></div><div class="dot2"></div></div>');
if ($target.get(0).tagName === 'IMG') {
$target.after($veil);
$veil.css({ width: $target.width(), height: $target.height() });
if ($target.parent().css('position') === 'static') {
$target.parent().css('position', 'relative');
}
} else {
$target.append($veil);
if ($target.css('position') === 'static') {
$target.parent().css('position', 'relative');
$target.parent().addClass('veiled');
}
if ($target.get(0).tagName === 'BODY') {
$veil.find('.spinner').css('position', 'fixed');
}
}
$veil.click(function (e) {
e.stopPropagation();
});
}
/**
* Remove existing spinner
* @param {element} $veil - jQuery pointer to the veil element
*/
function removeSpinner($veil) {
if ($veil.parent().hasClass('veiled')) {
$veil.parent().css('position', '');
$veil.parent().removeClass('veiled');
}
$veil.off('click');
$veil.remove();
}
// element level spinner:
$.fn.spinner = function () {
var $element = $(this);
var Fn = function () {
this.start = function () {
if ($element.length) {
addSpinner($element);
}
};
this.stop = function () {
if ($element.length) {
var $veil = $('.veil');
removeSpinner($veil);
}
};
};
return new Fn();
};
// page-level spinner:
$.spinner = function () {
var Fn = function () {
this.start = function () {
addSpinner($('body'));
};
this.stop = function () {
removeSpinner($('.veil'));
};
};
return new Fn();
};

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = function () {
$('.info-icon').on('mouseenter focusin', function () {
$(this).find('.tooltip').removeClass('d-none');
});
$('.info-icon').on('mouseleave focusout', function () {
$(this).find('.tooltip').addClass('d-none');
});
};

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./contactUs/contactUs'));
});

View File

@@ -0,0 +1,58 @@
'use strict';
/**
* Display the returned message.
* @param {string} data - data returned from the server's ajax call
* @param {Object} button - button that was clicked for contact us sign-up
*/
function displayMessage(data, button) {
$.spinner().stop();
var status;
if (data.success) {
status = 'alert-success';
} else {
status = 'alert-danger';
}
if ($('.contact-us-signup-message').length === 0) {
$('body').append(
'<div class="contact-us-signup-message"></div>'
);
}
$('.contact-us-signup-message')
.append('<div class="contact-us-signup-alert text-center ' + status + '" role="alert">' + data.msg + '</div>');
setTimeout(function () {
$('.contact-us-signup-message').remove();
button.removeAttr('disabled');
}, 3000);
}
module.exports = {
subscribeContact: function () {
$('form.contact-us').submit(function (e) {
e.preventDefault();
var form = $(this);
var button = $('.subscribe-contact-us');
var url = form.attr('action');
$.spinner().start();
button.attr('disabled', true);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: form.serialize(),
success: function (data) {
displayMessage(data, button);
if (data.success) {
$('.contact-us').trigger('reset');
}
},
error: function (err) {
displayMessage(err, button);
}
});
});
}
};

View File

@@ -0,0 +1,170 @@
'use strict';
/**
* Validates and Return the cquotient namespace provided by the commerce cloud platform
* @returns {Object} - einsteinUtils or null
*/
function getEinsteinUtils() {
var einsteinUtils = window.CQuotient;
if (einsteinUtils && (typeof einsteinUtils.getCQUserId === 'function') && (typeof einsteinUtils.getCQCookieId === 'function')) {
return einsteinUtils;
}
return null;
}
/**
* Renders the einstein response into a given dom element
* @param {jQuery} $parentElement parent element where recommendations will show.
*/
function showControls($parentElement) {
var $liTemplate = $parentElement.find('.hidden-indicators-template li');
var $carouselItems = $parentElement.find('.carousel-item');
$carouselItems.each(function (index) {
var $newIndiator = $liTemplate.clone();
if (index === 0) {
$parentElement.find('.pd-carousel-indicators').append($newIndiator);
} else {
$newIndiator.removeClass('active');
$parentElement.find('.pd-carousel-indicators').append($newIndiator);
}
$parentElement.find('.pd-carousel-indicators li').last().attr('data-position', index);
$parentElement.removeClass('hide-indicators');
});
}
/**
* fills in the carousel with product tile html objects
* @param {string} einsteinResponse string html for product tiles
* @param {jQuery} $parentElement parent element where recommendations will show.
*/
function fillDomElement(einsteinResponse, $parentElement) {
var recommender = $parentElement.data('recommender');
var recommendedProducts = einsteinResponse[recommender].recs;
if (recommendedProducts && recommendedProducts.length > 0) {
var template = $parentElement.data('template');
var swatches = $parentElement.data('swatches');
var displayRatings = $parentElement.data('displayratings');
var components = [];
components = recommendedProducts.map(function (recommendedProduct) {
var tiledefinition = {};
tiledefinition.classxs = $parentElement.data('bsxs');
tiledefinition.classsm = $parentElement.data('bssm');
tiledefinition.classmd = $parentElement.data('bsmd');
tiledefinition.template = template;
tiledefinition.swatches = swatches;
tiledefinition.displayratings = displayRatings;
tiledefinition.model = {
type: 'product',
id: recommendedProduct.id
};
return tiledefinition;
});
var url = new URL($parentElement.data('product-load-url'));
url.searchParams.append('components', JSON.stringify(components));
url.searchParams.append('limit', $parentElement.data('limit'));
url.searchParams.append('recommender', recommender);
$.ajax({
url: url.href,
type: 'get',
dataType: 'html',
success: function (html) {
$parentElement.find('.carousel-inner').html(html);
showControls($parentElement);
$('body').trigger('carousel:setup', {});
},
error: function () {
$parentElement.spinner().stop();
}
});
}
}
/**
* Processes a recommendation tile, with an already initialized category specific anchors array
* @param {jQuery} $parentElement parent element where recommendations will show.
* @param {Object} einsteinUtils cquotient object
* @param {Array} anchorsArray array of objects representing anchors
*/
function processRecommendationsTile($parentElement, einsteinUtils, anchorsArray) {
var recommender = $parentElement.data('recommender');
var params = {
userId: einsteinUtils.getCQUserId(),
cookieId: einsteinUtils.getCQCookieId(),
ccver: '1.01'
};
if (anchorsArray) {
params.anchors = anchorsArray;
}
/**
* Processes a recommendation responses
* @param {Object} einsteinResponse cquotient object
*/
function recommendationsReceived(einsteinResponse) {
fillDomElement(einsteinResponse, $parentElement);
$parentElement.spinner().stop();
}
if (einsteinUtils.getRecs) {
einsteinUtils.getRecs(einsteinUtils.clientId, recommender, params, recommendationsReceived);
} else {
einsteinUtils.widgets = einsteinUtils.widgets || []; // eslint-disable-line no-param-reassign
einsteinUtils.widgets.push({
recommenderName: recommender,
parameters: params,
callback: recommendationsReceived
});
}
}
/**
* Processes a recommendation tile, with an already initialized product specific anchors array
* @param {jQuery} $parentElement parent element where recommendations will show.
* @returns {Array} - containing an anchor object
*/
function createProductAnchor($parentElement) {
return [{
id: $parentElement.data('primaryProductId'),
sku: $parentElement.data('secondaryProductId'),
type: $parentElement.data('alternativeGroupType'),
alt_id: $parentElement.data('alternativeGroupId')
}];
}
/**
* Rerieves data attributes from parent element and converts to gretel compatible recommenders array
* @param {jQuery} $parentElement parent element where recommendations will show.
* @returns {Array} - containing an anchor object
*/
function createCategoryAnchor($parentElement) {
return [{ id: $parentElement.data('categoryId') }];
}
/**
* Gets all placeholder elements, which hold einstein recommendations queries the details from the
* einstein engine and feeds them back to the dom element
*/
function loadRecommendations() {
var einsteinUtils = getEinsteinUtils();
if (einsteinUtils) {
var $recommendationTiles = $('.einstein-carousel');
$recommendationTiles.each(function () {
var $parentElement = $(this);
$parentElement.spinner().start();
if ($(this).closest('.experience-einstein-einsteinCarouselProduct').length) {
return processRecommendationsTile($parentElement, einsteinUtils, createProductAnchor($parentElement));
} else if ($(this).closest('.experience-einstein-einsteinCarouselCategory').length) {
return processRecommendationsTile($parentElement, einsteinUtils, createCategoryAnchor($parentElement));
}
return processRecommendationsTile($parentElement, einsteinUtils);
});
}
}
$(document).ready(function () {
loadRecommendations();
});

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./login/login'));
});

View File

@@ -0,0 +1,125 @@
'use strict';
var formValidation = require('../components/formValidation');
var createErrorNotification = require('../components/errorNotification');
module.exports = {
login: function () {
$('form.login').submit(function (e) {
var form = $(this);
e.preventDefault();
var url = form.attr('action');
form.spinner().start();
$('form.login').trigger('login:submit', e);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: form.serialize(),
success: function (data) {
form.spinner().stop();
if (!data.success) {
formValidation(form, data);
$('form.login').trigger('login:error', data);
} else {
$('form.login').trigger('login:success', data);
location.href = data.redirectUrl;
}
},
error: function (data) {
if (data.responseJSON.redirectUrl) {
window.location.href = data.responseJSON.redirectUrl;
} else {
$('form.login').trigger('login:error', data);
form.spinner().stop();
}
}
});
return false;
});
},
register: function () {
$('form.registration').submit(function (e) {
var form = $(this);
e.preventDefault();
var url = form.attr('action');
form.spinner().start();
$('form.registration').trigger('login:register', e);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: form.serialize(),
success: function (data) {
form.spinner().stop();
if (!data.success) {
$('form.registration').trigger('login:register:error', data);
formValidation(form, data);
} else {
$('form.registration').trigger('login:register:success', data);
location.href = data.redirectUrl;
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
} else {
createErrorNotification($('.error-messaging'), err.responseJSON.errorMessage);
}
form.spinner().stop();
}
});
return false;
});
},
resetPassword: function () {
$('.reset-password-form').submit(function (e) {
var form = $(this);
e.preventDefault();
var url = form.attr('action');
form.spinner().start();
$('.reset-password-form').trigger('login:register', e);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: form.serialize(),
success: function (data) {
form.spinner().stop();
if (!data.success) {
formValidation(form, data);
} else {
$('.request-password-title').text(data.receivedMsgHeading);
$('.request-password-body').empty()
.append('<p>' + data.receivedMsgBody + '</p>');
if (!data.mobile) {
$('#submitEmailButton').text(data.buttonText)
.attr('data-dismiss', 'modal');
} else {
$('.send-email-btn').empty()
.html('<a href="'
+ data.returnUrl
+ '" class="btn btn-primary btn-block">'
+ data.buttonText + '</a>'
);
}
}
},
error: function () {
form.spinner().stop();
}
});
return false;
});
},
clearResetForm: function () {
$('#login .modal').on('hidden.bs.modal', function () {
$('#reset-password-email').val('');
$('.modal-dialog .form-control.is-invalid').removeClass('is-invalid');
});
}
};

View File

@@ -0,0 +1,18 @@
window.jQuery = window.$ = require('jquery');
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./components/menu'));
processInclude(require('./components/cookie'));
processInclude(require('./components/consentTracking'));
processInclude(require('./components/footer'));
processInclude(require('./components/miniCart'));
processInclude(require('./components/collapsibleItem'));
processInclude(require('./components/search'));
processInclude(require('./components/clientSideValidation'));
processInclude(require('./components/countrySelector'));
processInclude(require('./components/toolTip'));
});
require('./thirdParty/bootstrap');
require('./components/spinner');

View File

@@ -0,0 +1,13 @@
'use strict';
$(document).ready(function () {
$('body').on('click', '.show-more-button', function (e) {
e.preventDefault();
var $set2Element = $(this).closest('.look-book-layout').find('.look-book-set2');
$set2Element.removeClass('hide-set');
var $showMoreElement = $(this).closest('.look-book-layout').find('.show-more');
$showMoreElement.addClass('d-none');
});
});

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./orderHistory/orderHistory'));
});

View File

@@ -0,0 +1,24 @@
'use strict';
module.exports = function () {
$('body').on('change', '.order-history-select', function (e) {
var $ordersContainer = $('.order-list-container');
$ordersContainer.empty();
$.spinner().start();
$('.order-history-select').trigger('orderHistory:sort', e);
$.ajax({
url: e.currentTarget.value,
method: 'GET',
success: function (data) {
$ordersContainer.html(data);
$.spinner().stop();
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
$.spinner().stop();
}
});
});
};

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./paymentInstruments/paymentInstruments'));
});

View File

@@ -0,0 +1,81 @@
'use strict';
var formValidation = require('../components/formValidation');
var cleave = require('../components/cleave');
var url;
module.exports = {
removePayment: function () {
$('.remove-payment').on('click', function (e) {
e.preventDefault();
url = $(this).data('url') + '?UUID=' + $(this).data('id');
$('.payment-to-remove').empty().append($(this).data('card'));
$('.delete-confirmation-btn').click(function (f) {
f.preventDefault();
$('.remove-payment').trigger('payment:remove', f);
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function (data) {
$('#uuid-' + data.UUID).remove();
if (data.message) {
var toInsert = '<div class="row justify-content-center h3 no-saved-payments"><p>' +
data.message +
'</p></div>';
$('.paymentInstruments').empty().append(toInsert);
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
$.spinner().stop();
}
});
});
});
},
submitPayment: function () {
$('form.payment-form').submit(function (e) {
var $form = $(this);
e.preventDefault();
url = $form.attr('action');
$form.spinner().start();
$('form.payment-form').trigger('payment:submit', e);
var formData = cleave.serializeData($form);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: formData,
success: function (data) {
$form.spinner().stop();
if (!data.success) {
formValidation($form, data);
} else {
location.href = data.redirectUrl;
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
$form.spinner().stop();
}
});
return false;
});
},
handleCreditCardNumber: function () {
if ($('#cardNumber').length && $('#cardType').length) {
cleave.handleCreditCardNumber('#cardNumber', '#cardType');
}
}
};

View File

@@ -0,0 +1,848 @@
'use strict';
var focusHelper = require('../components/focus');
/**
* Retrieves the relevant pid value
* @param {jquery} $el - DOM container for a given add to cart button
* @return {string} - value to be used when adding product to cart
*/
function getPidValue($el) {
var pid;
if ($('#quickViewModal').hasClass('show') && !$('.product-set').length) {
pid = $($el).closest('.modal-content').find('.product-quickview').data('pid');
} else if ($('.product-set-detail').length || $('.product-set').length) {
pid = $($el).closest('.product-detail').find('.product-id').text();
} else {
pid = $('.product-detail:not(".bundle-item")').data('pid');
}
return pid;
}
/**
* Retrieve contextual quantity selector
* @param {jquery} $el - DOM container for the relevant quantity
* @return {jquery} - quantity selector DOM container
*/
function getQuantitySelector($el) {
var quantitySelected;
if ($el && $('.set-items').length) {
quantitySelected = $($el).closest('.product-detail').find('.quantity-select');
} else if ($el && $('.product-bundle').length) {
var quantitySelectedModal = $($el).closest('.modal-footer').find('.quantity-select');
var quantitySelectedPDP = $($el).closest('.bundle-footer').find('.quantity-select');
if (quantitySelectedModal.val() === undefined) {
quantitySelected = quantitySelectedPDP;
} else {
quantitySelected = quantitySelectedModal;
}
} else {
quantitySelected = $('.quantity-select');
}
return quantitySelected;
}
/**
* Retrieves the value associated with the Quantity pull-down menu
* @param {jquery} $el - DOM container for the relevant quantity
* @return {string} - value found in the quantity input
*/
function getQuantitySelected($el) {
return getQuantitySelector($el).val();
}
/**
* Process the attribute values for an attribute that has image swatches
*
* @param {Object} attr - Attribute
* @param {string} attr.id - Attribute ID
* @param {Object[]} attr.values - Array of attribute value objects
* @param {string} attr.values.value - Attribute coded value
* @param {string} attr.values.url - URL to de/select an attribute value of the product
* @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be
* selected. If there is no variant that corresponds to a specific combination of attribute
* values, an attribute may be disabled in the Product Detail Page
* @param {jQuery} $productContainer - DOM container for a given product
* @param {Object} msgs - object containing resource messages
*/
function processSwatchValues(attr, $productContainer, msgs) {
attr.values.forEach(function (attrValue) {
var $attrValue = $productContainer.find('[data-attr="' + attr.id + '"] [data-attr-value="' +
attrValue.value + '"]');
var $swatchButton = $attrValue.parent();
if (attrValue.selected) {
$attrValue.addClass('selected');
$attrValue.siblings('.selected-assistive-text').text(msgs.assistiveSelectedText);
} else {
$attrValue.removeClass('selected');
$attrValue.siblings('.selected-assistive-text').empty();
}
if (attrValue.url) {
$swatchButton.attr('data-url', attrValue.url);
} else {
$swatchButton.removeAttr('data-url');
}
// Disable if not selectable
$attrValue.removeClass('selectable unselectable');
$attrValue.addClass(attrValue.selectable ? 'selectable' : 'unselectable');
});
}
/**
* Process attribute values associated with an attribute that does not have image swatches
*
* @param {Object} attr - Attribute
* @param {string} attr.id - Attribute ID
* @param {Object[]} attr.values - Array of attribute value objects
* @param {string} attr.values.value - Attribute coded value
* @param {string} attr.values.url - URL to de/select an attribute value of the product
* @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be
* selected. If there is no variant that corresponds to a specific combination of attribute
* values, an attribute may be disabled in the Product Detail Page
* @param {jQuery} $productContainer - DOM container for a given product
*/
function processNonSwatchValues(attr, $productContainer) {
var $attr = '[data-attr="' + attr.id + '"]';
var $defaultOption = $productContainer.find($attr + ' .select-' + attr.id + ' option:first');
$defaultOption.attr('value', attr.resetUrl);
attr.values.forEach(function (attrValue) {
var $attrValue = $productContainer
.find($attr + ' [data-attr-value="' + attrValue.value + '"]');
$attrValue.attr('value', attrValue.url)
.removeAttr('disabled');
if (!attrValue.selectable) {
$attrValue.attr('disabled', true);
}
});
}
/**
* Routes the handling of attribute processing depending on whether the attribute has image
* swatches or not
*
* @param {Object} attrs - Attribute
* @param {string} attr.id - Attribute ID
* @param {jQuery} $productContainer - DOM element for a given product
* @param {Object} msgs - object containing resource messages
*/
function updateAttrs(attrs, $productContainer, msgs) {
// Currently, the only attribute type that has image swatches is Color.
var attrsWithSwatches = ['color'];
attrs.forEach(function (attr) {
if (attrsWithSwatches.indexOf(attr.id) > -1) {
processSwatchValues(attr, $productContainer, msgs);
} else {
processNonSwatchValues(attr, $productContainer);
}
});
}
/**
* Updates the availability status in the Product Detail Page
*
* @param {Object} response - Ajax response object after an
* attribute value has been [de]selected
* @param {jQuery} $productContainer - DOM element for a given product
*/
function updateAvailability(response, $productContainer) {
var availabilityValue = '';
var availabilityMessages = response.product.availability.messages;
if (!response.product.readyToOrder) {
availabilityValue = '<li><div>' + response.resources.info_selectforstock + '</div></li>';
} else {
availabilityMessages.forEach(function (message) {
availabilityValue += '<li><div>' + message + '</div></li>';
});
}
$($productContainer).trigger('product:updateAvailability', {
product: response.product,
$productContainer: $productContainer,
message: availabilityValue,
resources: response.resources
});
}
/**
* Generates html for product attributes section
*
* @param {array} attributes - list of attributes
* @return {string} - Compiled HTML
*/
function getAttributesHtml(attributes) {
if (!attributes) {
return '';
}
var html = '';
attributes.forEach(function (attributeGroup) {
if (attributeGroup.ID === 'mainAttributes') {
attributeGroup.attributes.forEach(function (attribute) {
html += '<div class="attribute-values">' + attribute.label + ': '
+ attribute.value + '</div>';
});
}
});
return html;
}
/**
* @typedef UpdatedOptionValue
* @type Object
* @property {string} id - Option value ID for look up
* @property {string} url - Updated option value selection URL
*/
/**
* @typedef OptionSelectionResponse
* @type Object
* @property {string} priceHtml - Updated price HTML code
* @property {Object} options - Updated Options
* @property {string} options.id - Option ID
* @property {UpdatedOptionValue[]} options.values - Option values
*/
/**
* Updates DOM using post-option selection Ajax response
*
* @param {OptionSelectionResponse} optionsHtml - Ajax response optionsHtml from selecting a product option
* @param {jQuery} $productContainer - DOM element for current product
*/
function updateOptions(optionsHtml, $productContainer) {
// Update options
$productContainer.find('.product-options').empty().html(optionsHtml);
}
/**
* Dynamically creates Bootstrap carousel from response containing images
* @param {Object[]} imgs - Array of large product images,along with related information
* @param {jQuery} $productContainer - DOM element for a given product
*/
function createCarousel(imgs, $productContainer) {
var carousel = $productContainer.find('.carousel');
$(carousel).carousel('dispose');
var carouselId = $(carousel).attr('id');
$(carousel).empty().append('<ol class="carousel-indicators"></ol><div class="carousel-inner" role="listbox"></div><a class="carousel-control-prev" href="#' + carouselId + '" role="button" data-slide="prev"><span class="fa icon-prev" aria-hidden="true"></span><span class="sr-only">' + $(carousel).data('prev') + '</span></a><a class="carousel-control-next" href="#' + carouselId + '" role="button" data-slide="next"><span class="fa icon-next" aria-hidden="true"></span><span class="sr-only">' + $(carousel).data('next') + '</span></a>');
for (var i = 0; i < imgs.length; i++) {
$('<div class="carousel-item"><img src="' + imgs[i].url + '" class="d-block img-fluid" alt="' + imgs[i].alt + ' image number ' + parseInt(imgs[i].index, 10) + '" title="' + imgs[i].title + '" itemprop="image" /></div>').appendTo($(carousel).find('.carousel-inner'));
$('<li data-target="#' + carouselId + '" data-slide-to="' + i + '" class=""></li>').appendTo($(carousel).find('.carousel-indicators'));
}
$($(carousel).find('.carousel-item')).first().addClass('active');
$($(carousel).find('.carousel-indicators > li')).first().addClass('active');
if (imgs.length === 1) {
$($(carousel).find('.carousel-indicators, a[class^="carousel-control-"]')).detach();
}
$(carousel).carousel();
$($(carousel).find('.carousel-indicators')).attr('aria-hidden', true);
}
/**
* Parses JSON from Ajax call made whenever an attribute value is [de]selected
* @param {Object} response - response from Ajax call
* @param {Object} response.product - Product object
* @param {string} response.product.id - Product ID
* @param {Object[]} response.product.variationAttributes - Product attributes
* @param {Object[]} response.product.images - Product images
* @param {boolean} response.product.hasRequiredAttrsSelected - Flag as to whether all required
* attributes have been selected. Used partially to
* determine whether the Add to Cart button can be enabled
* @param {jQuery} $productContainer - DOM element for a given product.
*/
function handleVariantResponse(response, $productContainer) {
var isChoiceOfBonusProducts =
$productContainer.parents('.choose-bonus-product-dialog').length > 0;
var isVaraint;
if (response.product.variationAttributes) {
updateAttrs(response.product.variationAttributes, $productContainer, response.resources);
isVaraint = response.product.productType === 'variant';
if (isChoiceOfBonusProducts && isVaraint) {
$productContainer.parent('.bonus-product-item')
.data('pid', response.product.id);
$productContainer.parent('.bonus-product-item')
.data('ready-to-order', response.product.readyToOrder);
}
}
// Update primary images
var primaryImageUrls = response.product.images.large;
createCarousel(primaryImageUrls, $productContainer);
// Update pricing
if (!isChoiceOfBonusProducts) {
var $priceSelector = $('.prices .price', $productContainer).length
? $('.prices .price', $productContainer)
: $('.prices .price');
$priceSelector.replaceWith(response.product.price.html);
}
// Update promotions
$productContainer.find('.promotions').empty().html(response.product.promotionsHtml);
updateAvailability(response, $productContainer);
if (isChoiceOfBonusProducts) {
var $selectButton = $productContainer.find('.select-bonus-product');
$selectButton.trigger('bonusproduct:updateSelectButton', {
product: response.product, $productContainer: $productContainer
});
} else {
// Enable "Add to Cart" button if all required attributes have been selected
$('button.add-to-cart, button.add-to-cart-global, button.update-cart-product-global').trigger('product:updateAddToCart', {
product: response.product, $productContainer: $productContainer
}).trigger('product:statusUpdate', response.product);
}
// Update attributes
$productContainer.find('.main-attributes').empty()
.html(getAttributesHtml(response.product.attributes));
}
/**
* @typespec UpdatedQuantity
* @type Object
* @property {boolean} selected - Whether the quantity has been selected
* @property {string} value - The number of products to purchase
* @property {string} url - Compiled URL that specifies variation attributes, product ID, options,
* etc.
*/
/**
* Updates the quantity DOM elements post Ajax call
* @param {UpdatedQuantity[]} quantities -
* @param {jQuery} $productContainer - DOM container for a given product
*/
function updateQuantities(quantities, $productContainer) {
if ($productContainer.parent('.bonus-product-item').length <= 0) {
var optionsHtml = quantities.map(function (quantity) {
var selected = quantity.selected ? ' selected ' : '';
return '<option value="' + quantity.value + '" data-url="' + quantity.url + '"' +
selected + '>' + quantity.value + '</option>';
}).join('');
getQuantitySelector($productContainer).empty().html(optionsHtml);
}
}
/**
* updates the product view when a product attribute is selected or deselected or when
* changing quantity
* @param {string} selectedValueUrl - the Url for the selected variation value
* @param {jQuery} $productContainer - DOM element for current product
*/
function attributeSelect(selectedValueUrl, $productContainer) {
if (selectedValueUrl) {
$('body').trigger('product:beforeAttributeSelect',
{ url: selectedValueUrl, container: $productContainer });
$.ajax({
url: selectedValueUrl,
method: 'GET',
success: function (data) {
handleVariantResponse(data, $productContainer);
updateOptions(data.product.optionsHtml, $productContainer);
updateQuantities(data.product.quantities, $productContainer);
$('body').trigger('product:afterAttributeSelect',
{ data: data, container: $productContainer });
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
}
}
/**
* Retrieves url to use when adding a product to the cart
*
* @return {string} - The provided URL to use when adding a product to the cart
*/
function getAddToCartUrl() {
return $('.add-to-cart-url').val();
}
/**
* Parses the html for a modal window
* @param {string} html - representing the body and footer of the modal window
*
* @return {Object} - Object with properties body and footer.
*/
function parseHtml(html) {
var $html = $('<div>').append($.parseHTML(html));
var body = $html.find('.choice-of-bonus-product');
var footer = $html.find('.modal-footer').children();
return { body: body, footer: footer };
}
/**
* Retrieves url to use when adding a product to the cart
*
* @param {Object} data - data object used to fill in dynamic portions of the html
*/
function chooseBonusProducts(data) {
$('.modal-body').spinner().start();
if ($('#chooseBonusProductModal').length !== 0) {
$('#chooseBonusProductModal').remove();
}
var bonusUrl;
if (data.bonusChoiceRuleBased) {
bonusUrl = data.showProductsUrlRuleBased;
} else {
bonusUrl = data.showProductsUrlListBased;
}
var htmlString = '<!-- Modal -->'
+ '<div class="modal fade" id="chooseBonusProductModal" tabindex="-1" role="dialog">'
+ '<span class="enter-message sr-only" ></span>'
+ '<div class="modal-dialog choose-bonus-product-dialog" '
+ 'data-total-qty="' + data.maxBonusItems + '"'
+ 'data-UUID="' + data.uuid + '"'
+ 'data-pliUUID="' + data.pliUUID + '"'
+ 'data-addToCartUrl="' + data.addToCartUrl + '"'
+ 'data-pageStart="0"'
+ 'data-pageSize="' + data.pageSize + '"'
+ 'data-moreURL="' + data.showProductsUrlRuleBased + '"'
+ 'data-bonusChoiceRuleBased="' + data.bonusChoiceRuleBased + '">'
+ '<!-- Modal content-->'
+ '<div class="modal-content">'
+ '<div class="modal-header">'
+ ' <span class="">' + data.labels.selectprods + '</span>'
+ ' <button type="button" class="close pull-right" data-dismiss="modal">'
+ ' <span aria-hidden="true">&times;</span>'
+ ' <span class="sr-only"> </span>'
+ ' </button>'
+ '</div>'
+ '<div class="modal-body"></div>'
+ '<div class="modal-footer"></div>'
+ '</div>'
+ '</div>'
+ '</div>';
$('body').append(htmlString);
$('.modal-body').spinner().start();
$.ajax({
url: bonusUrl,
method: 'GET',
dataType: 'json',
success: function (response) {
var parsedHtml = parseHtml(response.renderedTemplate);
$('#chooseBonusProductModal .modal-body').empty();
$('#chooseBonusProductModal .enter-message').text(response.enterDialogMessage);
$('#chooseBonusProductModal .modal-header .close .sr-only').text(response.closeButtonText);
$('#chooseBonusProductModal .modal-body').html(parsedHtml.body);
$('#chooseBonusProductModal .modal-footer').html(parsedHtml.footer);
$('#chooseBonusProductModal').modal('show');
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
}
/**
* Updates the Mini-Cart quantity value after the customer has pressed the "Add to Cart" button
* @param {string} response - ajax response from clicking the add to cart button
*/
function handlePostCartAdd(response) {
$('.minicart').trigger('count:update', response);
var messageType = response.error ? 'alert-danger' : 'alert-success';
// show add to cart toast
if (response.newBonusDiscountLineItem
&& Object.keys(response.newBonusDiscountLineItem).length !== 0) {
chooseBonusProducts(response.newBonusDiscountLineItem);
} else {
if ($('.add-to-cart-messages').length === 0) {
$('body').append(
'<div class="add-to-cart-messages"></div>'
);
}
$('.add-to-cart-messages').append(
'<div class="alert ' + messageType + ' add-to-basket-alert text-center" role="alert">'
+ response.message
+ '</div>'
);
setTimeout(function () {
$('.add-to-basket-alert').remove();
}, 5000);
}
}
/**
* Retrieves the bundle product item ID's for the Controller to replace bundle master product
* items with their selected variants
*
* @return {string[]} - List of selected bundle product item ID's
*/
function getChildProducts() {
var childProducts = [];
$('.bundle-item').each(function () {
childProducts.push({
pid: $(this).find('.product-id').text(),
quantity: parseInt($(this).find('label.quantity').data('quantity'), 10)
});
});
return childProducts.length ? JSON.stringify(childProducts) : [];
}
/**
* Retrieve product options
*
* @param {jQuery} $productContainer - DOM element for current product
* @return {string} - Product options and their selected values
*/
function getOptions($productContainer) {
var options = $productContainer
.find('.product-option')
.map(function () {
var $elOption = $(this).find('.options-select');
var urlValue = $elOption.val();
var selectedValueId = $elOption.find('option[value="' + urlValue + '"]')
.data('value-id');
return {
optionId: $(this).data('option-id'),
selectedValueId: selectedValueId
};
}).toArray();
return JSON.stringify(options);
}
/**
* Makes a call to the server to report the event of adding an item to the cart
*
* @param {string | boolean} url - a string representing the end point to hit so that the event can be recorded, or false
*/
function miniCartReportingUrl(url) {
if (url) {
$.ajax({
url: url,
method: 'GET',
success: function () {
// reporting urls hit on the server
},
error: function () {
// no reporting urls hit on the server
}
});
}
}
module.exports = {
attributeSelect: attributeSelect,
methods: {
editBonusProducts: function (data) {
chooseBonusProducts(data);
}
},
focusChooseBonusProductModal: function () {
$('body').on('shown.bs.modal', '#chooseBonusProductModal', function () {
$('#chooseBonusProductModal').siblings().attr('aria-hidden', 'true');
$('#chooseBonusProductModal .close').focus();
});
},
onClosingChooseBonusProductModal: function () {
$('body').on('hidden.bs.modal', '#chooseBonusProductModal', function () {
$('#chooseBonusProductModal').siblings().attr('aria-hidden', 'false');
});
},
trapChooseBonusProductModalFocus: function () {
$('body').on('keydown', '#chooseBonusProductModal', function (e) {
var focusParams = {
event: e,
containerSelector: '#chooseBonusProductModal',
firstElementSelector: '.close',
lastElementSelector: '.add-bonus-products'
};
focusHelper.setTabNextFocus(focusParams);
});
},
colorAttribute: function () {
$(document).on('click', '[data-attr="color"] button', function (e) {
e.preventDefault();
if ($(this).attr('disabled')) {
return;
}
var $productContainer = $(this).closest('.set-item');
if (!$productContainer.length) {
$productContainer = $(this).closest('.product-detail');
}
attributeSelect($(this).attr('data-url'), $productContainer);
});
},
selectAttribute: function () {
$(document).on('change', 'select[class*="select-"], .options-select', function (e) {
e.preventDefault();
var $productContainer = $(this).closest('.set-item');
if (!$productContainer.length) {
$productContainer = $(this).closest('.product-detail');
}
attributeSelect(e.currentTarget.value, $productContainer);
});
},
availability: function () {
$(document).on('change', '.quantity-select', function (e) {
e.preventDefault();
var $productContainer = $(this).closest('.product-detail');
if (!$productContainer.length) {
$productContainer = $(this).closest('.modal-content').find('.product-quickview');
}
if ($('.bundle-items', $productContainer).length === 0) {
attributeSelect($(e.currentTarget).find('option:selected').data('url'),
$productContainer);
}
});
},
addToCart: function () {
$(document).on('click', 'button.add-to-cart, button.add-to-cart-global', function () {
var addToCartUrl;
var pid;
var pidsObj;
var setPids;
$('body').trigger('product:beforeAddToCart', this);
if ($('.set-items').length && $(this).hasClass('add-to-cart-global')) {
setPids = [];
$('.product-detail').each(function () {
if (!$(this).hasClass('product-set-detail')) {
setPids.push({
pid: $(this).find('.product-id').text(),
qty: $(this).find('.quantity-select').val(),
options: getOptions($(this))
});
}
});
pidsObj = JSON.stringify(setPids);
}
pid = getPidValue($(this));
var $productContainer = $(this).closest('.product-detail');
if (!$productContainer.length) {
$productContainer = $(this).closest('.quick-view-dialog').find('.product-detail');
}
addToCartUrl = getAddToCartUrl();
var form = {
pid: pid,
pidsObj: pidsObj,
childProducts: getChildProducts(),
quantity: getQuantitySelected($(this))
};
if (!$('.bundle-item').length) {
form.options = getOptions($productContainer);
}
$(this).trigger('updateAddToCartFormData', form);
if (addToCartUrl) {
$.ajax({
url: addToCartUrl,
method: 'POST',
data: form,
success: function (data) {
handlePostCartAdd(data);
$('body').trigger('product:afterAddToCart', data);
$.spinner().stop();
miniCartReportingUrl(data.reportingURL);
},
error: function () {
$.spinner().stop();
}
});
}
});
},
selectBonusProduct: function () {
$(document).on('click', '.select-bonus-product', function () {
var $choiceOfBonusProduct = $(this).parents('.choice-of-bonus-product');
var pid = $(this).data('pid');
var maxPids = $('.choose-bonus-product-dialog').data('total-qty');
var submittedQty = parseInt($choiceOfBonusProduct.find('.bonus-quantity-select').val(), 10);
var totalQty = 0;
$.each($('#chooseBonusProductModal .selected-bonus-products .selected-pid'), function () {
totalQty += $(this).data('qty');
});
totalQty += submittedQty;
var optionID = $choiceOfBonusProduct.find('.product-option').data('option-id');
var valueId = $choiceOfBonusProduct.find('.options-select option:selected').data('valueId');
if (totalQty <= maxPids) {
var selectedBonusProductHtml = ''
+ '<div class="selected-pid row" '
+ 'data-pid="' + pid + '"'
+ 'data-qty="' + submittedQty + '"'
+ 'data-optionID="' + (optionID || '') + '"'
+ 'data-option-selected-value="' + (valueId || '') + '"'
+ '>'
+ '<div class="col-sm-11 col-9 bonus-product-name" >'
+ $choiceOfBonusProduct.find('.product-name').html()
+ '</div>'
+ '<div class="col-1"><i class="fa fa-times" aria-hidden="true"></i></div>'
+ '</div>'
;
$('#chooseBonusProductModal .selected-bonus-products').append(selectedBonusProductHtml);
$('.pre-cart-products').html(totalQty);
$('.selected-bonus-products .bonus-summary').removeClass('alert-danger');
} else {
$('.selected-bonus-products .bonus-summary').addClass('alert-danger');
}
});
},
removeBonusProduct: function () {
$(document).on('click', '.selected-pid', function () {
$(this).remove();
var $selected = $('#chooseBonusProductModal .selected-bonus-products .selected-pid');
var count = 0;
if ($selected.length) {
$selected.each(function () {
count += parseInt($(this).data('qty'), 10);
});
}
$('.pre-cart-products').html(count);
$('.selected-bonus-products .bonus-summary').removeClass('alert-danger');
});
},
enableBonusProductSelection: function () {
$('body').on('bonusproduct:updateSelectButton', function (e, response) {
$('button.select-bonus-product', response.$productContainer).attr('disabled',
(!response.product.readyToOrder || !response.product.available));
var pid = response.product.id;
$('button.select-bonus-product', response.$productContainer).data('pid', pid);
});
},
showMoreBonusProducts: function () {
$(document).on('click', '.show-more-bonus-products', function () {
var url = $(this).data('url');
$('.modal-content').spinner().start();
$.ajax({
url: url,
method: 'GET',
success: function (html) {
var parsedHtml = parseHtml(html);
$('.modal-body').append(parsedHtml.body);
$('.show-more-bonus-products:first').remove();
$('.modal-content').spinner().stop();
},
error: function () {
$('.modal-content').spinner().stop();
}
});
});
},
addBonusProductsToCart: function () {
$(document).on('click', '.add-bonus-products', function () {
var $readyToOrderBonusProducts = $('.choose-bonus-product-dialog .selected-pid');
var queryString = '?pids=';
var url = $('.choose-bonus-product-dialog').data('addtocarturl');
var pidsObject = {
bonusProducts: []
};
$.each($readyToOrderBonusProducts, function () {
var qtyOption =
parseInt($(this)
.data('qty'), 10);
var option = null;
if (qtyOption > 0) {
if ($(this).data('optionid') && $(this).data('option-selected-value')) {
option = {};
option.optionId = $(this).data('optionid');
option.productId = $(this).data('pid');
option.selectedValueId = $(this).data('option-selected-value');
}
pidsObject.bonusProducts.push({
pid: $(this).data('pid'),
qty: qtyOption,
options: [option]
});
pidsObject.totalQty = parseInt($('.pre-cart-products').html(), 10);
}
});
queryString += JSON.stringify(pidsObject);
queryString = queryString + '&uuid=' + $('.choose-bonus-product-dialog').data('uuid');
queryString = queryString + '&pliuuid=' + $('.choose-bonus-product-dialog').data('pliuuid');
$.spinner().start();
$.ajax({
url: url + queryString,
method: 'POST',
success: function (data) {
$.spinner().stop();
if (data.error) {
$('#chooseBonusProductModal').modal('hide');
if ($('.add-to-cart-messages').length === 0) {
$('body').append('<div class="add-to-cart-messages"></div>');
}
$('.add-to-cart-messages').append(
'<div class="alert alert-danger add-to-basket-alert text-center"'
+ ' role="alert">'
+ data.errorMessage + '</div>'
);
setTimeout(function () {
$('.add-to-basket-alert').remove();
}, 3000);
} else {
$('.configure-bonus-product-attributes').html(data);
$('.bonus-products-step2').removeClass('hidden-xl-down');
$('#chooseBonusProductModal').modal('hide');
if ($('.add-to-cart-messages').length === 0) {
$('body').append('<div class="add-to-cart-messages"></div>');
}
$('.minicart-quantity').html(data.totalQty);
$('.add-to-cart-messages').append(
'<div class="alert alert-success add-to-basket-alert text-center"'
+ ' role="alert">'
+ data.msgSuccess + '</div>'
);
setTimeout(function () {
$('.add-to-basket-alert').remove();
if ($('.cart-page').length) {
location.reload();
}
}, 1500);
}
},
error: function () {
$.spinner().stop();
}
});
});
},
getPidValue: getPidValue,
getQuantitySelected: getQuantitySelected,
miniCartReportingUrl: miniCartReportingUrl
};

View File

@@ -0,0 +1,151 @@
'use strict';
var base = require('./base');
/**
* Enable/disable UI elements
* @param {boolean} enableOrDisable - true or false
*/
function updateAddToCartEnableDisableOtherElements(enableOrDisable) {
$('button.add-to-cart-global').attr('disabled', enableOrDisable);
}
module.exports = {
methods: {
updateAddToCartEnableDisableOtherElements: updateAddToCartEnableDisableOtherElements
},
availability: base.availability,
addToCart: base.addToCart,
updateAttributesAndDetails: function () {
$('body').on('product:statusUpdate', function (e, data) {
var $productContainer = $('.product-detail[data-pid="' + data.id + '"]');
$productContainer.find('.description-and-detail .product-attributes')
.empty()
.html(data.attributesHtml);
if (data.shortDescription) {
$productContainer.find('.description-and-detail .description')
.removeClass('hidden-xl-down');
$productContainer.find('.description-and-detail .description .content')
.empty()
.html(data.shortDescription);
} else {
$productContainer.find('.description-and-detail .description')
.addClass('hidden-xl-down');
}
if (data.longDescription) {
$productContainer.find('.description-and-detail .details')
.removeClass('hidden-xl-down');
$productContainer.find('.description-and-detail .details .content')
.empty()
.html(data.longDescription);
} else {
$productContainer.find('.description-and-detail .details')
.addClass('hidden-xl-down');
}
});
},
showSpinner: function () {
$('body').on('product:beforeAddToCart product:beforeAttributeSelect', function () {
$.spinner().start();
});
},
updateAttribute: function () {
$('body').on('product:afterAttributeSelect', function (e, response) {
if ($('.product-detail>.bundle-items').length) {
response.container.data('pid', response.data.product.id);
response.container.find('.product-id').text(response.data.product.id);
} else if ($('.product-set-detail').eq(0)) {
response.container.data('pid', response.data.product.id);
response.container.find('.product-id').text(response.data.product.id);
} else {
$('.product-id').text(response.data.product.id);
$('.product-detail:not(".bundle-item")').data('pid', response.data.product.id);
}
});
},
updateAddToCart: function () {
$('body').on('product:updateAddToCart', function (e, response) {
// update local add to cart (for sets)
$('button.add-to-cart', response.$productContainer).attr('disabled',
(!response.product.readyToOrder || !response.product.available));
var enable = $('.product-availability').toArray().every(function (item) {
return $(item).data('available') && $(item).data('ready-to-order');
});
module.exports.methods.updateAddToCartEnableDisableOtherElements(!enable);
});
},
updateAvailability: function () {
$('body').on('product:updateAvailability', function (e, response) {
$('div.availability', response.$productContainer)
.data('ready-to-order', response.product.readyToOrder)
.data('available', response.product.available);
$('.availability-msg', response.$productContainer)
.empty().html(response.message);
if ($('.global-availability').length) {
var allAvailable = $('.product-availability').toArray()
.every(function (item) { return $(item).data('available'); });
var allReady = $('.product-availability').toArray()
.every(function (item) { return $(item).data('ready-to-order'); });
$('.global-availability')
.data('ready-to-order', allReady)
.data('available', allAvailable);
$('.global-availability .availability-msg').empty()
.html(allReady ? response.message : response.resources.info_selectforstock);
}
});
},
sizeChart: function () {
$('.size-chart a').on('click', function (e) {
e.preventDefault();
var url = $(this).attr('href');
var $prodSizeChart = $(this).closest('.size-chart').find('.size-chart-collapsible');
if ($prodSizeChart.is(':empty')) {
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function (data) {
$prodSizeChart.append(data.content);
}
});
}
$prodSizeChart.toggleClass('active');
});
var $sizeChart = $('.size-chart-collapsible');
$('body').on('click touchstart', function (e) {
if ($('.size-chart').has(e.target).length <= 0) {
$sizeChart.removeClass('active');
}
});
},
copyProductLink: function () {
$('body').on('click', '#fa-link', function () {
event.preventDefault();
var $temp = $('<input>');
$('body').append($temp);
$temp.val($('#shareUrl').val()).select();
document.execCommand('copy');
$temp.remove();
$('.copy-link-message').attr('role', 'alert');
$('.copy-link-message').removeClass('d-none');
setTimeout(function () {
$('.copy-link-message').addClass('d-none');
}, 3000);
});
},
focusChooseBonusProductModal: base.focusChooseBonusProductModal()
};

View File

@@ -0,0 +1,201 @@
'use strict';
var base = require('./base');
var focusHelper = require('../components/focus');
/**
* Generates the modal window on the first call.
*
*/
function getModalHtmlElement() {
if ($('#quickViewModal').length !== 0) {
$('#quickViewModal').remove();
}
var htmlString = '<!-- Modal -->'
+ '<div class="modal fade" id="quickViewModal" role="dialog">'
+ '<span class="enter-message sr-only" ></span>'
+ '<div class="modal-dialog quick-view-dialog">'
+ '<!-- Modal content-->'
+ '<div class="modal-content">'
+ '<div class="modal-header">'
+ ' <a class="full-pdp-link" href=""></a>'
+ ' <button type="button" class="close pull-right" data-dismiss="modal">'
+ ' <span aria-hidden="true">&times;</span>'
+ ' <span class="sr-only"> </span>'
+ ' </button>'
+ '</div>'
+ '<div class="modal-body"></div>'
+ '<div class="modal-footer"></div>'
+ '</div>'
+ '</div>'
+ '</div>';
$('body').append(htmlString);
}
/**
* @typedef {Object} QuickViewHtml
* @property {string} body - Main Quick View body
* @property {string} footer - Quick View footer content
*/
/**
* Parse HTML code in Ajax response
*
* @param {string} html - Rendered HTML from quickview template
* @return {QuickViewHtml} - QuickView content components
*/
function parseHtml(html) {
var $html = $('<div>').append($.parseHTML(html));
var body = $html.find('.product-quickview');
var footer = $html.find('.modal-footer').children();
return { body: body, footer: footer };
}
/**
* replaces the content in the modal window on for the selected product variation.
* @param {string} selectedValueUrl - url to be used to retrieve a new product model
*/
function fillModalElement(selectedValueUrl) {
$('.modal-body').spinner().start();
$.ajax({
url: selectedValueUrl,
method: 'GET',
dataType: 'json',
success: function (data) {
var parsedHtml = parseHtml(data.renderedTemplate);
$('.modal-body').empty();
$('.modal-body').html(parsedHtml.body);
$('.modal-footer').html(parsedHtml.footer);
$('.full-pdp-link').text(data.quickViewFullDetailMsg);
$('#quickViewModal .full-pdp-link').attr('href', data.productUrl);
$('#quickViewModal .size-chart').attr('href', data.productUrl);
$('#quickViewModal .modal-header .close .sr-only').text(data.closeButtonText);
$('#quickViewModal .enter-message').text(data.enterDialogMessage);
$('#quickViewModal').modal('show');
$('body').trigger('quickview:ready');
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
}
module.exports = {
showQuickview: function () {
$('body').on('click', '.quickview', function (e) {
e.preventDefault();
var selectedValueUrl = $(this).closest('a.quickview').attr('href');
$(e.target).trigger('quickview:show');
getModalHtmlElement();
fillModalElement(selectedValueUrl);
});
},
focusQuickview: function () {
$('body').on('shown.bs.modal', '#quickViewModal', function () {
$('#quickViewModal .close').focus();
});
},
trapQuickviewFocus: function () {
$('body').on('keydown', '#quickViewModal', function (e) {
var focusParams = {
event: e,
containerSelector: '#quickViewModal',
firstElementSelector: '.full-pdp-link',
lastElementSelector: '.add-to-cart-global',
nextToLastElementSelector: '.modal-footer .quantity-select'
};
focusHelper.setTabNextFocus(focusParams);
});
},
availability: base.availability,
addToCart: base.addToCart,
showSpinner: function () {
$('body').on('product:beforeAddToCart', function (e, data) {
$(data).closest('.modal-content').spinner().start();
});
},
hideDialog: function () {
$('body').on('product:afterAddToCart', function () {
$('#quickViewModal').modal('hide');
});
},
beforeUpdateAttribute: function () {
$('body').on('product:beforeAttributeSelect', function () {
$('.modal.show .modal-content').spinner().start();
});
},
updateAttribute: function () {
$('body').on('product:afterAttributeSelect', function (e, response) {
if ($('.modal.show .product-quickview>.bundle-items').length) {
$('.modal.show').find(response.container).data('pid', response.data.product.id);
$('.modal.show').find(response.container)
.find('.product-id').text(response.data.product.id);
} else if ($('.set-items').length) {
response.container.find('.product-id').text(response.data.product.id);
} else {
$('.modal.show .product-quickview').data('pid', response.data.product.id);
$('.modal.show .full-pdp-link')
.attr('href', response.data.product.selectedProductUrl);
}
});
},
updateAddToCart: function () {
$('body').on('product:updateAddToCart', function (e, response) {
// update local add to cart (for sets)
$('button.add-to-cart', response.$productContainer).attr('disabled',
(!response.product.readyToOrder || !response.product.available));
// update global add to cart (single products, bundles)
var dialog = $(response.$productContainer)
.closest('.quick-view-dialog');
$('.add-to-cart-global', dialog).attr('disabled',
!$('.global-availability', dialog).data('ready-to-order')
|| !$('.global-availability', dialog).data('available')
);
});
},
updateAvailability: function () {
$('body').on('product:updateAvailability', function (e, response) {
// bundle individual products
$('.product-availability', response.$productContainer)
.data('ready-to-order', response.product.readyToOrder)
.data('available', response.product.available)
.find('.availability-msg')
.empty()
.html(response.message);
var dialog = $(response.$productContainer)
.closest('.quick-view-dialog');
if ($('.product-availability', dialog).length) {
// bundle all products
var allAvailable = $('.product-availability', dialog).toArray()
.every(function (item) { return $(item).data('available'); });
var allReady = $('.product-availability', dialog).toArray()
.every(function (item) { return $(item).data('ready-to-order'); });
$('.global-availability', dialog)
.data('ready-to-order', allReady)
.data('available', allAvailable);
$('.global-availability .availability-msg', dialog).empty()
.html(allReady ? response.message : response.resources.info_selectforstock);
} else {
// single product
$('.global-availability', dialog)
.data('ready-to-order', response.product.readyToOrder)
.data('available', response.product.available)
.find('.availability-msg')
.empty()
.html(response.message);
}
});
}
};

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./product/detail'));
});

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./product/quickView'));
});

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./profile/profile'));
});

View File

@@ -0,0 +1,67 @@
'use strict';
var formValidation = require('../components/formValidation');
module.exports = {
submitProfile: function () {
$('form.edit-profile-form').submit(function (e) {
var $form = $(this);
e.preventDefault();
var url = $form.attr('action');
$form.spinner().start();
$('form.edit-profile-form').trigger('profile:edit', e);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: $form.serialize(),
success: function (data) {
$form.spinner().stop();
if (!data.success) {
formValidation($form, data);
} else {
location.href = data.redirectUrl;
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
$form.spinner().stop();
}
});
return false;
});
},
submitPassword: function () {
$('form.change-password-form').submit(function (e) {
var $form = $(this);
e.preventDefault();
var url = $form.attr('action');
$form.spinner().start();
$('form.change-password-form').trigger('password:edit', e);
$.ajax({
url: url,
type: 'post',
dataType: 'json',
data: $form.serialize(),
success: function (data) {
$form.spinner().stop();
if (!data.success) {
formValidation($form, data);
} else {
location.href = data.redirectUrl;
}
},
error: function (err) {
if (err.responseJSON.redirectUrl) {
window.location.href = err.responseJSON.redirectUrl;
}
$form.spinner().stop();
}
});
return false;
});
}
};

View File

@@ -0,0 +1,8 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./search/search'));
processInclude(require('./product/quickView'));
});

View File

@@ -0,0 +1,223 @@
'use strict';
/**
* Update DOM elements with Ajax results
*
* @param {Object} $results - jQuery DOM element
* @param {string} selector - DOM element to look up in the $results
* @return {undefined}
*/
function updateDom($results, selector) {
var $updates = $results.find(selector);
$(selector).empty().html($updates.html());
}
/**
* Keep refinement panes expanded/collapsed after Ajax refresh
*
* @param {Object} $results - jQuery DOM element
* @return {undefined}
*/
function handleRefinements($results) {
$('.refinement.active').each(function () {
$(this).removeClass('active');
var activeDiv = $results.find('.' + $(this)[0].className.replace(/ /g, '.'));
activeDiv.addClass('active');
activeDiv.find('button.title').attr('aria-expanded', 'true');
});
updateDom($results, '.refinements');
}
/**
* Parse Ajax results and updated select DOM elements
*
* @param {string} response - Ajax response HTML code
* @return {undefined}
*/
function parseResults(response) {
var $results = $(response);
var specialHandlers = {
'.refinements': handleRefinements
};
// Update DOM elements that do not require special handling
[
'.grid-header',
'.header-bar',
'.header.page-title',
'.product-grid',
'.show-more',
'.filter-bar'
].forEach(function (selector) {
updateDom($results, selector);
});
Object.keys(specialHandlers).forEach(function (selector) {
specialHandlers[selector]($results);
});
}
/**
* This function retrieves another page of content to display in the content search grid
* @param {JQuery} $element - the jquery element that has the click event attached
* @param {JQuery} $target - the jquery element that will receive the response
* @return {undefined}
*/
function getContent($element, $target) {
var showMoreUrl = $element.data('url');
$.spinner().start();
$.ajax({
url: showMoreUrl,
method: 'GET',
success: function (response) {
$target.append(response);
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
}
/**
* Update sort option URLs from Ajax response
*
* @param {string} response - Ajax response HTML code
* @return {undefined}
*/
function updateSortOptions(response) {
var $tempDom = $('<div>').append($(response));
var sortOptions = $tempDom.find('.grid-footer').data('sort-options').options;
sortOptions.forEach(function (option) {
$('option.' + option.id).val(option.url);
});
}
module.exports = {
filter: function () {
// Display refinements bar when Menu icon clicked
$('.container').on('click', 'button.filter-results', function () {
$('.refinement-bar, .modal-background').show();
$('.refinement-bar').siblings().attr('aria-hidden', true);
$('.refinement-bar').closest('.row').siblings().attr('aria-hidden', true);
$('.refinement-bar').closest('.tab-pane.active').siblings().attr('aria-hidden', true);
$('.refinement-bar').closest('.container.search-results').siblings().attr('aria-hidden', true);
$('.refinement-bar .close').focus();
});
},
closeRefinements: function () {
// Refinements close button
$('.container').on('click', '.refinement-bar button.close, .modal-background', function () {
$('.refinement-bar, .modal-background').hide();
$('.refinement-bar').siblings().attr('aria-hidden', false);
$('.refinement-bar').closest('.row').siblings().attr('aria-hidden', false);
$('.refinement-bar').closest('.tab-pane.active').siblings().attr('aria-hidden', false);
$('.refinement-bar').closest('.container.search-results').siblings().attr('aria-hidden', false);
$('.btn.filter-results').focus();
});
},
resize: function () {
// Close refinement bar and hide modal background if user resizes browser
$(window).resize(function () {
$('.refinement-bar, .modal-background').hide();
$('.refinement-bar').siblings().attr('aria-hidden', false);
$('.refinement-bar').closest('.row').siblings().attr('aria-hidden', false);
$('.refinement-bar').closest('.tab-pane.active').siblings().attr('aria-hidden', false);
$('.refinement-bar').closest('.container.search-results').siblings().attr('aria-hidden', false);
});
},
sort: function () {
// Handle sort order menu selection
$('.container').on('change', '[name=sort-order]', function (e) {
e.preventDefault();
$.spinner().start();
$(this).trigger('search:sort', this.value);
$.ajax({
url: this.value,
data: { selectedUrl: this.value },
method: 'GET',
success: function (response) {
$('.product-grid').empty().html(response);
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
});
},
showMore: function () {
// Show more products
$('.container').on('click', '.show-more button', function (e) {
e.stopPropagation();
var showMoreUrl = $(this).data('url');
e.preventDefault();
$.spinner().start();
$(this).trigger('search:showMore', e);
$.ajax({
url: showMoreUrl,
data: { selectedUrl: showMoreUrl },
method: 'GET',
success: function (response) {
$('.grid-footer').replaceWith(response);
updateSortOptions(response);
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
});
},
applyFilter: function () {
// Handle refinement value selection and reset click
$('.container').on(
'click',
'.refinements li button, .refinement-bar button.reset, .filter-value button, .swatch-filter button',
function (e) {
e.preventDefault();
e.stopPropagation();
$.spinner().start();
$(this).trigger('search:filter', e);
$.ajax({
url: $(this).data('href'),
data: {
page: $('.grid-footer').data('page-number'),
selectedUrl: $(this).data('href')
},
method: 'GET',
success: function (response) {
parseResults(response);
$.spinner().stop();
},
error: function () {
$.spinner().stop();
}
});
});
},
showContentTab: function () {
// Display content results from the search
$('.container').on('click', '.content-search', function () {
if ($('#content-search-results').html() === '') {
getContent($(this), $('#content-search-results'));
}
});
// Display the next page of content results from the search
$('.container').on('click', '.show-more-content button', function () {
getContent($(this), $('#content-search-results'));
$('.show-more-content').remove();
});
}
};

View File

@@ -0,0 +1,7 @@
'use strict';
var processInclude = require('./util');
$(document).ready(function () {
processInclude(require('./storeLocator/storeLocator'));
});

View File

@@ -0,0 +1,264 @@
/* globals google */
'use strict';
/**
* appends params to a url
* @param {string} url - Original url
* @param {Object} params - Parameters to append
* @returns {string} result url with appended parameters
*/
function appendToUrl(url, params) {
var newUrl = url;
newUrl += (newUrl.indexOf('?') !== -1 ? '&' : '?') + Object.keys(params).map(function (key) {
return key + '=' + encodeURIComponent(params[key]);
}).join('&');
return newUrl;
}
/**
* Uses google maps api to render a map
*/
function maps() {
var map;
var infowindow = new google.maps.InfoWindow();
// Init U.S. Map in the center of the viewport
var latlng = new google.maps.LatLng(37.09024, -95.712891);
var mapOptions = {
scrollwheel: false,
zoom: 4,
center: latlng
};
map = new google.maps.Map($('.map-canvas')[0], mapOptions);
var mapdiv = $('.map-canvas').attr('data-locations');
mapdiv = JSON.parse(mapdiv);
var bounds = new google.maps.LatLngBounds();
// Customized google map marker icon with svg format
var markerImg = {
path: 'M13.5,30.1460153 L16.8554555,25.5 L20.0024287,25.5 C23.039087,25.5 25.5,' +
'23.0388955 25.5,20.0024287 L25.5,5.99757128 C25.5,2.96091298 23.0388955,0.5 ' +
'20.0024287,0.5 L5.99757128,0.5 C2.96091298,0.5 0.5,2.96110446 0.5,5.99757128 ' +
'L0.5,20.0024287 C0.5,23.039087 2.96110446,25.5 5.99757128,25.5 L10.1445445,' +
'25.5 L13.5,30.1460153 Z',
fillColor: '#0070d2',
fillOpacity: 1,
scale: 1.1,
strokeColor: 'white',
strokeWeight: 1,
anchor: new google.maps.Point(13, 30),
labelOrigin: new google.maps.Point(12, 12)
};
Object.keys(mapdiv).forEach(function (key) {
var item = mapdiv[key];
var lable = parseInt(key, 10) + 1;
var storeLocation = new google.maps.LatLng(item.latitude, item.longitude);
var marker = new google.maps.Marker({
position: storeLocation,
map: map,
title: item.name,
icon: markerImg,
label: { text: lable.toString(), color: 'white', fontSize: '16px' }
});
marker.addListener('click', function () {
infowindow.setOptions({
content: item.infoWindowHtml
});
infowindow.open(map, marker);
});
// Create a minimum bound based on a set of storeLocations
bounds.extend(marker.position);
});
// Fit the all the store marks in the center of a minimum bounds when any store has been found.
if (mapdiv && mapdiv.length !== 0) {
map.fitBounds(bounds);
}
}
/**
* Renders the results of the search and updates the map
* @param {Object} data - Response from the server
*/
function updateStoresResults(data) {
var $resultsDiv = $('.results');
var $mapDiv = $('.map-canvas');
var hasResults = data.stores.length > 0;
if (!hasResults) {
$('.store-locator-no-results').show();
} else {
$('.store-locator-no-results').hide();
}
$resultsDiv.empty()
.data('has-results', hasResults)
.data('radius', data.radius)
.data('search-key', data.searchKey);
$mapDiv.attr('data-locations', data.locations);
if ($mapDiv.data('has-google-api')) {
maps();
} else {
$('.store-locator-no-apiKey').show();
}
if (data.storesResultsHtml) {
$resultsDiv.append(data.storesResultsHtml);
}
}
/**
* Search for stores with new zip code
* @param {HTMLElement} element - the target html element
* @returns {boolean} false to prevent default event
*/
function search(element) {
var dialog = element.closest('.in-store-inventory-dialog');
var spinner = dialog.length ? dialog.spinner() : $.spinner();
spinner.start();
var $form = element.closest('.store-locator');
var radius = $('.results').data('radius');
var url = $form.attr('action');
var urlParams = { radius: radius };
var payload = $form.is('form') ? $form.serialize() : { postalCode: $form.find('[name="postalCode"]').val() };
url = appendToUrl(url, urlParams);
$.ajax({
url: url,
type: $form.attr('method'),
data: payload,
dataType: 'json',
success: function (data) {
spinner.stop();
updateStoresResults(data);
$('.select-store').prop('disabled', true);
}
});
return false;
}
module.exports = {
init: function () {
if ($('.map-canvas').data('has-google-api')) {
maps();
} else {
$('.store-locator-no-apiKey').show();
}
if (!$('.results').data('has-results')) {
$('.store-locator-no-results').show();
}
},
detectLocation: function () {
// clicking on detect location.
$('.detect-location').on('click', function () {
$.spinner().start();
if (!navigator.geolocation) {
$.spinner().stop();
return;
}
navigator.geolocation.getCurrentPosition(function (position) {
var $detectLocationButton = $('.detect-location');
var url = $detectLocationButton.data('action');
var radius = $('.results').data('radius');
var urlParams = {
radius: radius,
lat: position.coords.latitude,
long: position.coords.longitude
};
url = appendToUrl(url, urlParams);
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function (data) {
$.spinner().stop();
updateStoresResults(data);
$('.select-store').prop('disabled', true);
}
});
});
});
},
search: function () {
$('.store-locator-container form.store-locator').submit(function (e) {
e.preventDefault();
search($(this));
});
$('.store-locator-container .btn-storelocator-search[type="button"]').click(function (e) {
e.preventDefault();
search($(this));
});
},
changeRadius: function () {
$('.store-locator-container .radius').change(function () {
var radius = $(this).val();
var searchKeys = $('.results').data('search-key');
var url = $(this).data('action-url');
var urlParams = {};
if (searchKeys.postalCode) {
urlParams = {
radius: radius,
postalCode: searchKeys.postalCode
};
} else if (searchKeys.lat && searchKeys.long) {
urlParams = {
radius: radius,
lat: searchKeys.lat,
long: searchKeys.long
};
}
url = appendToUrl(url, urlParams);
var dialog = $(this).closest('.in-store-inventory-dialog');
var spinner = dialog.length ? dialog.spinner() : $.spinner();
spinner.start();
$.ajax({
url: url,
type: 'get',
dataType: 'json',
success: function (data) {
spinner.stop();
updateStoresResults(data);
$('.select-store').prop('disabled', true);
}
});
});
},
selectStore: function () {
$('.store-locator-container').on('click', '.select-store', (function (e) {
e.preventDefault();
var selectedStore = $(':checked', '.results-card .results');
var data = {
storeID: selectedStore.val(),
searchRadius: $('#radius').val(),
searchPostalCode: $('.results').data('search-key').postalCode,
storeDetailsHtml: selectedStore.siblings('label').find('.store-details').html(),
event: e
};
$('body').trigger('store:selected', data);
}));
},
updateSelectStoreButton: function () {
$('body').on('change', '.select-store-input', (function () {
$('.select-store').prop('disabled', false);
}));
}
};

View File

@@ -0,0 +1,8 @@
{
"env": {
"es6": true
},
"parserOptions": {
"sourceType": "module"
}
}

View File

@@ -0,0 +1,11 @@
require('bootstrap/js/src/util.js');
require('bootstrap/js/src/alert.js');
// require('bootstrap/js/src/button.js');
require('bootstrap/js/src/carousel.js');
require('bootstrap/js/src/collapse.js');
// require('bootstrap/js/src/dropdown.js');
require('bootstrap/js/src/modal.js');
require('bootstrap/js/src/scrollspy.js');
require('bootstrap/js/src/tab.js');
// require('bootstrap/js/src/tooltip.js');
// require('bootstrap/js/src/popover.js');

View File

@@ -0,0 +1,13 @@
'use strict';
module.exports = function (include) {
if (typeof include === 'function') {
include();
} else if (typeof include === 'object') {
Object.keys(include).forEach(function (key) {
if (typeof include[key] === 'function') {
include[key]();
}
});
}
};

View File

@@ -0,0 +1,35 @@
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
@import "bootstrap/scss/images";
@import "bootstrap/scss/code";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/tables";
@import "bootstrap/scss/forms";
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/transitions";
@import "bootstrap/scss/dropdown";
@import "bootstrap/scss/button-group";
@import "bootstrap/scss/input-group";
@import "bootstrap/scss/custom-forms";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/navbar";
@import "bootstrap/scss/card";
@import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/pagination";
@import "bootstrap/scss/badge";
@import "bootstrap/scss/jumbotron";
@import "bootstrap/scss/alert";
@import "bootstrap/scss/progress";
@import "bootstrap/scss/media";
@import "bootstrap/scss/list-group";
@import "bootstrap/scss/close";
@import "bootstrap/scss/modal";
@import "bootstrap/scss/tooltip";
@import "bootstrap/scss/popover";
@import "bootstrap/scss/carousel";
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/print";

View File

@@ -0,0 +1,86 @@
.carousel {
.icon-prev,
.icon-next {
background-color: $white;
font-size: 1.875em;
// width and height here need to use rem units because the font size used here is 30px
height: 3rem;
padding-top: 0.24em;
width: 3rem;
&::before {
color: black;
font-family: 'FontAwesome';
}
}
.icon-prev {
&::before {
content: '\f104';
}
}
.icon-next {
&::before {
content: '\f105';
}
}
.carousel-control-prev {
justify-content: flex-start;
}
.carousel-control-next {
justify-content: flex-end;
}
}
.nav-tabs {
border-bottom: $border-width solid $grey3;
.nav-link {
font-size: 1.1rem;
color: $nav-tabs-link-hover-border-color;
&.active {
border-bottom: 0.188em solid #{var(--skin-primary-color-1)};
}
}
}
.card {
margin-bottom: 1em;
}
.card-header h4 {
margin-bottom: 0;
}
.modal .modal-body {
flex: 0 0 auto;
}
dt {
color: $gray-700;
font-weight: normal;
}
.custom-checkbox .custom-control-label::before {
border: 1px solid black;
background: $grey1; /* For browsers that do not support gradients */
background: linear-gradient($grey1, $grey3); /* Standard syntax */
}
.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {
background-image: $svg-check;
}
.custom-radio .custom-control-label::before {
border: 1px solid black;
background: $grey3; /* For browsers that do not support gradients */
background: linear-gradient($grey3, $grey5); /* Standard syntax */
}
.form-control.is-invalid {
background-image: none;
}

View File

@@ -0,0 +1,116 @@
@import "variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "productCard";
.minicart {
position: relative;
h1 {
font-size: 1rem;
}
.cart {
padding-top: 0.625em;
padding-bottom: 0.625em;
background-color: $body-bg;
}
.remove-btn {
color: $slightly-darker-gray;
float: right;
background-color: white;
border: none;
font-size: 1.625em;
margin-top: -0.313em;
padding: 0;
}
.product-summary {
margin-right: -0.938em;
max-height: 21.875em;
overflow-y: auto;
overflow-x: hidden;
padding-right: 0.938em;
}
.card-body {
padding: 0.625em;
}
.quantity-label {
font-size: 0.813em;
}
.quantity {
width: 100%;
}
.popover {
top: 100%;
left: auto;
right: 0;
min-width: 23.44rem;
max-width: 23.44rem;
min-height: 22.7rem;
display: none;
&::before {
left: auto;
right: 15px;
}
&::after {
left: auto;
right: 16px;
}
&.show {
display: block;
}
}
.minicart-footer {
border-top: 1px solid $grey3;
}
.estimated-total {
margin-top: 0.625em;
}
.sub-total-label {
font-size: 1em;
font-weight: 600;
}
.sub-total {
font-size: 1em;
font-weight: 600;
}
.line-item-divider {
margin: 0.625em -0.625em 0.625em -1.325em;
}
.line-item-name {
width: 90%;
}
}
.hide-link-med {
@include media-breakpoint-only(sm) {
display: none;
}
}
.hide-no-link {
@include media-breakpoint-up(md) {
display: none;
}
@include media-breakpoint-down(xs) {
display: none;
}
}

View File

@@ -0,0 +1,108 @@
@import "variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
.item-attributes {
vertical-align: top;
padding-left: 0;
}
.line-item-attributes,
.line-item-option {
font-size: 0.813rem;
margin: 0;
}
.line-item-name {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
color: $darker-gray;
font-size: 1em;
font-weight: bold;
margin-bottom: 0.313em;
}
.line-item-pricing-info {
margin-bottom: 0;
+ .price {
font-size: 1em;
font-weight: bolder;
}
+ .unit-price .price {
font-size: 1em;
font-weight: bolder;
}
}
.line-item-price-quantity-info {
margin-top: 0.625em;
border-top: 1px solid $horizontal-rule-grey;
}
.line-item-total-text {
font-size: 0.813em;
}
.pricing {
font-size: 1em;
font-weight: bolder;
}
.item-image {
height: 5.625em;
width: 5.625em;
margin-right: 0.938em;
flex-grow: 0;
flex-shrink: 0;
img.product-image {
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
max-height: 5.625em;
}
}
.non-adjusted-price {
display: none;
}
.line-item-promo {
color: $success;
font-size: 0.813em;
}
.line-item-header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.bundled-line-item + .bundled-line-item {
margin-top: 0.625em;
}
.bundle-includes {
font-size: 0.813em;
margin-bottom: 0.625em;
}
.line-item-divider {
margin: 0.625em -1.225em 0.625em -1.325em;
}
.line-dotted {
border-top: 0.063em dashed #ccc;
}
.line-item-availability {
font-size: 0.813rem;
}
.product-line-item-details {
overflow-y: auto;
}

View File

@@ -0,0 +1,83 @@
@import "bootstrap/scss/functions";
// font-awesome font file locations in relation to target location of the css file.
$fa-font-path: "../fonts" !default;
// flag icons location in relation to target location of the css file.
$flag-icon-css-path: "../fonts/flags/" !default;
$white: #fff !default;
$black: #000 !default;
$blue: #0070d2 !default;
$green: #008827 !default;
// primary is replaced by css variable --skin-primary-color-1
$primary: #00a1e0 !default;
$red: #c00 !default;
$success: $green !default;
$danger: $red !default;
$light-blue: #7ed0ee !default;
// Consolidated values
$grey1: #f9f9f9 !default;
$grey2: #eee !default;
$grey3: #ccc !default;
$grey4: #999 !default;
$grey5: #666 !default;
$grey6: #444 !default;
$grey7: #222 !default;
$grey8: #333 !default;
$grey-transparent-1: rgba(0, 0, 0, 0.65) !default;
$grey-transparent-2: rgba(0, 0, 0, 0.25) !default;
$light-gray: $grey1 !default;
$slightly-darker-gray: $grey4 !default;
$dark-gray: $grey6 !default;
$darker-gray: $grey7 !default;
$horizontal-rule-grey: $grey3 !default;
$product-number-grey: $grey3 !default;
$horizontal-border-grey: $grey4 !default;
$menu-link: $grey6 !default;
$close-menu-bg: $grey2 !default;
$link-color: $dark-gray !default;
$hr-border-color: $grey3 !default;
$grid-breakpoints: (
xs: 0,
sm: 544px,
md: 769px,
lg: 992px,
xl: 1200px
) !default;
$container-max-widths: (
md: 720px,
lg: 940px,
xl: 1140px
) !default;
$border-radius: 0.1875rem !default;
$border-radius-lg: $border-radius !default;
$border-radius-sm: $border-radius !default;
$state-danger-text: #fff !default;
$alert-success-text: #fff !default;
// Font sizes
$base16-14px: 0.875em !default;
// Bootstrap overrides
$body-bg: $grey1 !default;
$card-cap-bg: $white !default;
// Tabs
$nav-tabs-border-width: 0 !default;
$nav-tabs-border-radius: 0 !default;
$nav-tabs-active-link-hover-bg: transparent !default;
// breadcrumb
$breadcrumb-bg: transparent !default;
// table border
$table-border-color: $grey3 !default;
$svg-check: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23ff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E") !default;

View File

@@ -0,0 +1,62 @@
@import "../variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "../components/formFields";
.account-image {
background-image: url(../../images/account.jpg);
background-position-y: 40%;
}
.card-footer > a {
color: #{var(--skin-primary-color-1)};
text-decoration: underline;
}
.card-header > a {
color: #{var(--skin-primary-color-1)};
float: right;
text-decoration: underline;
}
.card-info-group {
p {
margin-bottom: 0;
}
div {
margin-bottom: 1rem;
}
div:last-child {
margin-bottom: 0;
}
}
.order-history-control,
.order-history {
h2 {
font-weight: bold;
margin-top: 0.5rem;
}
}
.dashboard-order-card-image {
width: 7rem;
padding-right: 1rem;
}
.dashboard-order-card-footer-columns:last-child {
text-align: right;
}
.dashboard-order-card-footer-value {
font-weight: bold;
font-size: 1rem;
}
.card-header h2,
.card-header h3 {
font-size: 1.5rem;
margin-bottom: 0;
}

View File

@@ -0,0 +1,19 @@
@import "../variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "account";
@import "../utilities/deleteCardButton";
.card-body-positioning {
position: relative;
}
.card-make-default-link {
margin-top: 0.625em; /* 10/16 */
}
.remove-btn {
@include delete-card-button();
width: 3rem;
}

View File

@@ -0,0 +1,44 @@
@import "../variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "account";
.card-footer-border {
border-top: 1px dashed $horizontal-border-grey;
}
.card-make-default-link {
margin-top: 1rem;
}
.dashboard-cards-block-title {
font-weight: bold;
}
.dashboard-order-card-status {
text-transform: capitalize;
}
.dashboard-order-card-image {
width: 7rem;
}
.account-landing-ordercard {
padding: 0;
height: 1rem;
margin-left: 1.25rem;
border-bottom: 0 none;
margin-top: 1rem;
h4 {
font-size: 1.2rem;
}
}
div.order-number {
padding-top: 0.2rem;
}
.dashboard-cards-block-title {
margin-bottom: 1em;
}

View File

@@ -0,0 +1,14 @@
@import "../variables";
@import "../checkout/checkoutComponents";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
.section-label {
font-size: $receipt-font-size;
font-weight: bolder;
}
.my-account {
text-align: center;
margin-bottom: 0.938em;
}

View File

@@ -0,0 +1,36 @@
@import "../variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "account";
@import "../utilities/deleteCardButton";
@import "../components/creditCardField";
.back-to-account-link {
margin-bottom: 0;
}
.card-body-positioning {
position: relative;
}
.make-default-payment {
margin-bottom: 1rem;
}
.masked-card-number {
margin-top: 1rem;
}
.payment-to-remove {
font-weight: bold;
}
.remove-btn {
@include delete-card-button();
width: 3rem;
}
.no-saved-payments {
font-family: var(--skin-header-font), sans-serif;
}

View File

@@ -0,0 +1,17 @@
@import "../variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "account";
.profile-back-to-account-link {
color: #{var(--skin-primary-color-1)};
text-decoration: underline;
display: block;
text-align: center;
}
.tracking-consent {
color: #{var(--skin-link-color-1)};
text-decoration: underline;
cursor: pointer;
}

View File

@@ -0,0 +1,314 @@
@import "variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "utilities/deleteCardButton";
@import "components/quickView";
$spacer: 0.625em;
$negative-spacer: -0.625rem;
.single-approaching-discount {
border: 1px solid rgba(0, 0, 0, 0.125);
background-color: $white;
color: $success;
margin-bottom: 0.3125rem;
}
.checkout-continue {
position: fixed;
bottom: 0;
z-index: 1;
padding-right: 0;
padding-left: 0;
@include media-breakpoint-down(xs) {
background-color: rgba(255, 255, 255, 0.95);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
div {
padding: $spacer;
}
}
@include media-breakpoint-up(sm) {
position: static;
padding-right: 0.938em;
padding-left: 0.938em;
}
}
.edit {
margin-right: 0.625em;
}
.product-edit {
margin-top: auto;
a {
font-size: 0.813em;
}
}
.line-item-attributes {
font-size: 0.813rem;
margin: 0;
}
.item-attributes {
display: inline-block;
vertical-align: top;
}
.line-item-divider {
margin: $spacer $negative-spacer $spacer $negative-spacer;
}
.line-item-name {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-size: 1em;
color: $darker-gray;
font-weight: bold;
margin-bottom: $spacer;
width: 90%;
}
.line-item-price {
font-size: 1em;
color: $darker-gray;
font-weight: bold;
}
.line-item-price-info {
font-size: 0.75em;
margin-bottom: 0.5rem;
}
.no-margin-top {
margin-top: 0;
}
.number-of-items {
font-size: 1.25rem;
margin-top: 1rem;
@include media-breakpoint-up(sm) {
margin-top: 0;
}
}
.optional-promo {
color: #{var(--skin-primary-color-1)};
}
.product-info {
margin-bottom: 0.313em;
padding: $spacer;
@include media-breakpoint-up(md) {
height: auto;
}
}
.product-to-remove {
font-weight: bold;
}
.item-image {
height: 5.625em;
width: 5.625em;
margin-right: 0.938em;
flex-grow: 0;
flex-shrink: 0;
img.product-image {
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
max-height: 5.625em;
}
}
.promo-code-form {
display: none;
@include media-breakpoint-up(sm) {
display: block;
}
}
.promo-code-submit {
padding-left: 0;
}
.quantity-form {
margin-bottom: 0;
margin-top: -0.313em;
}
.product-info {
.remove-btn {
color: $slightly-darker-gray;
font-size: 1.625em;
padding: 0;
position: absolute;
top: $negative-spacer;
right: 0.25rem;
border: none;
background-color: $white;
@include media-breakpoint-up(lg) {
top: 0;
bottom: 0;
right: $negative-spacer;
margin-top: $negative-spacer;
margin-bottom: $negative-spacer;
}
}
.remove-btn-lg {
@include delete-card-button();
padding-left: 0.3125rem;
padding-right: 0.3125rem;
z-index: 1;
}
}
.bonus-product {
display: block;
text-align: center;
}
.remove-line-item {
position: relative;
}
.remove-coupon {
border: none;
background: transparent;
}
.sub-total {
font-weight: bold;
}
.grand-total {
font-size: 1em;
font-weight: 600;
}
.coupon-price-adjustment + .coupon-price-adjustment {
margin-top: 0.625rem;
}
.coupon-price-adjustment {
background-color: $white;
padding: 0.625em;
border: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0.1875rem;
}
.coupon-promotion-relationship {
font-size: 0.813em;
padding-left: 1rem;
margin-bottom: 0;
}
.coupons-and-promos {
margin-bottom: 0.625rem;
padding-right: 0;
padding-left: 0;
}
.coupon-code {
font-size: 1.125em;
}
.coupon-applied {
color: $success;
font-size: 0.813em;
}
.coupon-not-applied {
color: $danger;
font-size: 0.813em;
}
.coupon-error {
color: $danger;
margin-top: 0.25rem;
}
.coupon-missing-error {
display: none;
}
.applied-promotion-discount {
color: $success;
float: right;
}
.promotion-information {
margin-bottom: 0.625rem;
margin-top: 0.625rem;
}
.line-item-header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.bundle-includes {
font-size: 0.813em;
margin-bottom: 0.625em;
}
.cart-page .bundled-line-item + .bundled-line-item::before,
.cart-page .bonus-line-item-row + .bonus-line-item-msg::before {
content: "";
display: block;
border-bottom: 0.063em dashed $horizontal-rule-grey;
margin: 0.625em -0.625em;
}
.quantity-label {
font-size: 0.813em;
}
.quantity {
width: 100%;
min-width: 5em;
}
.bundle-misc {
font-size: 0.813rem;
@include media-breakpoint-down(md) {
margin-bottom: 0.625em;
}
}
.cart-error-messaging.cart-error {
position: fixed;
top: 0;
width: 100%;
z-index: 2;
}
.valid-cart-error {
min-height: 6.5rem;
}
.bundled-line-item {
.item-attributes {
margin-left: 0;
}
}
.bonus-product-button {
margin-right: 1.5em;
}

View File

@@ -0,0 +1,225 @@
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
$checkout-font-weight: 600;
$receipt-font-size: 0.875rem;
$receipt-spacing: 0.625em;
$stored-payment-spacing: 1rem;
.page {
background-color: $light-gray;
}
.checkout-card-header {
font-size: 1.5rem;
}
.grand-total-price {
float: right;
font-weight: $checkout-font-weight;
}
.grand-total-label {
font-weight: $checkout-font-weight;
}
.grand-total {
font-size: 1.125rem !important;
font-weight: 600;
}
.order-receipt-label {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.product-divider {
margin-left: -$receipt-spacing;
margin-right: -$receipt-spacing;
hr {
border-top: dashed 0.063em;
}
}
.product-line-item + .product-line-item::before,
.multi-shipping + .product-line-item::before {
content: "";
display: block;
border-bottom: 0.063em dashed $horizontal-rule-grey;
margin: $receipt-spacing -0.625em;
@include media-breakpoint-up(lg) {
margin: $receipt-spacing -1.225em;
}
}
.shipment-block + .shipment-block::before {
content: "";
display: block;
border-bottom: 0.063em dashed $horizontal-rule-grey;
margin: $receipt-spacing -0.625em;
@include media-breakpoint-up(lg) {
margin: $receipt-spacing -1.225em;
}
}
.shipping-method {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.hero-confirmation {
background-image: url('../../images/thankyou.jpg');
background-position-y: -8.125em;
}
.product-summary-block {
margin: 1em 0;
h3 {
font-size: 1.25rem;
}
}
.leading-lines {
overflow: hidden;
margin: 0;
label {
background-color: white;
}
.start-lines {
padding: 1px;
span {
position: relative;
background-color: white;
z-index: 2;
}
&::before {
float: left;
width: 0;
white-space: nowrap;
content: ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ";
z-index: 1;
color: #adadad;
}
}
.end-lines {
padding: 1px;
span {
position: relative;
background-color: white;
z-index: 2;
}
}
}
.summary-details {
font-size: 0.938em;
margin-bottom: 1em;
}
.summary-details .address-summary {
margin-bottom: 0.5em;
}
.summary-section-label {
font-size: 1em;
font-weight: $checkout-font-weight;
@include media-breakpoint-up(lg) {
font-weight: 500;
}
}
.add-payment {
margin-top: $stored-payment-spacing;
}
.selected-payment {
background-color: $gray-200;
}
.saved-security-code {
margin-top: $stored-payment-spacing;
}
.saved-credit-card-type {
font-weight: 600;
}
.saved-payment-information {
margin-top: $stored-payment-spacing;
margin-bottom: $stored-payment-spacing;
}
.payment-information {
margin-bottom: $stored-payment-spacing;
}
.checkout-hidden {
display: none;
}
.card-image {
margin-top: 0.5rem;
width: 100%;
}
.cancel-new-payment {
margin-top: $stored-payment-spacing;
}
.form-check.start-lines {
padding-left: 1.5rem;
}
.multi-ship .single-shipping .shipping-content {
display: none;
}
.multi-ship .shipping-summary .single-shipping {
display: none;
}
.gift-message-block {
padding-bottom: 1em;
padding-top: 1em;
}
.single-shipping .summary-section-label {
margin-bottom: 0;
}
.confirm-details .shipping-method,
.confirm-details .shipping-method-price {
margin-bottom: 0;
}
.multi-ship .confirm-details .single-shipping {
display: none;
}
.multi-shipping {
display: none;
}
.contact-info-block {
border-bottom: 0.063em dashed $horizontal-rule-grey;
}
.view-address-block h3,
.shipping-method-block h3 {
font-size: 1.25rem;
}

View File

@@ -0,0 +1,339 @@
@import "../variables";
@import "bootstrap/scss/variables";
@import "checkoutComponents";
@import "bootstrap/scss/mixins/breakpoints";
@import "../components/formFields";
@import "../components/creditCardField";
$checkout-font-weight: 600;
.card.ghost {
opacity: 0.5;
}
.arrival-time {
white-space: pre;
}
.billing-address {
display: block;
}
.checkout-checkbox {
font-size: 0.875em;
}
.customer-information-block .btn-link {
color: #{var(--skin-link-color-2)};
padding: 0;
vertical-align: baseline;
}
.edit-button,
.btn-show-details,
.btn-add-new {
color: #{var(--skin-primary-color-1)};
float: right;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.edit-button {
border: none;
padding: 0;
background: none;
}
.error-message {
display: none;
}
.next-step-button {
position: fixed;
bottom: 0;
z-index: 3;
padding-right: 0;
padding-left: 0;
@include media-breakpoint-down(xs) {
background-color: rgba(255, 255, 255, 0.95);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
div {
padding: $spacer;
}
}
@include media-breakpoint-up(sm) {
position: static;
padding-right: 0.938em;
padding-left: 0.938em;
}
}
.shipping-methods {
font-size: 1.125em;
font-weight: $checkout-font-weight;
}
.shipping-method-option {
font-size: 0.938em;
}
.shipping-method-pricing {
font-weight: bolder;
}
.multi-ship .multi-shipping {
display: block;
}
span.ship-to-name,
span.ship-to-address1,
span.ship-to-address2,
span.ship-to-phone,
span.ship-to-city-st-zip {
display: block;
}
.data-checkout-stage {
// Initial states ------------------------
&[data-checkout-stage] {
.card.payment-summary,
.shipping-summary {
display: none;
}
button.place-order {
display: none;
}
button.submit-payment {
display: none;
}
button.submit-shipping {
display: none;
}
}
// Customer ------------------------------
&[data-checkout-stage=customer] {
.card.ghost.customer {
display: none;
}
.card.customer-summary {
display: none;
}
.card.shipping-section {
display: none;
}
.card.payment-form {
display: none;
}
button.submit-customer {
display: block;
}
}
// Shipping ------------------------------
&[data-checkout-stage=shipping] {
.card.customer-section {
display: none;
}
button.submit-customer {
display: none;
}
.card.ghost.customer {
display: none;
}
.card.ghost {
display: none;
}
&.multi-ship .order-product-summary {
display: none;
}
.card.payment-form {
display: none;
}
button.submit-shipping {
display: block;
}
.shipment-selector-block {
.btn-show-details,
.btn-add-new {
border: none;
}
}
[data-address-mode=customer] {
.shipping-address-block {
display: none;
}
}
[data-address-mode=shipment] {
.shipping-address-form {
display: none;
}
}
[data-address-mode=edit] {
.shipping-address-block {
display: none;
}
}
[data-address-mode=new] {
.btn-show-details,
.btn-add-new {
display: none;
}
}
}
// Payment -------------------------------
&[data-checkout-stage=payment] {
button.submit-customer {
display: none;
}
.customer-section,
.shipping-section,
.card.ghost {
display: none;
}
.card.payment-form,
.shipping-summary {
display: block;
}
button.submit-payment {
display: block;
}
.address-selector-block {
.btn-show-details,
.btn-add-new {
border: none;
}
}
[data-address-mode=customer] {
.billing-address {
display: none;
}
}
[data-address-mode=shipment] {
.billing-address {
display: none;
}
}
[data-address-mode=edit] {
.billing-address {
display: none;
}
}
[data-address-mode=new] {
.btn-show-details,
.btn-add-new {
display: none;
}
}
[data-address-mode=details] {
.btn-show-details,
.btn-add-new {
display: none;
}
}
}
// Place Order -----------------------------
&[data-checkout-stage=placeOrder] {
button.submit-customer,
.customer-section,
.shipping-section,
.card.payment-form,
.card.ghost {
display: none;
}
.card.payment-summary,
.shipping-summary {
display: block;
}
button.place-order {
display: block;
}
}
&[data-checkout-stage=submitted] {
.shipping-form,
.card.payment-form,
button.submit-customer,
.card.ghost {
display: none;
}
.summary-section-label.shipping-addr-label {
display: none;
}
.card.payment-summary,
.shipping-summary {
display: block;
}
button.place-order {
display: none;
}
}
option[value=new] {
display: none;
}
h5 > span {
font-size: 0.8em;
}
}
[data-customer-type=guest] .single-shipping .shipment-selector-block {
display: none;
}
.single-shipping .multi-ship-action-buttons {
display: none;
}
.single-shipping .view-address-block {
display: none;
}
.btn-show-details {
padding-top: 0;
}
.multi-ship-address-actions .btn-save-multi-ship {
margin-left: 10px;
}

View File

@@ -0,0 +1,19 @@
@import "../variables";
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "../components/formFields";
.login-oauth {
margin-top: 1rem;
}
.total-items-label {
font-weight: bold;
margin-bottom: 0.25rem;
}
.total-price {
font-weight: bold;
margin-bottom: 0.25rem;
}

View File

@@ -0,0 +1,4 @@
.container .breadcrumb {
border-radius: 0;
border-bottom: $border-width solid $grey3;
}

View File

@@ -0,0 +1,20 @@
.category-tile {
position: relative;
h1,
h2 {
font-size: 1.75rem;
position: absolute;
bottom: 1.875rem;
left: 1.875rem;
color: $white;
}
&::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
background-image: linear-gradient(to bottom, transparent 60%, rgba(0, 0, 0, 0.5) 100%);
}
}

View File

@@ -0,0 +1,46 @@
@each $size in map-keys($grid-breakpoints) {
@include media-breakpoint-down($size) {
.collapsible-#{$size} {
.title {
line-height: 2.5rem; /* 40/16 */
@include clearfix;
&::after {
float: right;
content: "\f078";
font-family: "FontAwesome";
}
}
.content,
.card-body {
display: none;
}
&.active {
.title::after {
content: "\f077";
margin-top: -0.125em; /* 2/16 */
}
.content,
.card-body {
display: block;
}
}
}
}
.container div.collapsible-#{$size} button.title {
color: $black;
text-decoration: none;
border: none;
background-color: transparent;
&:hover {
text-decoration: none;
}
}
}

View File

@@ -0,0 +1,86 @@
.modal-background {
background-color: $black;
display: none;
height: 100%;
position: fixed;
opacity: 0.5;
width: 100%;
top: 0;
left: 0;
}
input[placeholder] {
text-overflow: ellipsis;
}
header ~ #maincontent .container a:not(.btn-primary, .btn-outline-primary) {
color: #{var(--skin-link-color-2)};
}
.hide-order-discount {
display: none;
}
.hide-shipping-discount {
display: none;
}
.order-discount {
color: $success;
}
.shipping-discount {
color: $success;
}
.error-messaging {
position: fixed;
top: 0;
width: 100%;
z-index: 1;
}
.error-hero {
background-image: url('../images/storelocator.jpg');
margin-bottom: 0.625em;
}
.error-message {
margin-top: 3.125rem;
margin-bottom: 3.125rem;
}
.error.continue-shopping {
margin-bottom: 6.25em;
}
.error-unassigned-category {
color: $red;
}
.skip {
position: absolute;
left: 0;
top: -4.2em;
overflow: hidden;
padding: 1em 1.5em;
background: $white;
transition: all 0.2s ease-in-out;
}
a.skip:active,
a.skip:focus,
a.skip:hover {
left: 0;
top: 0;
width: auto;
height: auto;
z-index: 10000000;
background: $white;
transition: all 0.2s ease-in-out;
}
.card-header-custom {
font-size: 1.5rem;
margin-bottom: 0;
}

View File

@@ -0,0 +1,39 @@
.card-number-wrapper {
position: relative;
&::after {
content: '';
position: absolute;
right: 3px;
background-repeat: no-repeat;
background-image: url('../../images/credit.png');
background-size: contain;
width: 48px;
height: 30px;
top: 5px;
}
&[data-type="visa"]::after {
background-image: url('../../images/payment-types.png');
background-size: auto;
background-position: -162px -110px;
}
&[data-type="mastercard"]::after {
background-image: url('../../images/payment-types.png');
background-size: auto;
background-position: -295px -110px;
}
&[data-type="amex"]::after {
background-image: url('../../images/payment-types.png');
background-size: auto;
background-position: -230px -15px;
}
&[data-type="discover"]::after {
background-image: url('../../images/payment-types.png');
background-size: auto;
background-position: -95px -110px;
}
}

View File

@@ -0,0 +1,145 @@
@import "collapsibleItem";
@import "toastMessage";
footer {
background-color: $gray-200;
padding-top: 1.25em; /* 20/16 */
padding-bottom: 1.25em;
h2 {
font-size: $font-size-base;
margin-bottom: 0;
line-height: 2.5em; /* 40/16 */
}
ul {
list-style: none;
padding-left: 0;
}
.social {
h2 {
margin-top: 0;
}
@include clearfix;
}
.copyright,
.social {
margin-top: 1.25em;
}
.footer-container .footer-item.collapsible-xs button {
font-family: 'Dosis', sans-serif;
padding: 0;
}
.social-links {
@include clearfix;
@include media-breakpoint-down(xs) {
width: 80%;
}
float: left;
li {
float: left;
margin: 0.313em;
@include media-breakpoint-down(xs) {
width: 20%;
text-align: center;
}
}
a {
font-size: 2.25em;
&:hover {
text-decoration: none;
}
}
}
.store {
@include media-breakpoint-down(xs) {
border-bottom: 1px solid $dark-gray;
.content {
display: none;
}
h2 {
@include clearfix;
&::after {
font-family: "FontAwesome";
float: right;
content: "\f041";
}
}
}
}
.content {
font-size: 0.875em;
li {
height: 1.875rem;
}
}
.copyright,
.postscript {
font-size: 0.8125em;
}
.copyright {
margin-bottom: 0.625em;
}
.back-to-top {
margin: 0.1em 0.313em;
padding: 0;
background-color: transparent;
border: 0;
-webkit-appearance: none;
i {
&.fa-arrow-up {
color: rgba(0, 0, 0, 0.7);
}
&.fa-circle {
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
}
}
}
.footer-item {
@include media-breakpoint-down(xs) {
border-bottom: 1px solid $dark-gray;
}
}
#consent-tracking {
.button-wrapper {
button {
margin: 0.5em;
}
}
}
.email-signup-message {
@include toast-message();
}
.email-signup-alert {
@include toast-alert();
}

View File

@@ -0,0 +1,7 @@
.form-group {
&.required .form-control-label::before {
content: "*";
color: $danger;
}
}

View File

@@ -0,0 +1,197 @@
@import "menu";
$banner-padding: 0.3125em;
$menu-padding: 0.5em;
$menu-item-margin: 0.625em;
.header {
position: relative;
}
.navbar-header {
height: 4.375em; /* 70/16 */
.user,
.country-selector,
.search,
.minicart {
display: inline-block;
margin: 1.125em 0 0 0.5em;
}
.user,
.country-selector,
.minicart,
.navbar-toggler {
line-height: 2.25em; /* 36/16 */
height: auto;
}
.navbar-toggler {
font-size: 1.6em;
width: auto;
}
.user {
position: relative;
.popover {
position: absolute;
display: none;
padding: 1em;
top: 85%;
left: 0;
a {
white-space: nowrap;
margin-bottom: 0.5em;
}
&::before {
left: 1.5rem;
}
&::after {
left: 1.5rem;
}
&.show {
display: block;
}
}
}
}
.brand {
position: absolute;
left: 50%;
display: block;
text-align: center;
img {
width: 100%;
}
@include media-breakpoint-up(lg) {
width: 14.125em; /* 226/16 */
margin-left: -7.0625em; /* 113/16 */
padding-top: 0.5em;
}
@include media-breakpoint-down(md) {
width: 4em; /* 64/16 */
margin-left: -2em;
padding-top: 0.8em;
}
}
.main-menu {
background-color: $dark-gray;
.navbar .close-menu button,
.navbar .close-button button {
background-color: transparent;
border: 0;
-webkit-appearance: none;
}
}
.header-banner {
background-color: $darker-gray;
text-align: center;
color: $white;
.close-button {
width: 1.5em + $banner-padding * 2;
.close {
opacity: 1;
color: $white;
width: 100%;
height: 100%;
background-color: #{var(--skin-primary-color-1)};
}
}
.content {
margin-right: 1.5em;
padding-top: $banner-padding;
padding-bottom: $banner-padding;
}
}
.minicart {
margin-top: 0.1875em; /* 3/16 */
vertical-align: top;
.minicart-icon {
font-size: 1.5em;
}
a.minicart-link {
&:hover {
text-decoration: none;
}
}
.minicart-quantity {
background-color: #{var(--skin-primary-color-1)};
border-radius: 50%;
width: 1.25em; /* 20/16 */
height: 1.25em; /* 20/16 */
line-height: normal;
display: inline-block;
text-align: center;
font-size: 0.8125em; /* 13/16 */
position: relative;
top: -0.9375em; /* 15/16 */
left: -0.9375em; /* 15/16 */
color: $white;
}
}
a.normal {
color: #{var(--skin-primary-color-1)};
text-decoration: underline;
}
.slide-up {
transition-duration: 0.5s;
transition-timing-function: ease-in;
max-height: 100px;
overflow: hidden;
&.hide {
max-height: 0;
padding-top: 0;
padding-bottom: 0;
margin-top: 0;
margin-bottom: 0;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
}
.dropdown-country-selector {
margin-top: -0.0625em;
}
.cookie-warning-messaging.cookie-warning {
position: fixed;
bottom: 0;
left: 50%;
transform: translate(-50%, 0);
text-align: center;
display: none;
}
.valid-cookie-warning {
background-color: #{var(--skin-link-color-1)};
color: $white;
white-space: nowrap;
p {
margin-top: 0;
margin-bottom: 0.2em;
padding-right: 2em;
}
}

View File

@@ -0,0 +1,149 @@
@import "../utilities/swatch";
@import "../variables";
.site-search {
position: relative;
height: 2.5em; /* 40/16 */
@include media-breakpoint-up(sm) {
margin-right: 0.5em; /* 20/16 */
}
@include media-breakpoint-up(xl) {
width: 20em; /* 320/16 */
}
@include media-breakpoint-only(md) {
width: 14.0625em; /* 225/16 */
}
@include media-breakpoint-only(sm) {
width: 12.5em; /* 200/16 */
}
.fa-close,
.fa-search {
position: absolute;
border: none;
top: 0.5625em; /* 9/16 */
right: 0.5625em; /* 9/16 */
padding: 0;
background-color: transparent;
}
.reset-button {
position: absolute;
border: none;
top: 0.5625em; /* 9/16 */
right: 2em; /* 32/16 */
padding: 0;
background-color: transparent;
}
input {
padding-right: 2rem;
height: 100%;
}
}
@include media-breakpoint-down(xs) {
.header-search {
.site-search {
display: none;
}
}
}
.suggestions-wrapper {
position: relative;
}
.suggestions {
display: block;
position: absolute;
border: 1px solid $grey3;
background-color: $white;
top: 0;
right: 0;
width: 21.875rem;
z-index: 3;
@include media-breakpoint-only(xs) {
display: flex;
position: fixed;
width: 100%;
}
.swatch-circle {
@include swatch(2.5em, $white);
}
.header {
color: $grey4;
font-size: 0.875em;
padding-top: 0.625em;
&:not(:first-child) {
border-top: 1px solid $grey3;
}
}
.items {
padding: 0.313em 0;
}
.item {
padding-bottom: 0.625em;
.name {
margin-top: 0.313em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@include media-breakpoint-down(xs) {
padding-bottom: 0.938em;
}
}
.category-parent {
color: $grey4;
font-size: 0.875em;
}
.selected {
background-color: $grey2;
}
.container {
list-style-type: none;
}
}
.more-below {
-moz-border-radius: 1.25em;
background: $grey3;
border: 0.063em solid rgba(0, 0, 0, 0.1);
border-radius: 1.25em;
bottom: 1.875em;
box-shadow: 0 1px 7px rgba(0, 0, 0, 0.3);
display: none;
height: 2.5em;
position: fixed;
right: 1.875em;
width: 2.5em;
i.fa-long-arrow-down {
border-radius: 50%;
color: $white;
display: table-caption;
height: 0.75em;
font-size: 1.5rem;
left: 0.57em;
line-height: 0.8em;
position: absolute;
top: 0.4em;
width: 0.8em;
}
}

View File

@@ -0,0 +1,8 @@
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Dosis', sans-serif;
}

View File

@@ -0,0 +1,104 @@
@import "../variables";
.hero {
height: 25vw;
background-size: cover;
background-position: 50%;
position: relative;
h1.page-title {
top: 50%;
margin: -1em 0 0;
}
}
.slant-down {
@include media-breakpoint-up(sm) {
&::after {
content: "";
position: absolute;
bottom: 0;
right: 0;
width: 0;
height: 0;
border: 0 solid transparent;
border-right-width: 0;
border-left-width: 90vw;
border-bottom: 4vw solid $body-bg;
}
}
}
.slant-up {
@include media-breakpoint-up(sm) {
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
border: 0 solid transparent;
border-left-width: 0;
border-right-width: 90vw;
border-top: 4vw solid $body-bg;
}
}
}
h1.page-title {
position: relative;
color: white;
padding: 0.3125em 0.625em 0.3125em $grid-gutter-width / 2;
background-color: #{var(--skin-primary-color-1)};
display: inline-block;
margin: 0.9375em 0;
font-size: 1.5rem;
@include media-breakpoint-up(sm) { font-size: 2rem; }
@include media-breakpoint-up(md) { font-size: 3rem; }
&::before {
content: "";
background-color: #{var(--skin-primary-color-1)};
height: 100%;
width: 0;
position: absolute;
left: 0;
top: 0;
@include media-breakpoint-only(xl) {
width: calc((100vw - #{map-get($container-max-widths, xl)}) / 2);
left: calc((100vw - #{map-get($container-max-widths, xl)}) / 2 * -1);
}
@include media-breakpoint-only(lg) {
width: calc((100vw - #{map-get($container-max-widths, lg)}) / 2);
left: calc((100vw - #{map-get($container-max-widths, lg)}) / 2 * -1);
}
@include media-breakpoint-only(md) {
width: calc((100vw - #{map-get($container-max-widths, md)}) / 2);
left: calc((100vw - #{map-get($container-max-widths, md)}) / 2 * -1);
}
}
@include media-breakpoint-only(xl) {
left: calc((100% - #{map-get($container-max-widths, xl)}) / 2);
}
@include media-breakpoint-only(lg) {
left: calc((100% - #{map-get($container-max-widths, lg)}) / 2);
}
@include media-breakpoint-only(md) {
left: calc((100% - #{map-get($container-max-widths, md)}) / 2);
}
@include media-breakpoint-down(sm) {
left: 0;
}
}

View File

@@ -0,0 +1,227 @@
$breakpoint-name: 'sm';
$breakpoint-name: 'sm' !default;
$breakpoint-index: index(map-keys($grid-breakpoints), $breakpoint-name);
$prev-breakpoint: nth(map-keys($grid-breakpoints), $breakpoint-index - 1);
$next-breakpoint: nth(map-keys($grid-breakpoints), $breakpoint-index + 1);
$slide-out-animation: left 0.5s cubic-bezier(0, 1, 0.5, 1);
@mixin caret-left() {
border-top: 0.3em solid transparent;
border-bottom: 0.3em solid transparent;
border-right: 0.3em solid;
border-left: 0.3 solid transparent;
width: 0;
height: 0;
display: inline-block;
margin-bottom: 0.125em;
}
@mixin caret-right() {
border-top: 0.3em solid transparent;
border-bottom: 0.3em solid transparent;
border-left: 0.3em solid;
position: absolute;
right: 0.3em;
margin-top: 0.55em;
}
@each $size in map-keys($grid-breakpoints) {
@include media-breakpoint-down($size) {
.menu-toggleable-left.navbar-toggleable-#{$size} {
position: fixed;
left: -100%;
top: 0;
bottom: 0;
transition: $slide-out-animation;
display: block;
max-width: 100%;
&.in {
min-width: 50%;
left: 0;
}
}
}
}
.navbar.bg-inverse {
background-color: transparent !important;
padding: 0;
@include media-breakpoint-up($next-breakpoint) {
.navbar-nav .nav-item + .nav-item {
margin-left: 0;
}
.navbar-nav .nav-link {
padding: 0.8rem;
white-space: nowrap;
}
}
}
.navbar-expand-md .navbar-nav.nav-center {
justify-content: center;
}
.navbar-expand-md .navbar-nav.nav-spaced {
justify-content: space-evenly;
}
.navbar-expand-md .navbar-nav.nav-right {
justify-content: end;
}
.nav-item .nav-link:hover,
.nav-item .nav-link:focus,
.nav-item.show .nav-link {
background-color: $white;
color: $menu-link;
}
@include media-breakpoint-up($next-breakpoint) {
.nav-item > .nav-link {
color: $white;
}
}
.main-menu.menu-toggleable-left {
@include media-breakpoint-down($breakpoint-name) {
background-color: $white;
z-index: 4;
}
}
.menu-toggleable-left {
.close-menu {
padding: 15px;
background-color: $close-menu-bg;
border-bottom: 1px solid $grey3;
flex: 0 0 100%;
@include media-breakpoint-up($next-breakpoint) {
display: none;
}
}
.menu-group {
flex: 0 0 100%;
}
li > .close-menu {
margin-right: 0;
margin-top: -0.6rem;
margin-left: 0;
}
@include media-breakpoint-down($breakpoint-name) {
.bg-inverse {
background-color: white !important;
color: $grey7;
}
}
&.in {
@include media-breakpoint-down($prev-breakpoint) {
right: 0;
margin-right: 1.25em; /* 20/16 */
}
@include media-breakpoint-down($breakpoint-name) {
.nav-item + .nav-item {
border-top: 1px solid $grey2;
}
.dropdown {
display: block;
position: static;
}
.dropdown-toggle {
padding-left: 1rem;
&::after {
@include caret-right();
}
}
.nav-item .nav-link {
padding-left: 1rem;
}
.show > .dropdown-menu {
left: 0;
}
.dropdown-menu {
position: absolute;
left: -100%;
top: 0;
width: 100%;
height: 100%;
border: 0 none;
transition: $slide-out-animation;
display: block;
}
}
}
}
.multilevel-dropdown {
.dropdown-menu {
top: 90%;
border: 0;
border-radius: 0;
@include media-breakpoint-up($next-breakpoint) {
box-shadow: 0 3px 5px rgba(43, 36, 25, 0.4);
}
}
.dropdown-item.dropdown > .dropdown-toggle::after {
@include caret-right();
}
.dropdown-menu > .dropdown > .dropdown-menu {
@include media-breakpoint-up($next-breakpoint) {
top: -0.65em;
left: 99%;
}
}
.navbar > .close-menu > .back {
display: none;
}
.close-menu .back {
.caret-left {
@include caret-left();
}
}
.dropdown-item {
padding: 0 0 0 1em;
.dropdown-link {
display: block;
padding: 0.425em 5em 0.425em 0;
}
+ .dropdown-item {
border-top: 1px solid $close-menu-bg;
}
&.top-category {
font-weight: bold;
> .nav-link {
padding-left: 0;
}
}
}
}

View File

@@ -0,0 +1,66 @@
@import "../variables";
.price {
color: $grey7;
.strike-through {
text-decoration: line-through;
color: $grey4;
margin-right: 0.938rem;
}
.starting,
.range,
.sales {
font-weight: bold;
}
.tiered {
color: $grey7;
table {
border-top: 1px solid $grey3;
margin: 0 auto;
tr {
&:nth-child(odd) {
background-color: $grey2;
}
}
}
td,
span.price {
font-size: 0.875rem;
font-weight: bold;
}
td {
padding: 0.313rem;
&.quantity {
font-weight: normal;
text-align: right;
}
&.value {
text-align: left;
}
}
.table-header {
font-size: 1.125rem;
padding: 0.313rem;
}
.column-header {
font-size: 1rem;
padding: 0.313rem;
font-weight: normal;
}
.column-header.quantity {
text-align: right;
}
}
}

View File

@@ -0,0 +1,112 @@
@import "toastMessage";
.attribute {
margin-top: 0.938em;
label {
display: block;
}
}
.swatch a {
text-decoration: none;
}
.primary-images {
@include media-breakpoint-down(xs) {
margin: 0;
padding: 0;
}
}
.prices-add-to-cart-actions {
width: 100%;
position: fixed;
bottom: 0;
z-index: 50;
padding-right: 0;
padding-left: 0;
left: 0;
@include media-breakpoint-down(xs) {
background-color: rgba(255, 255, 255, 0.95);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
@include media-breakpoint-up(sm) {
position: static;
padding: 0 0.9375em;
}
.price {
text-align: center;
}
}
.prices {
padding-bottom: 0.5em;
padding-top: 0.5em;
text-align: center;
}
.cart-and-ipay {
text-align: center;
@include media-breakpoint-only(xs) {
padding-bottom: 26px;
.btn {
width: 98%;
margin: 1%;
display: block;
}
}
}
.add-to-cart-messages {
@include toast-message();
}
.add-to-basket-alert {
@include toast-alert();
}
.simple-quantity {
margin-top: 1em;
}
.main-attributes {
margin-top: 1em;
}
.size-chart {
margin-top: 1.071em;
}
div.availability {
margin-top: 1.071em;
}
.bundle-item {
padding-bottom: 1em;
border-bottom: 1px solid $hr-border-color;
&:last-child {
border-bottom: none;
}
}
.container.product-detail {
margin-top: 2em;
margin-bottom: 2em;
@include media-breakpoint-only(xs) {
margin-top: 0;
margin-bottom: 0;
}
}
.product-option:not(:first-child) {
margin-top: 1.071em;
}

View File

@@ -0,0 +1,176 @@
@import "../utilities/swatch";
@import "productCommon";
@import "quickView";
.product-tile {
@include media-breakpoint-down(md) {
min-height: 23.4375em;
}
@include media-breakpoint-down(sm) {
min-height: 19.6875em;
}
@include media-breakpoint-down(xs) {
min-height: 13.4375em;
}
border: 0;
margin-bottom: 0;
.tile-body {
padding: 0.625em 0 1.875em;
.color-swatches {
min-height: 2.25em;
.product-tile-color-label {
cursor: pointer;
font-size: 1em;
@include media-breakpoint-down(md) {
font-size: 0.9375em;
}
@include media-breakpoint-down(sm) {
font-size: 0.8125em;
}
}
}
.price {
font-size: 1.125em;
margin-bottom: 0;
@include media-breakpoint-down(md) {
font-size: 1.0625em;
}
@include media-breakpoint-down(sm) {
font-size: 1em;
}
.tiered {
font-size: 0.875em;
.value {
font-weight: bold;
}
}
}
.coming-soon-tile {
text-align: center;
}
.pdp-link {
line-height: 1.2;
a {
font-size: 1em;
text-decoration: none;
@include media-breakpoint-down(md) {
font-size: 0.9375em;
}
@include media-breakpoint-down(sm) {
font-size: 0.8125em;
}
}
}
.ratings {
font-size: 0.9em;
}
}
.image-container {
position: relative;
overflow: auto;
.quickview {
position: absolute;
bottom: 1rem;
right: 1rem;
i {
&.fa-expand {
color: rgba(0, 0, 0, 0.7);
}
&.fa-circle {
color: rgba(255, 255, 255, 0.7);
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
}
}
a {
display: block;
.tile-image {
width: 100%;
}
}
}
.swatches {
a {
text-decoration: none;
}
}
.swatch-circle {
// $white will be replaced by color image background via Javascript
@include swatch(1.8em, $white);
}
}
#chooseBonusProductModal {
.modal-footer {
.container {
margin-left: 0;
width: 100%;
margin-right: 0;
}
}
.select-cbp-container {
margin-top: auto;
margin-bottom: auto;
}
.product-name-wrapper {
width: 100%;
}
.bonus-quantity,
.bonus-option {
margin-top: 0.938em;
}
.bonus-quantity-select {
min-width: 5em;
}
.select-bonus-product {
margin-top: 1em;
}
.selected-pid {
border: 1px solid $grey3;
.bonus-product-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.bonus-product-price {
text-align: center;
margin-top: 1em;
}
}

View File

@@ -0,0 +1,124 @@
@import "../utilities/swatch";
@import "productCommon";
.quick-view-dialog,
.choose-bonus-product-dialog {
max-width: 56.25em;
.selectable-bonus-product-line-item {
margin-top: 0.2em;
margin-bottom: 0.2em;
padding-top: 0.3em;
padding-bottom: 0.3em;
}
.beenSelected {
background-color: $grey2;
}
.modal-header {
background-color: $grey2;
border-bottom: 2px solid #ccc;
border-top-left-radius: 0.1875rem;
border-top-right-radius: 0.1875rem;
.full-pdp-link {
color: #{var(--skin-primary-color-1)};
}
.close {
font-size: 2rem;
line-height: 1.5rem;
}
}
.modal-title {
font-size: 1em;
}
.product-name {
font-size: 1.875em;
}
.swatch-circle {
// $white will be replaced by color image background via Javascript
@include swatch(2.5em, $white);
}
a[disabled] .swatch-circle {
cursor: not-allowed;
&.color-value.selected::after {
background-color: $gray-700;
}
}
.availablity-container {
text-align: right;
}
.availablity-container,
.size-chart {
margin-top: 0.938em;
}
.modal-content {
border: 1px solid rgba(0, 0, 0, 0.2);
}
.modal-body {
max-height: 28.125em; /* 450/16 */
overflow-y: auto;
}
button.close {
font-size: 1.25em;
}
.modal-footer {
background-color: $white;
border: none;
border-bottom-right-radius: 0.1875rem;
border-bottom-left-radius: 0.1875rem;
.prices .price {
font-size: 1.6em;
}
}
.prices .sales {
font-size: 1.5rem;
}
.promotions {
text-align: left;
color: $red;
}
.bonus-summary {
@include media-breakpoint-down(sm) {
font-size: 0.625em;
}
.bonus-product-name {
@include media-breakpoint-down(sm) {
padding: 0;
}
}
}
.pre-cart-products {
margin-right: 0.125em;
}
.color-attribute {
border: none;
padding: 0;
background: none;
}
.non-input-label {
display: block;
margin-bottom: 0.5rem;
}
}

View File

@@ -0,0 +1,61 @@
.veil {
position: absolute;
z-index: 100;
text-align: center;
top: 0;
left: 0;
width: 100%;
height: 100%;
.underlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.5;
background-color: $black;
}
}
$spinner-size: 80px;
.spinner {
width: $spinner-size;
height: $spinner-size;
text-align: center;
animation: sk-rotate 2s infinite linear;
position: absolute;
top: 50%;
left: 50%;
margin-top: $spinner-size / 2 * -1;
margin-left: $spinner-size / 2 * -1;
}
.dot1,
.dot2 {
width: 60%;
height: 60%;
display: inline-block;
position: absolute;
top: 0;
background-color: $white;
border-radius: 100%;
animation: sk-bounce 2s infinite ease-in-out;
}
.dot2 {
top: auto;
bottom: 0;
animation-delay: -1s;
}
@keyframes sk-rotate {
100% { transform: rotate(360deg); }
}
@keyframes sk-bounce {
0%,
100% { transform: scale(0); }
50% { transform: scale(1); }
}

View File

@@ -0,0 +1,24 @@
@import "../variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
.form-nav .nav-tabs .nav-link {
color: $grey8;
}
.form-nav .nav-tabs .nav-link.active,
.form-nav .nav-tabs .nav-link.active:focus,
.form-nav .nav-tabs .nav-link.active:hover {
background-color: $white;
color: $grey8;
}
.nav-tabs .nav-link.active {
background-color: transparent;
}
@include media-breakpoint-up(lg) {
.form-nav .nav-item > .nav-link {
color: $grey8;
}
}

View File

@@ -0,0 +1,23 @@
@mixin toast-message() {
transform: translate(-50%, -50%);
position: fixed;
top: 15%;
left: 50%;
}
@mixin toast-alert() {
animation: fade 5s linear forwards;
box-shadow: 1px 1px 5px grey;
padding: 1em;
@keyframes fade {
0% { opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { opacity: 0; }
}
&.show {
display: block;
}
}

View File

@@ -0,0 +1,64 @@
$arrow-height: 1.25em; /* 20/16 */
.info-icon {
position: relative;
cursor: pointer;
display: inline-block;
border: none;
padding: 0;
background: inherit;
.icon {
border-radius: 1.25rem;
background-color: #{var(--skin-primary-color-1)};
width: 1.5625rem;
display: inline-block;
text-align: center;
color: #fff;
font-weight: 600;
}
.tooltip {
position: absolute;
bottom: 100%;
padding: 0.312rem;
border-radius: $border-radius;
background-color: $grey6;
color: $white;
font-size: 0.928rem;
min-width: 20rem;
max-width: 15rem;
transform: translate(-50%, -$arrow-height/2);
left: 50%;
margin-left: 4px;
animation: fade-in 0.5s linear forwards;
@keyframes fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
&::before {
content: " ";
position: absolute;
display: block;
height: $arrow-height; /* 20/16 */
left: 0;
bottom: -$arrow-height;
width: 100%;
}
&::after {
border-left: solid transparent $arrow-height/2; /* 10/16 */
border-right: solid transparent $arrow-height/2; /* 10/16 */
border-top: solid $grey6 $arrow-height/2;
bottom: -$arrow-height/2;
content: " ";
height: 0;
left: 50%;
margin-left: -13px;
position: absolute;
width: 0;
}
}
}

View File

@@ -0,0 +1,18 @@
@import "variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
@import "components/formFields";
@import "components/toastMessage";
.contact-us-banner {
background-image: url('../images/contact-us.jpg');
background-position-y: 20%;
}
.contact-us-signup-message {
@include toast-message();
}
.contact-us-signup-alert {
@include toast-alert();
}

View File

@@ -0,0 +1,17 @@
@import "variables";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins/breakpoints";
.sorry-hero {
background-image: url('../images/storelocator.jpg');
margin-bottom: 0.625em;
}
.error-message {
margin-bottom: 3.125rem;
}
.error-page-content {
margin-top: 3.125rem;
margin-bottom: 6.25em;
}

Some files were not shown because too many files have changed in this diff Show More