1function getCSRFToken() { 2 const cookieName = "csrftoken"; 3 let cookieValue = null; 4 if (document.cookie && document.cookie !== "") { 5 var cookies = document.cookie.split(";"); 6 for (var i = 0; i < cookies.length; i++) { 7 let cookie = cookies[i].trim(); 8 if ( 9 cookie.substring(0, cookieName.length + 1) === 10 cookieName + "=" 11 ) { 12 cookieValue = decodeURIComponent( 13 cookie.substring(cookieName.length + 1) 14 ); 15 break; 16 } 17 } 18 } 19 return cookieValue; 20} 21 22document.addEventListener("DOMContentLoaded", function () { 23 Array.from( 24 document.querySelectorAll('button[id$="cart-increment"]') 25 ).forEach((button) => { 26 initializeIncrementButton(button, true); 27 }); 28 Array.from( 29 document.querySelectorAll('button[id$="cart-decrement"]') 30 ).forEach((button) => { 31 initializeIncrementButton(button, false); 32 }); 33 Array.from(document.querySelectorAll('a[id$="add"]')).forEach((button) => { 34 initializeAddButton(button); 35 }); 36 Array.from(document.querySelectorAll('button[id$="cart-add"]')).forEach( 37 (button) => { 38 initializeAddButton(button); 39 } 40 ); 41 42 initializeRefreshButton(); 43 initializeClearButton(); 44}); 45 46function initializeAddButton(addButton) { 47 if (addButton) { 48 let addInProgress = false; 49 let states = { 50 ADD: 0, 51 ADDING: 1, 52 UNAVAILABLE: 2, 53 ERROR: 3, 54 ADDED: 4, 55 }; 56 let classes = { 57 addButton: { 58 [states.ADDING]: ["adding"], 59 }, 60 errorModal: { 61 [states.ADD]: ["hidden"], 62 [states.ADDING]: ["hidden"], 63 [states.ADDED]: ["hidden"], 64 [states.ERROR]: [], 65 [states.UNAVAILABLE]: ["hidden"], 66 }, 67 }; 68 let html = { 69 addButton: { 70 [states.ADD]: "Add to Cart", 71 [states.ADDING]: "Adding...", 72 [states.ADDED]: "In Cart", 73 [states.ERROR]: "Unavailable", 74 }, 75 errorModal: {}, 76 }; 77 let state = states.ADD; 78 function transitionState(next, effects) { 79 effects = effects || []; 80 const previous = state; 81 const elements = { 82 addButton, 83 }; 84 Object.entries(elements).forEach(function ([name, element]) { 85 if (!element) { 86 return; 87 } 88 element.classList.remove(...(classes[name][previous] || [])); 89 element.classList.add(...(classes[name][next] || [])); 90 91 const innerHTML = html[name][next]; 92 if (innerHTML) { 93 element.innerHTML = innerHTML; 94 } 95 }); 96 state = next; 97 } 98 addButton.addEventListener("click", function (event) { 99 console.log(addButton.dataset); 100 if (addButton.dataset.disabled) { 101 return; 102 } 103 event.preventDefault(); 104 event.stopPropagation(); 105 const addToCartURL = addButton.dataset.href; 106 if (state === states.ADD || state === states.ERROR) { 107 transitionState(states.ADDING); 108 ajaxAddToCart(addToCartURL, addButton.dataset.pk); 109 } else if (state === states.ADDED) { 110 window.location.href = addButton.dataset.cartHref; 111 } 112 }); 113 114 let variantSelector = addButton.parentNode.parentNode.querySelector( 115 ".shop-item-variant > select" 116 ); 117 if (variantSelector) { 118 variantSelector.addEventListener("change", function (event) { 119 if (state === states.ADDED || state === states.ERROR) { 120 transitionState(states.ADD); 121 } 122 }); 123 } 124 125 function ajaxAddToCart(addToCartURL, pk) { 126 let clickTime = Date.now(); 127 if (addInProgress) { 128 return; 129 } 130 addInProgress = true; 131 let timeSinceClick = Date.now() - clickTime; 132 let delay = Math.max(600 - timeSinceClick, 0); 133 134 const cartContents = document.querySelector("p#cart-contents"); 135 const cartCountLabel = document.querySelector("span#cart-count"); 136 setTimeout(function () { 137 new Promise(function (resolve, reject) { 138 const httpRequest = new XMLHttpRequest(); 139 httpRequest.open("POST", addToCartURL, true); 140 httpRequest.setRequestHeader("X-CSRFToken", getCSRFToken()); 141 httpRequest.setRequestHeader( 142 "Content-Type", 143 "application/x-www-form-urlencoded" 144 ); 145 httpRequest.onreadystatechange = function () { 146 if (httpRequest.readyState === XMLHttpRequest.DONE) { 147 if (httpRequest.status === 200) { 148 resolve(httpRequest.responseText); 149 } else { 150 console.log(httpRequest.responseText); 151 reject( 152 "There was an error adding this item to the cart." 153 ); 154 } 155 } 156 }; 157 httpRequest.send("item=" + pk + "&quantity=1"); 158 }) 159 .then(function (data) { 160 setTimeout(function () { 161 transitionState(states.ADDED); 162 if (cartContents) { 163 cartContents.innerHTML = data; 164 } 165 let cartData = JSON.parse(data); 166 cartCountLabel.innerHTML = 167 cartData["cart"]["item_count"]; 168 }, delay); 169 }) 170 .catch(function (error) {
171 html.errorModal[states.ERROR] = error; 172 transitionState(states.ERROR); 173 if (cartContents) { 174 cartContents.innerHTML = error; 175 } 176 }) 177 .finally(function () { 178 addInProgress = false; 179 }); 180 }, delay); 181 } 182 183 document.body.addEventListener( 184 "click", 185 function (ev) { 186 if (state === states.ADDING) { 187 transitionState(states.ADDED); 188 } 189 }, 190 false 191 ); 192 } 193} 194 195function initializeRefreshButton() { 196 const refreshButton = document.querySelector("a#refresh-cart"); 197 const cartContents = document.querySelector("p#cart-contents"); 198 if (!refreshButton) { 199 return; 200 } 201 refreshButton.addEventListener("click", function (event) { 202 event.preventDefault(); 203 event.stopPropagation(); 204 205 const refreshURL = event.target.href; 206 207 new Promise(function (resolve, reject) { 208 const httpRequest = new XMLHttpRequest(); 209 httpRequest.open("GET", refreshURL, true); 210 httpRequest.onreadystatechange = function () { 211 if (httpRequest.readyState === XMLHttpRequest.DONE) { 212 if (httpRequest.status === 200) { 213 resolve(httpRequest.responseText); 214 } else { 215 console.log(httpRequest.responseText); 216 reject("There was an error refreshing the cart."); 217 } 218 } 219 }; 220 httpRequest.send(); 221 }) 222 .then(function (content) { 223 cartContents.innerHTML = content; 224 }) 225 .catch(function (error) { 226 console.log(error); 227 }); 228 }); 229} 230 231function initializeClearButton() { 232 const clearButton = document.querySelector("a#clear-cart"); 233 const cartContents = document.querySelector("p#cart-contents"); 234 if (!clearButton) { 235 return; 236 } 237 clearButton.addEventListener("click", function (event) { 238 event.preventDefault(); 239 event.stopPropagation(); 240 241 const refreshURL = event.target.href; 242 243 new Promise(function (resolve, reject) { 244 const httpRequest = new XMLHttpRequest(); 245 httpRequest.open("POST", refreshURL, true); 246 httpRequest.setRequestHeader("X-CSRFToken", getCSRFToken()); 247 httpRequest.onreadystatechange = function () { 248 if (httpRequest.readyState === XMLHttpRequest.DONE) { 249 if (httpRequest.status === 200) { 250 resolve(httpRequest.responseText); 251 } else { 252 console.log(httpRequest.responseText); 253 reject("There was an error clearing the cart."); 254 } 255 } 256 }; 257 httpRequest.send(); 258 }) 259 .then(function (content) { 260 cartContents.innerHTML = content; 261 }) 262 .catch(function (error) { 263 console.log(error); 264 }); 265 }); 266} 267 268function updateHTMLFromCartData(cartData) { 269 const cartCountLabel = document.querySelector("span#cart-count"); 270 const cartContents = document.querySelector("p#cart-contents"); 271 // cartData: { 272 // cart: { 273 // item_count: int, 274 // items: [ 275 // { 276 // bundle_discount: { 277 // discount_dollars: "5.00", 278 // discount_percentage: 0, 279 // discount_type: "FLAT", 280 // quantity: 1, 281 // }, 282 // metadata: null 283 // promo_code_discount: null 284 // quantity: 1, 285 // price: "199.00", 286 // sku_identifier: "PDU-Y-01" 287 // sku_title: "Playdate" 288 // subtotal: "199.00" 289 // pre_discount_total: "199.00" 290 // } 291 // ], 292 // metadata: null, 293 // promo_code_discount: { 294 // code: ":)", 295 // discount_dollars: "0", 296 // discount_percentage: 50, 297 // discount_type: "PERCENTAGE", 298 // }, 299 // total: str 300 // pre_discount_total: str 301 // } 302 // } 303 if (cartContents) { 304 cartContents.innerHTML = cartData; 305 } 306 let emptyCartMessage = document.querySelector("#empty-cart-message"); 307 if (emptyCartMessage) { 308 if (cartData["cart"]["item_count"] == 0) { 309 emptyCartMessage.classList.remove("hidden"); 310 } else { 311 emptyCartMessage.classList.add("hidden"); 312 } 313 } 314 cartCountLabel.innerHTML = cartData["cart"]["item_count"]; 315 // update the pre-discount total 316 if (cartData.cart.promo_code_discount) { 317 document.querySelectorAll(".cart-discount-element").forEach((node) => { 318 node.classList.remove("hidden"); 319 }); 320 document.querySelector(".cart-discounts").classList.remove("hidden"); 321 document.querySelector( 322 ".cart-compare-price" 323 ).innerHTML = `$${cartData.cart.pre_discount_total}`; 324 document.querySelector( 325 ".cart-discount-title .title" 326 ).innerHTML = `${cartData.cart.promo_code_discount.display}`; 327 } else { 328 document.querySelectorAll(".cart-discount-element").forEach((node) => { 329 node.classList.add("hidden"); 330 }); 331 } 332 // update the cart total 333 document.querySelector( 334 ".cart-items dd.cart-subtotal .cart-total" 335 ).innerHTML = `$${cartData.cart.total}`; 336 // update the item 337 const presentRows = Array.from( 338 document.querySelectorAll(".item-row") 339 ).reduce((acc, row) => { 340 acc[row.id] = row; 341 return acc; 342 }, {});
343 cartData.cart.items.forEach((item) => { 344 const sku = item.sku_identifier; 345 const row = document.querySelector(`div#${sku.replaceAll('.', '\\.')}`); 346 const quantityValue = row.querySelector("dd.cart-item-quantity .quantity") 347 quantityValue && (quantityValue.innerHTML = item.quantity); 348 if (item.bundle_discount) { 349 row.querySelector( 350 "s.cart-item-compare-price" 351 ).innerHTML = `$${item.pre_discount_total}`; 352 row.querySelector(".cart-item-discount-title").innerHTML = 353 item.bundle_discount.display; 354 row.querySelector(".cart-item-discount-title")?.classList.remove( 355 "hidden" 356 ); 357 row.querySelector(".cart-item-compare-price")?.classList.remove( 358 "hidden" 359 ); 360 } else if (item.ghost) { 361 // make sure we show "Included in your bundle" 362 row.querySelector(".cart-item-discount-title")?.classList.remove( 363 "hidden" 364 ); 365 } else { 366 row.querySelector(".cart-item-discount-title")?.classList.add( 367 "hidden" 368 ); 369 row.querySelector(".cart-item-compare-price")?.classList.add( 370 "hidden" 371 ); 372 } 373 row.querySelector( 374 "dd.cart-item-price > .cart-item-total" 375 ).innerHTML = `$${item.subtotal}`; 376 presentRows[row.id] = null; 377 });
378 Object.entries(presentRows).forEach(([k, v]) => { 379 if (v) { 380 v.remove(); 381 } 382 }); 383 if (cartData.cart.items.length === 0) { 384 // hide the promo code element and subtotal. 385 const selectorsToHide = [ 386 ".checkout-button", 387 ".cart-items", 388 ".cart-promocode", 389 ]; 390 selectorsToHide.forEach((selector) => { 391 document.querySelector(selector).classList.add("hidden"); 392 }); 393 } 394} 395 396function initializeIncrementButton(incrementButton, increment) { 397 if (incrementButton) { 398 let addInProgress = false; 399 let states = { 400 ADD: 0, 401 ADDING: 1, 402 UNAVAILABLE: 2, 403 ERROR: 3, 404 }; 405 let html = { 406 incrementButton: { 407 [states.ADD]: increment ? "+" : "-", 408 [states.ADDING]: "â", 409 [states.ERROR]: "X", 410 }, 411 }; 412 let classes = { 413 incrementButton: {}, 414 }; 415 let state = states.ADD; 416 function transitionState(next, effects) { 417 const elements = { 418 incrementButton, 419 }; 420 const previous = state; 421 Object.entries(elements).forEach(function ([name, element]) { 422 if (!element) { 423 return; 424 } 425 element.classList.remove(...(classes[name][previous] || [])); 426 element.classList.add(...(classes[name][next] || [])); 427 428 const innerHTML = html[name][next]; 429 if (innerHTML) { 430 element.innerHTML = innerHTML; 431 } 432 }); 433 state = next; 434 } 435 incrementButton.addEventListener("click", function (event) { 436 if (incrementButton.dataset.disabled) { 437 return; 438 } 439 event.preventDefault(); 440 event.stopPropagation(); 441 const addToCartURL = incrementButton.dataset.href; 442 if (state === states.ADD || state === states.ERROR) { 443 transitionState(states.ADDING); 444 ajaxAddToCart(addToCartURL, incrementButton.dataset.pk); 445 } 446 }); 447 448 function ajaxAddToCart(addToCartURL, pk) { 449 let clickTime = Date.now(); 450 if (addInProgress) { 451 return; 452 } 453 addInProgress = true; 454 let timeSinceClick = Date.now() - clickTime; 455 let delay = Math.max(600 - timeSinceClick, 0); 456 457 setTimeout(function () { 458 new Promise(function (resolve, reject) { 459 const httpRequest = new XMLHttpRequest(); 460 httpRequest.open("POST", addToCartURL, true); 461 httpRequest.setRequestHeader("X-CSRFToken", getCSRFToken()); 462 httpRequest.setRequestHeader( 463 "Content-Type", 464 "application/x-www-form-urlencoded" 465 ); 466 httpRequest.onreadystatechange = function () { 467 if (httpRequest.readyState === XMLHttpRequest.DONE) { 468 if (httpRequest.status === 200) { 469 resolve(httpRequest.responseText); 470 } else { 471 console.log(httpRequest.responseText); 472 reject( 473 `There was an error ${ 474 increment 475 ? "adding this item to the cart." 476 : "removing this item from the cart." 477 }` 478 ); 479 } 480 } 481 }; 482 httpRequest.send(`item=${pk}&quantity=1`); 483 }) 484 .then(function (data) { 485 let cartData = JSON.parse(data); 486 if (cartData["reload"]) { 487 window.location.reload(); 488 } else { 489 setTimeout(function () { 490 updateHTMLFromCartData(cartData); 491 }, delay); 492 } 493 }) 494 .catch(function (error) {
495 html.errorModal[states.ERROR] = error; 496 transitionState(states.ERROR); 497 if (cartContents) { 498 cartContents.innerHTML = error; 499 } 500 }) 501 .finally(function () { 502 addInProgress = false; 503 transitionState(states.ADD); 504 }); 505 }, delay); 506 } 507 508 document.body.addEventListener( 509 "click", 510 function (ev) { 511 if (state === states.ADDING) { 512 transitionState(states.ADDED); 513 } 514 }, 515 false 516 ); 517 } 518}
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.