1 2 3(function($){ 4 window.addEventListener('locationSet',function() { 5 var typingTimer; // Timer identifier 6 var doneTypingInterval = 300; // Time in ms, e.g., 300ms 7 var $input = $('#recipe_search'); 8 var searchCache = new Map(); // Cache to store search results 9 10 function generateRecipeBoxHtmlSearch(recipes, url) { 11 if(typeof url == "undefined") { 12 url = embeddata.recipeinfo; 13 } 14 return recipes.map(recipe => ` 15 <div class="svt-recipe-box"> 16 <a href="${url}?id=${recipe.id}"> 17 <div class="svt-recipe-img-wrap"> 18 <img src="https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages_tn/${recipe.id}.jpg" 19 onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" 20 alt="${recipe.displayName}"> 21 </div> 22 <div class="svt-recipe-info-wrap"> 23 <h6>${recipe.tags[0]}</h6> 24 <h2>${recipe.displayName}</h2> 25 <div class="svt-recipe-short-desc">${recipe.description}</div> 26 <div class="svt-recipe-meta"> 27 <span>Serves ${recipe.servings}</span> 28 <span>${recipe.prepTime}</span> 29 <span>${recipe.difficulty}</span> 30 </div> 31 </div> 32 </a> 33 </div> 34 `).join(''); 35 } 36 37 async function searchRecipe( query ) { 38 $('.recipe-listing-wrap').hide(); 39 40 $('.svt-recipe-search-result-wrap h5 span').html(query); 41 $('.svt-recipe-search-result-wrap').fadeIn(); 42 43 if (searchCache.has(query)) { 44 renderSearchResults(searchCache.get(query), query); 45 } 46 else { 47 const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key); 48 49 const location = getCookie('selectedLocation'); 50 const pricelist = getCookie('selectedPriceList'); 51 52 let headers = { 53 'apikey': hsdata.hs_api_key, 54 'location': location, 55 'pricelist': pricelist 56 }; 57 58 const response = await api.getData('/recipe/search?searchPhrase='+query, headers); 59 if(response.results) { 60 $('.shimmer-loading').hide(); 61 html = generateRecipeBoxHtmlSearch(response.results); 62 searchCache.set(query, html); 63 renderSearchResults(html, query); 64 } 65 else { 66 $('.svt-recipe-result-grid').html('<p>No recipes found.</p>'); 67 $('.svt-recipe-result-grid').show(); 68 } 69 } 70 } 71 72 // On keyup, start the countdown 73 $input.on('input', function () { 74 const $this = $(this); 75 const query = $this.val().trim(); 76 77 if (query.length >= 3) { 78 $('.recipe-listing-wrap').hide(); 79 searchRecipe(query); 80 } 81 else { 82 $('.recipe-listing-wrap').show(); 83 $('.recipe-listing-wrap').find('.slick-slider').slick('refresh'); 84 $('.svt-recipe-search-result-wrap').hide(); 85 } 86 }); 87 88 $('.svt-recipe-search-form').submit(function() { 89 return false; // Prevent form submission 90 }); 91 92 // On keydown, clear the countdown 93 $input.on('keydown', function () { 94 clearTimeout(typingTimer); 95 }); 96 97 // If input already has value, trigger search 98 if ($input.length && $input.val() !== '') { 99 const query = $input.val().trim(); 100 searchRecipe(query); 101 // doneTyping(); 102 } 103 104 // User is "finished typing," do something 105 function doneTyping() { 106 var keyword = $input.val().trim(); 107 108 // Hide existing content during search 109 $('.svt-recipe-content').hide(); 110 111 // If the input is empty, show all content 112 if (keyword === '') { 113 $('.recipe-listing-wrap').show(); 114 $('.svt-recipe-search-result-wrap').hide(); 115 } else { 116 // Check if search result is already in cache 117 if (searchCache.has(keyword)) { 118 // Use cached result 119 renderSearchResults(searchCache.get(keyword), keyword); 120 } else { 121 // Perform AJAX search if not cached 122 $.ajax({
123 type: 'GET', 124 data: { action: 'recipe_search', keyword: keyword }, 125 dataType: 'json', 126 url: sitedata.ajax_url, 127 beforeSend: function() { 128 $('.svt-recipe-search-result-wrap span').html(keyword); 129 $('.svt-recipe-search-result-wrap').fadeIn(); 130 $('.shimmer-loading').show(); 131 $('.svt-recipe-result-grid').hide(); 132 $('.recipe-listing-wrap').hide(); 133 }, 134 success: function(response) { 135 $('.shimmer-loading').hide(); 136 137 if (response.html) { 138 // Cache the result for future use 139 searchCache.set(keyword, response.html); 140 141 // Render the search results 142 renderSearchResults(response.html, keyword); 143 } else { 144 // Handle case where no result is found 145 $('.svt-recipe-result-grid').html('<p>No recipes found.</p>'); 146 $('.svt-recipe-result-grid').show(); 147 } 148 }, 149 error: function() { 150 console.error('Search failed.'); 151 } 152 }); 153 } 154 } 155 } 156 157 // Function to render search results
158 function renderSearchResults(html, keyword) { 159 $('.svt-recipe-result-grid').html(html); 160 $('.svt-recipe-result-grid').show(); 161 } 162 163 if ($('.svt-featured-recipes-container').length) { 164 // Cache DOM selections and frequently used data 165 const featuredRecipeContainer = $('.svt-featured-recipes-container'); 166 const featuredRecipeList = featuredRecipeContainer.find('.svt-featured-recipes'); 167 const ids = featuredRecipeContainer.data('ids').split(','); 168 const recipeURL = featuredRecipeContainer.data('recipe-url'); 169 170 // Use fetch instead of $.ajax 171 const fetchRecipe = async (id) => { 172 const location = getCookie('selectedLocation'); 173 const pricelist = getCookie('selectedPriceList'); 174 const cachedRecipe = null; //sessionStorage.getItem(`recipe_${id}_${location}_${pricelist}`); 175 if (cachedRecipe) { 176 // You can return the cached recipe if it's found 177 return JSON.parse(cachedRecipe); 178 } 179 180 const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key); 181 let headers = { 182 'apikey': hsdata.hs_api_key, 183 'location': location, 184 'pricelist': pricelist 185 }; 186 187 try { 188 // Use await to wait for the response from the API 189 const response = await api.getData(`/recipe?recipeId=${id}`, headers); 190 // console.log(response); 191 // Save the response in sessionStorage and return the data 192 //sessionStorage.setItem(`recipe_${id}_${location}_${pricelist}`, JSON.stringify(response)); 193 return response; 194 } catch (error) { 195 console.error('Error fetching recipe data:', error); 196 return null; // Return null if there's an error 197 } 198 }; 199 200 // Generate HTML for a recipe 201 const generateRecipeHtml = (recipeObj, isLeftSide) => { 202 const { id, tags, displayName } = recipeObj; 203 const type = tags[0]; 204 const recipeImg = `https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages/${id}.jpg`; 205 206 return ` 207 <div class="${isLeftSide ? 'svt-featured-recipe-left' : ''}"> 208 <div class="svt-recipe-box"> 209 <a href="${recipeURL}?id=${id}"> 210 <img src="${recipeImg}" onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" alt="${displayName}"> 211 <div class="svt-recipe-info-wrap"> 212 <h6>${type}</h6> 213 <h2>${displayName}</h2> 214 </div> 215 </a> 216 </div> 217 </div>`; 218 }; 219 220 // Main function to load and display recipes 221 const loadFeaturedRecipes = async () => { 222 try { 223 const recipes = await Promise.all(ids.map(fetchRecipe)); 224 225 let featuredHtml = recipes.reduce((html, recipe, index) => { 226 if (index === 0) { 227 return html + generateRecipeHtml(recipe, true); 228 } else if (index === 1) { 229 return html + '<div class="svt-featured-recipe-right">' + generateRecipeHtml(recipe, false); 230 } else { 231 return html + generateRecipeHtml(recipe, false); 232 } 233 }, ''); 234 235 if (recipes.length > 1) { 236 featuredHtml += '</div>'; // Close svt-featured-recipe-right if it was opened 237 } 238 239 $('.svt-featured-recipes-skeleton').hide(); 240 featuredRecipeList.html(featuredHtml); 241 } catch (error) { 242 console.error("Error loading recipes: ", error); 243 } 244 }; 245 246 // Execute the main function 247 loadFeaturedRecipes(); 248 } 249 // Generic function to load recipes 250 async function loadRecipes(type) { 251 const location = getCookie('selectedLocation'); 252 const pricelist = getCookie('selectedPriceList'); 253 254 const config = { 255 trending: { 256 container: '.svt-trending-recipe-wrap-container', 257 skeleton: '.svt-trending-recipe-skeleton', 258 storageKey: `trending_recipes_${location}_${pricelist}`, 259 action: '/recipes?sectionName=rlp-top' 260 }, 261 seasonal: { 262 container: '.svt-seasonal-recipe-wrap-container', 263 skeleton: '.svt-seasonal-recipe-skeleton', 264 storageKey: `seasonal_recipes_${location}_${pricelist}`, 265 action: '/recipes?sectionName=rlp-seasonal' 266 } 267 }; 268 269 const { container, skeleton, storageKey, action } = config[type]; 270 271 if (!$(container).length) return; 272 273 const recipeSkeleton = $(skeleton); 274 const recipeContainer = $(container); 275 const recipeList = recipeContainer.find('.svt-recipe-carousel'); 276 const recipeURL = recipeContainer.data('recipe-url'); 277 278 let recipes = null; //sessionStorage.getItem(storageKey); 279 280 if (!recipes) { 281 const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key); 282 283 let headers = { 284 'apikey': hsdata.hs_api_key, 285 'location': location, 286 'pricelist': pricelist 287 }; 288 289 try { 290 // Use await to wait for the response from the API 291 const response = await api.getData(action, headers); 292 recipes = JSON.stringify(response); 293 // sessionStorage.setItem(storageKey, recipes); 294 } catch (error) { 295 console.error('Error fetching recipe data:', error); 296 return null; // Return null if there's an error 297 } 298 } 299 300 renderRecipes(JSON.parse(recipes), recipeSkeleton, recipeList, recipeURL); 301 } 302 303 function renderRecipes(recipes, skeleton, list, url) { 304 const html = generateRecipeBoxHtml(recipes, url); 305 skeleton.hide(); 306 list.html(html); 307 initializeSlick(list); 308 } 309 310 function generateRecipeBoxHtml(recipes, url) { 311 return recipes.map(recipe => ` 312 <div class="svt-recipe-box"> 313 <a href="${url}?id=${recipe.id}"> 314 <div class="svt-recipe-img-wrap"> 315 <img src="https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages_tn/${recipe.id}.jpg" 316 onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'"
317 alt="${recipe.displayName}"> 318 </div> 319 <div class="svt-recipe-info-wrap"> 320 <h6>${recipe.tags[0]}</h6> 321 <h2>${recipe.displayName}</h2> 322 <div class="svt-recipe-short-desc">${recipe.description}</div> 323 <div class="svt-recipe-meta"> 324 <span>Serves ${recipe.servings}</span> 325 <span>${recipe.prepTime}</span> 326 <span>${recipe.difficulty}</span> 327 </div> 328 </div> 329 </a> 330 </div> 331 `).join(''); 332 } 333 334 // Execute the main functions 335 loadRecipes('trending'); 336 loadRecipes('seasonal'); 337 338 function initializeSlick(recipeList) { 339 recipeList.removeClass('slick-initialized slick-slider'); 340 setTimeout(() => { 341 // console.log(recipeList); 342 recipeList.slick({ 343 slidesToShow: 3, 344 slidesToScroll: 1, 345 dots: true, 346 arrows: true, 347 prevArrow: '<button type="button" class="slick-prev" aria-hidden="true" aria-label="Previous"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M15.5303 5.46967C15.2374 5.17678 14.7626 5.17678 14.4697 5.46967L8.46967 11.4697C8.17678 11.7626 8.17678 12.2374 8.46967 12.5303L14.4697 18.5303C14.7626 18.8232 15.2374 18.8232 15.5303 18.5303C15.8232 18.2374 15.8232 17.7626 15.5303 17.4697L10.0607 12L15.5303 6.53033C15.8232 6.23744 15.8232 5.76256 15.5303 5.46967Z" fill="currentColor"></path></svg></button>', 348 nextArrow: '<button type="button" class="slick-next" aria-hidden="true" aria-label="Next"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.46967 5.46967C8.76256 5.17678 9.23744 5.17678 9.53033 5.46967L15.5303 11.4697C15.8232 11.7626 15.8232 12.2374 15.5303 12.5303L9.53033 18.5303C9.23744 18.8232 8.76256 18.8232 8.46967 18.5303C8.17678 18.2374 8.17678 17.7626 8.46967 17.4697L13.9393 12L8.46967 6.53033C8.17678 6.23744 8.17678 5.76256 8.46967 5.46967Z" fill="currentColor"></path></svg></button>', 349 responsive: [ 350 { breakpoint: 0, settings: { slidesToShow: 1, arrows: false } }, 351 { breakpoint: 767, settings: { slidesToShow: 1, arrows: false } }, 352 { breakpoint: 1024, settings: { slidesToShow: 3 } } 353 ] 354 }); 355 }, 100); 356 } 357 358 if($('.svt-recommanded-recipe-list').length) { 359 const recommandedRecipeSkeleton = $('.svt-recommanded-recipe-skeleton'); 360 const recommandedRecipeList = $('.svt-recommanded-recipe-list'); 361 const recipeURL = recommandedRecipeList.data('recipe-url'); 362 363 recommandedRecipeSkeleton.show(); 364 365 const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key); 366 367 const location = getCookie('selectedLocation'); 368 const pricelist = getCookie('selectedPriceList'); 369 370 let headers = { 371 'apikey': hsdata.hs_api_key, 372 'location': location, 373 'pricelist': pricelist 374 }; 375 376 let user = localStorage.getItem('user'); 377 if(user != null) { 378 user = JSON.parse(user); 379 headers.emailid = user.email; 380 headers.authToken = user.authToken; 381 } 382 383 api.getData('/recipes?sectionName=rlp-foryou', headers) 384 .then(response => { 385 const html = generateRecommandedRecipeHTML(response); 386 recommandedRecipeSkeleton.hide(); 387 recommandedRecipeList.html(html); 388 }) 389 .catch(console.error); 390 391 function generateRecommandedRecipeHTML( recipes ) { 392 let recommandedHtml = recipes.reduce((html, recipe, index) => { 393 if (index === 0) { 394 return html + generateRecommRecipeHtml(recipe, true); 395 } else if (index === 1) { 396 return html + '<div class="svt-recommanded-recipe-left-right">' + generateRecommRecipeHtml(recipe, false); 397 } else { 398 return html + generateRecommRecipeHtml(recipe, false); 399 } 400 }, ''); 401 402 if (recipes.length > 1) { 403 recommandedHtml += '</div>'; // Close svt-featured-recipe-right if it was opened 404 } 405 return recommandedHtml; 406 } 407 408 const generateRecommRecipeHtml = (recipeObj, isLeftSide) => { 409 const { id, tags, displayName, description, servings, prepTime, difficulty } = recipeObj; 410 const type = tags[0]; 411 const recipeImg = `https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages/${id}.jpg`; 412 413 var box = ''; 414 if(isLeftSide == true) { 415 box += `<div class="${isLeftSide ? 'svt-recommanded-recipe-left' : ''}">`; 416 } 417 box += `<div class="svt-recipe-box"> 418 <a href="${recipeURL}?id=${id}"> 419 <div class="svt-recipe-img-wrap"> 420 <img src="${recipeImg}" onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" alt="${displayName}"> 421 </div> 422 <div class="svt-recipe-info-wrap"> 423 <h6>${type}</h6> 424 <h2>${displayName}</h2>`; 425 if(isLeftSide == true) { 426 box += ` 427 <div class="svt-recipe-short-desc">${description}</div> 428 <div class="svt-recipe-meta">
429 <span>Serves ${servings}</span> 430 <span>${prepTime}</span> 431 <span>${difficulty}</span> 432 </div> 433 `; 434 } 435 box += ` 436 </div> 437 </a> 438 </div>`; 439 if(isLeftSide == true) { 440 box += `</div>`; 441 } 442 443 return box; 444 }; 445 } 446 447 if($('.svt-cat-recipe-listing').length) { 448 // Cache DOM selections 449 const catRecipeLoader = $('.svt-recipe-listing-skeleton'); 450 const catRecipeListing = $('.svt-cat-recipe-listing'); 451 const cat = catRecipeListing.data('cat'); 452 const recipeURL = catRecipeListing.data('recipe-url'); 453 454 // Show loader immediately 455 catRecipeLoader.show(); 456 457 const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key); 458 459 const location = getCookie('selectedLocation'); 460 const pricelist = getCookie('selectedPriceList'); 461 462 let headers = { 463 'apikey': hsdata.hs_api_key, 464 'location': location, 465 'pricelist': pricelist 466 }; 467 468 api.getData('/recipes?sectionName=rtp-all§ionParams='+encodeURIComponent(cat), headers) 469 .then(response => { 470 const html = generateRecipeWithMetaBoxHtml(response); 471 catRecipeLoader.hide(); 472 catRecipeListing.html(html); 473 }) 474 .catch(console.error); 475 476 function generateRecipeWithMetaBoxHtml(recipes) { 477 // Use map and join instead of forEach and string concatenation 478 return recipes.map(recipeObj => { 479 const { id, tags, displayName, description, servings, prepTime, difficulty } = recipeObj; 480 const type = tags[0]; 481 const recipeImg = `https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages_tn/${id}.jpg`; 482 483 return ` 484 <div class="svt-recipe-box"> 485 <a href="${recipeURL}?id=${id}"> 486 <div class="svt-recipe-img-wrap"> 487 <img src="${recipeImg}" onerror="this.onerror=null; this.className='placeholder-img'; this.src='${embeddata.theme_url}/images/Recipe.svg'" alt="${displayName}"> 488 </div> 489 <div class="svt-recipe-info-wrap"> 490 <h6>${type}</h6> 491 <h2>${displayName}</h2> 492 <div class="svt-recipe-short-desc">${description}</div> 493 <div class="svt-recipe-meta"> 494 <span>Serves ${servings}</span> 495 <span>${prepTime}</span> 496 <span>${difficulty}</span> 497 </div> 498 </div> 499 </a> 500 </div>`; 501 }).join(''); 502 } 503 } 504 505 const loadRelatedRecipes = function( recipeName, recipeID ) { 506 let html = ''; 507 const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key); 508 509 let headers = { 510 'apikey': hsdata.hs_api_key, 511 'location': getCookie('selectedLocation'), 512 'pricelist': getCookie('selectedPriceList'), 513 }; 514 515 api.getData('/recipes?sectionName=rdp-related§ionParams='+recipeName+'§ionParams='+recipeID,headers).then(response => { 516 const recipes = response; 517 if(recipes) { 518 html += ` 519 <div class="svt-related-recipes"> 520 <div class="svt-related-recipes-head"> 521 <h2>You may also like</h2> 522 </div> 523 <div class="svt-recipe-carousel-wrap"> 524 <div class="svt-recipe-carousel"> 525 ${(() => generateRecipeBoxHtml(recipes,embeddata.recipeinfo))()} 526 </div> 527 </div> 528 </div> 529 `; 530 $('.svt-recipe-detail-wrap .svt-page-container').append(html); 531 initializeSlick($('.svt-recipe-carousel')); 532 } 533 }) 534 .catch(error => { 535 console.error('Error posting data:', error); 536 }); 537 } 538 539 if($('.svt-recipe-detail-wrap').length) { 540 541 const fetchRecipe = async (id) => { 542 const location = getCookie('selectedLocation'); 543 const pricelist = getCookie('selectedPriceList'); 544 // const cachedRecipe = sessionStorage.getItem(`recipe_${id}_${location}_${pricelist}`); 545 // if (cachedRecipe) { 546 // // You can return the cached recipe if it's found 547 // return JSON.parse(cachedRecipe); 548 // } 549 550 const api = new HomesomeAPI(hsdata.recipe_url, hsdata.hs_api_key); 551 let headers = { 552 'apikey': hsdata.hs_api_key, 553 'location': location, 554 'pricelist': pricelist 555 }; 556 557 try { 558 // Use await to wait for the response from the API 559 const response = await api.getData(`/recipe?recipeId=${id}`, headers); 560 // console.log(response); 561 // Save the response in sessionStorage and return the data 562 // sessionStorage.setItem(`recipe_${id}_${location}_${pricelist}`, JSON.stringify(response)); 563 return response; 564 } catch (error) { 565 console.error('Error fetching recipe data:', error); 566 return null; // Return null if there's an error 567 } 568 }; 569 570 let recipeID = getUrlParameter('id'); 571 const recipeInfo = fetchRecipe(recipeID); 572 let html = ''; 573 recipeInfo.then(data => { 574 const recipeImg = `https://s3-us-west-2.amazonaws.com/www.gethomesome.com/productimages/${data.id}.jpg`; 575 const type = data.tags[0]; 576 const instructions = data.instructions; 577 const ingredients = data.ingredients; 578 const recipeProducts = data.products; 579 const productIDs = recipeProducts.map(item => item.name); 580 $('i.recipe-name').html(data.displayName); 581 html += ` 582 <div class="svt-page-container"> 583 <div class="svt-recipe-detail-breadcrumb"> 584 <a href="${sitedata.home_url}">Home</a>
585 <svg width="6" height="11" viewBox="0 0 6 11" fill="none" xmlns="http://www.w3.org/2000/svg"> 586 <path d="M1 9.5L5 5.5L1 1.5" stroke="#232323" stroke-width="1.3" stroke-linecap="round" 587 stroke-linejoin="round" /> 588 </svg> 589 <a href="${sitedata.home_url}/recipes/">Recipes</a> 590 <svg width="6" height="11" viewBox="0 0 6 11" fill="none" xmlns="http://www.w3.org/2000/svg"> 591 <path d="M1 9.5L5 5.5L1 1.5" stroke="#232323" stroke-width="1.3" stroke-linecap="round" 592 stroke-linejoin="round" /> 593 </svg> 594 <span class="recipe-name">${data.displayName}</span> 595 </div> 596 <div class="svt-recipe-detail-info-wrap"> 597 <div class="svt-recipe-detail-img"> 598 <img src="${recipeImg}" onerror="this.onerror=null; this.className='placeholder-img'; this.src='<?php echo get_stylesheet_directory_uri().'/images/Recipe.svg'; ?>'" alt=""> 599 </div> 600 <div class="svt-recipe-detail-info"> 601 <h6>${type}</h6> 602 <h1>${data.displayName}</h1> 603 <div class="svt-recipe-detail-desc"> 604 ${data.description} 605 </div> 606 <div class="svt-recipe-detail-meta"> 607 <span>Serves <strong>${data.servings}</strong></span> 608 <span>Time <strong>${data.prepTime}</strong></span> 609 <span>Difficulty <strong>${data.difficulty}</strong></span> 610 </div> 611 </div> 612 </div> 613 <div class="svt-recipe-other-info-wrap"> 614 <div class="svt-instructions-wrap"> 615 <h4>Instructions</h4> 616 <ol> 617 ${(() => instructions.map(ins => `<li>${ins}</li>`).join(''))()} 618 </ol> 619 </div> 620 <div class="svt-ingredient-products-wrap"> 621 <div class="svt-ingredient-wrap"> 622 <h4>Ingredients</h4> 623 <ul> 624 ${(() => ingredients.map(ing => `<li>${ing}</li>`).join(''))()} 625 </ul> 626 </div> 627 <div class="svt-recipe-products-wrap"> 628 <div class="svt-recipe-products-header"> 629 <h4>Shop Ingredients</h4> 630 <div class="add-to-cart-bulk-wrap"> 631 <a href="#" class="add-to-cart-bulk"> 632 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" 633 fill="none"> 634 <path 635 d="M8.75 1C8.75 0.585786 8.41421 0.25 8 0.25C7.58579 0.25 7.25 0.585786 7.25 1V7.25H1C0.585786 7.25 0.25 7.58579 0.25 8C0.25 8.41421 0.585786 8.75 1 8.75H7.25V15C7.25 15.4142 7.58579 15.75 8 15.75C8.41421 15.75 8.75 15.4142 8.75 15V8.75H15C15.4142 8.75 15.75 8.41421 15.75 8C15.75 7.58579 15.4142 7.25 15 7.25H8.75V1Z" 636 fill="white" /> 637 </svg> 638 <span>Add All <span class="product-count"></span> Items</span> 639 </a> 640 <div class="bulk-subtotal-wrap">Subtotal: <span class="subtotal"></span></div> 641 </div> 642 </div> 643 <div class="svt-recipe-products-list" data-product-ids='${productIDs}'> 644 645 </div> 646 <div class="svt-recipe-products-footer"> 647 <div class="add-to-cart-bulk-wrap"> 648 <a href="#" class="add-to-cart-bulk"> 649 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewB
649ox="0 0 16 16" 650 fill="none"> 651 <path 652 d="M8.75 1C8.75 0.585786 8.41421 0.25 8 0.25C7.58579 0.25 7.25 0.585786 7.25 1V7.25H1C0.585786 7.25 0.25 7.58579 0.25 8C0.25 8.41421 0.585786 8.75 1 8.75H7.25V15C7.25 15.4142 7.58579 15.75 8 15.75C8.41421 15.75 8.75 15.4142 8.75 15V8.75H15C15.4142 8.75 15.75 8.41421 15.75 8C15.75 7.58579 15.4142 7.25 15 7.25H8.75V1Z" 653 fill="white" /> 654 </svg> 655 <span>Add All <span class="product-count"></span> Items</span> 656 </a> 657 <div class="bulk-subtotal-wrap">Subtotal: <span class="subtotal"></span></div> 658 </div> 659 </div> 660 </div> 661 </div> 662 </div> 663 </div> 664 `; 665 $('.svt-recipe-detail-wrap').html(html); 666 loadRelatedRecipes(data.displayName,data.id); 667 }).catch(error => { 668 console.log(error); 669 }); 670 } 671 }); 672 673 function loadRecipeProducts( productIDs, productsData ) { 674 675 const { products, promotions } = productsData; 676 let html = ''; 677 678 // Filter recipe products 679 const recipeProducts = products.filter(product => productIDs.includes(product.name)); 680 681 // Calculate subtotal 682 const subTotal = recipeProducts.reduce((acc, product) => acc + parseFloat(product.price), 0); 683 684 // Generate HTML for products 685 html = recipeProducts.map(product => renderProductCard(product, promotions)).join(''); 686 687 // Update DOM in one batch 688 $('.svt-recipe-products-wrap .add-to-cart-bulk-wrap').fadeIn(); 689 $('.svt-recipe-products-wrap .add-to-cart-bulk-wrap .product-count').html(recipeProducts.length); 690 $('.svt-recipe-products-wrap .add-to-cart-bulk-wrap .subtotal').html(`$${subTotal.toFixed(2)}`); 691 $('.svt-recipe-products-list .shimmer-product-boxes').hide(); 692 $('.svt-recipe-products-list').append(html); 693 694 } 695 696 $(window).on('CACHE_DATA_READY', function(event) { 697 const productsData = event.detail.data; 698 if($('.svt-recipe-products-list').length) { 699 let productIDs = $('.svt-recipe-products-list').data('product-ids'); 700 if(typeof productIDs == "undefined") { 701 let recipeProductsTimer = setInterval(function() { 702 productIDs = $('.svt-recipe-products-list').data('product-ids'); 703 if(typeof productIDs != "undefined") { 704 productIDs = productIDs.split(','); 705 loadRecipeProducts( productIDs, productsData ); 706 clearInterval(recipeProductsTimer); 707 } 708 },200); 709 } 710 else { 711 productIDs = productIDs.split(','); 712 loadRecipeProducts( productIDs, productsData ); 713 } 714 } 715 }); 716})(jQuery);
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.