1var ProductStateManager = new function () { 2 3 var customSaleShieldTargets = []; 4 var customSaleStateByPid = {}; 5 var pendingProductStateRequests = {}; 6 var productStateBatch = { current: null }; 7 var fullProductStateByPid = {}; 8 var pendingFullProductStateRequests = {}; 9 var fullProductStateBatch = { current: null }; 10 11 var uniqueValues = function (arr) { 12 var seen = {}, result = []; 13 for (var i = 0; i < arr.length; i++) { 14 var key = arr[i]; 15 if (!seen.hasOwnProperty(key)) { 16 seen[key] = true; 17 result.push(key); 18 } 19 } 20 return result; 21 }; 22 23 var escapeHtml = function (str) { 24 return str == null ? str : String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); 25 }; 26 27 var applyCustomSaleShield = function (priceWrap, insertAnchor, customSalePromo) { 28 var coupon = priceWrap.find('.prod-coupon[data-custom-sale]'); 29 30 var discountLabel = priceWrap.find('.promo-disc-label'); 31 if (!discountLabel.length) { 32 discountLabel = priceWrap.parent().find('.promo-disc-label'); 33 } 34 discountLabel.toggleClass('hidden', !!customSalePromo); 35 36 if (!customSalePromo) { 37 coupon.remove(); 38 return; 39 } 40 41 priceWrap.find('.prod-coupon').not(coupon).remove(); 42 43 var amount = CurrencyUtils.formatAsCurrency(customSalePromo.promoPrice), 44 shieldText = escapeHtml(customSalePromo.shieldText); 45 46 if (!coupon.length) { 47 insertAnchor.after( 48 '<div class="prod-coupon" data-custom-sale="1"><div class="prod-coupon__inner">' 49 + '<div class="prod-coupon__amount">' + amount + '</div>' 50 + '<div class="prod-coupon__code"><span class="code-label">' + shieldText + '</span></div>' 51 + '</div></div>' 52 ); 53 return; 54 } 55 56 coupon.find('.prod-coupon__amount').html(amount); 57 coupon.find('.prod-coupon__code').html('<span class="code-label">' + shieldText + '</span>'); 58 }; 59 60 var renderCustomSaleShields = function () { 61 $.each(customSaleShieldTargets, function (i, target) { 62 $(target.ctrlsSelector).each(function () { 63 var ctrl = $(this), 64 pid = ctrl.data('pid'); 65 66 if (!customSaleStateByPid.hasOwnProperty(pid)) { 67 return; 68 } 69 70 var priceWrap = target.findPriceWrap(ctrl); 71 if (!priceWrap.length) { 72 return; 73 } 74 75 var state = customSaleStateByPid[pid]; 76 applyCustomSaleShield(priceWrap, target.findInsertAnchor(priceWrap), state && state.customSalePromo); 77 }); 78 }); 79 }; 80 81 var processCustomSaleShieldTarget = function (target, container) { 82 var cont = container ? $(container) : target.defaultContainer(); 83 var ctrls = cont.find(target.ctrlsSelector).filter(function () { 84 return !$(this).data('customSaleChecked'); 85 }); 86 87 if (!ctrls.length) { 88 return; 89 } 90 91 ctrls.data('customSaleChecked', true); 92 93 var pidsToFetch = ctrls.map(function () { 94 return $(this).data('pid'); 95 }).get().filter(function (pid) { 96 return !customSaleStateByPid.hasOwnProperty(pid); 97 }); 98 99 if (pidsToFetch.length) { 100 getProductsState(pidsToFetch, $.noop); 101 } else { 102 renderCustomSaleShields(); 103 } 104 }; 105 106 var rescanCustomSaleShieldTargets = function () { 107 $.each(customSaleShieldTargets, function (i, target) { 108 processCustomSaleShieldTarget(target); 109 }); 110 }; 111 112 var debouncedCustomSaleRescan = (function () { 113 var timer = null; 114 return function () { 115 clearTimeout(timer); 116 timer = setTimeout(rescanCustomSaleShieldTargets, 150); 117 }; 118 })(); 119 120 document.addEventListener('DOMContentLoaded', function () { 121 rescanCustomSaleShieldTargets(); 122 123 if (window.MutationObserver) { 124 new MutationObserver(debouncedCustomSaleRescan).observe(document.body, { childList: true, subtree: true });
125 } 126 }); 127 128 var getCartButtonsElements = function (cartButtons) { 129 var objitemEl = cartButtons.parent(); 130 return { 131 objitem: objitemEl, 132 infoIcon: cartButtons.find('.prev_info'), 133 compareBtn: cartButtons.find('.cmpricon, .mc-icon_compare'), 134 cartStatusIcon: cartButtons.find('.carticon'), 135 wishlistBtn: cartButtons.find('.item_addtowishlist, .mc-icon_wishlist'), 136 shoppingCartSaleMarketingGroupId: objitemEl.find('.jsScsmkid').val() 137 }; 138 } 139 140 var setProductButtonsState = function (cartButtons, productState) { 141 var btns = getCartButtonsElements(cartButtons); 142 143 if (productState.isInCompare) { 144 btns.compareBtn.removeClass('item_addtocompare').addClass('item_removefromcompare'); 145 } 146 147 if (productState.isInWishList) { 148 btns.wishlistBtn.removeClass('add_wishlist').addClass('added_wishlist'); 149 } 150 151 if (productState.status != "Normal") { 152 btns.cartStatusIcon.removeClass('item_addtocart').addClass('oos_status'); 153 btns.objitem.addClass('product-item__oos'); 154 } else if (productState.isInCart) { 155 btns.cartStatusIcon.removeClass('item_addtocart').addClass('item_removefromcart'); 156 } 157 158 return btns; 159 }; 160 161 var restoreButtonsState = function (cartButtons, productState) { 162 163 if (cartButtons.data('state-restored')) { 164 return null; 165 } 166 167 var objitem = cartButtons.parent(), 168 infoIcon = cartButtons.find('.prev_info'), 169 compareBtn = cartButtons.find('.cmpricon, .mc-icon_compare'), 170 cartStatusIcon = cartButtons.find('.carticon'), 171 wishlistBtn = cartButtons.find('.item_addtowishlist, .mc-icon_wishlist'), 172 shoppingCartSaleMarketingGroupId = objitem.find('.jsScsmkid').val(); 173 174 if (productState.isInCompare) { 175 compareBtn.removeClass('item_addtocompare').addClass('item_removefromcompare'); 176 } 177 178 if (productState.isInWishList) { 179 wishlistBtn.removeClass('add_wishlist').addClass('added_wishlist'); 180 } 181 182 if (productState.status != "Normal") { 183 cartStatusIcon.removeClass('item_addtocart').addClass('oos_status'); 184 objitem.addClass('product-item__oos'); 185 } else if (productState.isInCart) { 186 cartStatusIcon.removeClass('item_addtocart').addClass('item_removefromcart'); 187 } 188 189 190 if (productState.hasSpecialPrice && productState.status == "Normal") { 191 var productInfoBlock = cartButtons.parent().find(".product-item-visual"); 192 productInfoBlock = productInfoBlock.length ? productInfoBlock : cartButtons.parents(".product-item-visual, .product-card-small"); 193 if (productInfoBlock.length) { 194 if (productInfoBlock.find(".special-price").length === 0) { 195 productInfoBlock.append(UTILS.getSpecialOfferLabelMarkup()); 196 } 197 } 198 } 199 200 cartButtons.data('state-restored', true); 201 cartButtons.removeClass('dynamic-state'); 202 203 return ProdCollection.add({ 204 objitem: objitem, 205 pid: productState.pid, 206 infoIcon: infoIcon, 207 masterpid: productState.masterPid, 208 compareIcon: compareBtn, 209 cartPStatusIcon: cartStatusIcon, 210 wishListIcon: wishlistBtn, 211 stockStatus: productState.status, 212 shoppingCartPromotion: shoppingCartSaleMarketingGroupId ? { promotionMarketingGroupId: shoppingCartSaleMarketingGroupId } : null 213 }); 214 }; 215 216 var PID_BATCH_MAX_RETRIES = 1; 217 var PID_BATCH_WINDOW_MS = 20; 218 219 var sendPidBatchRequest = function (cache, pending, buildAjaxConfig, batch, retriesLeft) { 220 $.ajax($.extend(buildAjaxConfig(batch.pids), { 221 success: function (resp) { 222 $.each(resp || [], function (i, item) { 223 cache[item.pid] = item; 224 }); 225 }, 226 complete: function (jqXHR) { 227 if (jqXHR.status !== 200 && retriesLeft > 0) { 228 sendPidBatchRequest(cache, pending, buildAjaxConfig, batch, retriesLeft - 1); 229 return; 230 } 231 232 $.each(batch.pids, function (i, pid) { 233 if (pending[pid] === batch.deferred) {
234 delete pending[pid]; 235 } 236 }); 237 batch.deferred.resolve(); 238 } 239 })); 240 }; 241 242 var flushPidBatch = function (cache, pending, batchHolder, buildAjaxConfig) { 243 var batch = batchHolder.current; 244 batchHolder.current = null; 245 246 $.each(batch.pids, function (i, pid) { 247 pending[pid] = batch.deferred; 248 }); 249 250 sendPidBatchRequest(cache, pending, buildAjaxConfig, batch, PID_BATCH_MAX_RETRIES); 251 }; 252 253 var fetchPidsWithCoalescing = function (pids, cache, pending, batchHolder, buildAjaxConfig, callback, onSettled) { 254 var uniquePids = uniqueValues(pids), 255 waitFor = []; 256 257 $.each(uniquePids, function (i, pid) { 258 if (cache.hasOwnProperty(pid)) { 259 return; 260 } 261 if (pending.hasOwnProperty(pid)) { 262 waitFor.push(pending[pid]); 263 return; 264 } 265 266 if (!batchHolder.current) { 267 batchHolder.current = { pids: [], deferred: $.Deferred() }; 268 setTimeout(function () { 269 flushPidBatch(cache, pending, batchHolder, buildAjaxConfig); 270 }, PID_BATCH_WINDOW_MS); 271 } 272 273 if ($.inArray(pid, batchHolder.current.pids) === -1) { 274 batchHolder.current.pids.push(pid); 275 } 276 277 waitFor.push(batchHolder.current.deferred); 278 }); 279 280 var onSettledInternal = function () { 281 onSettled && onSettled(); 282 283 callback && callback(uniquePids.map(function (pid) { 284 return cache[pid]; 285 }).filter(function (item) { 286 return !!item; 287 })); 288 }; 289 290 return $.when.apply($, waitFor).done(onSettledInternal).fail(onSettledInternal); 291 }; 292 293 var getProductsState = function (pids, callback) { 294 return fetchPidsWithCoalescing(pids, customSaleStateByPid, pendingProductStateRequests, productStateBatch, function (pidsToRequest) { 295 return { 296 url: '/ajax/product/ProductState.aspx', 297 data: { pids: pidsToRequest.join(',') }, 298 cache: false 299 }; 300 }, callback, renderCustomSaleShields); 301 }; 302 303 var getFullProductVariations = function (pids, callback) { 304 return fetchPidsWithCoalescing(pids, fullProductStateByPid, pendingFullProductStateRequests, fullProductStateBatch, function (pidsToRequest) { 305 return { 306 url: '/ajax/htabs/exec.aspx', 307 dataType: 'json', 308 data: { 309 op: 'vsd', 310 pids: pidsToRequest.join(','), 311 soosp: false 312 } 313 }; 314 }, callback); 315 }; 316 317 return { 318 syncCartButtonsState: function (context, onCompleteCallback, pidSrc) { 319 var pidsList = [], 320 pidSrcSelector = pidSrc || '.cart_btns.dynamic-state'; 321 322 $(pidSrcSelector, context || document).each(function () { 323 var el = $(this); 324 if (el.data('state-restored')) { 325 return true; 326 } 327 pidsList.push(el.data('pid')); 328 }); 329 330 if (!pidsList.length) { 331 return; 332 } 333 getProductsState(pidsList, function (productStateList) { 334 for (var i = 0, len = productStateList.length; i < len; i++) { 335 var prodState = productStateList[i], 336 cartButtonsCont = $('div[data-pid="' + prodState.pid + '"]'); 337 338 cartButtonsCont.each(function() { 339 restoreButtonsState($(this), prodState); 340 }); 341 342 } 343 onCompleteCallback && onCompleteCallback(); 344 }); 345 }, 346 getState: function (pid, callback) { 347 var pidsParam = []; 348 pidsParam = Object.prototype.toString.call(pid) == '[object Array]' ? pid : [pid]; 349 return getProductsState(pidsParam, callback); 350 }, 351 getFullProductState: function(pids, callback) { 352 return getFullProductVariations(pids, callback); 353 }, 354 setProductButtonsState: function (cartButtons, productState) { 355 setProductButtonsState(cartButtons, productState); 356 }, 357 registerCustomSaleShieldTarget: function (target) { 358 customSaleShieldTargets.push(target); 359 processCustomSaleShieldTarget(target); 360 }, 361 processCustomSaleShieldTarget: function (target, container) { 362 processCustomSaleShieldTarget(target, container); 363 } 364 }; 365};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.