1function fupi_woo(){ 2 3 if ( ! fp.woo ) return; 4 5 fp.loaded.push('woo'); // checked in other functions 6 7 // Helpers 8 9 FP.fns.get_woo_prod_id = prod => { // This must be equivalent to "get_woo_prod_id" in class-fupi-public-woo.php 10 if ( prod.type == 'variation' && fp.woo.variable_tracking_method == 'track_parents' ) { 11 return fp.woo.sku_is_id ? prod.parent_sku || prod.parent_id : prod.parent_id; 12 } else { 13 return fp.woo.sku_is_id ? prod.sku || prod.id : prod.id; 14 }; 15 } 16 17 FP.fns.get_woo_prod_name = prod => { // This must be equivalent to "get_woo_prod_name" in class-fupi-public-woo.php 18 return prod.type == 'variation' && fp.woo.variable_tracking_method == 'track_parents' ? prod.parent_name || prod.name : prod.name; 19 } 20 21 function get_current_variant_id(){ 22 23 // Get fields with the main product ID and the currently selected variation ID 24 let product_id_field = FP.findFirst( 'form.variations_form input[name="product_id"]' ), 25 variant_id_field = FP.findFirst( 'form.variations_form input[name="variation_id"]' ); 26 27 if ( ! ( product_id_field && variant_id_field ) ) return false; 28 29 let product_id = product_id_field.value, 30 variant_id = variant_id_field.value; 31 32 // Check if main prod and variant are the same 33 if ( product_id == variant_id ) { 34 35 // Mark this variant as viewed and return 36 fpdata.woo.viewed_variants.push(variant_id); 37 return false; 38 }; 39 40 // Check if this variant has already been tracked 41 if ( fpdata.woo.viewed_variants && fpdata.woo.viewed_variants.includes( variant_id ) ) return false; 42 43 // Mark this variant as viewed 44 fpdata.woo.viewed_variants.push(variant_id); 45 46 return variant_id; 47 } 48 49 50 51 // PREPARE GROUPED PRODUCTS 52 53 function prepare_grouped_prods_for_addtocart(){ 54 55 FP.findAll( 'form.grouped_form' ).forEach( form_el => { 56 57 let item_wrap = FP.findAll('.woocommerce-grouped-product-list-item', form_el), 58 dataset_info = [], 59 added_value = false; 60 61 if ( item_wrap.length > 0 ){ 62 63 // save info on products with qty > 0 in a data property in the form element 64 item_wrap.forEach( item => { 65 66 let qty = FP.findFirst( 'input.qty', item ), 67 id = item.id.replace('product-', ''); 68 69 if ( id && qty && qty.value && qty.value > 0 ) { 70 dataset_info.push( [ id, qty.value ] ); 71 added_value = true; 72 } 73 }); 74 }; 75 76 form_el.dataset.fupi_products = added_value ? JSON.stringify(dataset_info) : ''; 77 } ) 78 }; 79 80 setTimeout( prepare_grouped_prods_for_addtocart, 500 ); 81 document.addEventListener( 'change', prepare_grouped_prods_for_addtocart ); 82 document.addEventListener( 'click', () => { setTimeout( prepare_grouped_prods_for_addtocart, 100 ); } ); // this makes sure the script will not slow down tracking 83 84 // PREPARE SINGLE PRODUCTS AND TEASERS 85 // TRACK IMPRESSIONS OF SINGLE PRODS AND TEASERS 86 87 function get_teaser_list_name( teaser_wrap, el ){ 88 89 let list = teaser_wrap.closest("ul") || teaser_wrap.parentElement, 90 list_name = el.dataset.list_name || list && list.dataset.fupi_list_name; 91 92 if ( list && ! list_name ) { 93 94 if ( list.parentElement.parentElement.classList.contains('wp-block-woocommerce-related-products') ) { 95 list_name = 'woo related block'; 96 } else if ( list.parentElement.classList.contains('wp-block-handpicked-products') ) { 97 list_name = 'woo handpicked block'; 98 } else if ( list.parentElement.classList.contains('wp-block-product-best-sellers') ) { 99 list_name = 'woo bestsellers block'; 100 } else if ( list.parentElement.classList.contains('wp-block-product-new') ) { 101 list_name = 'woo new products block'; 102 } else if ( list.parentElement.classList.contains('wp-block-product-on-sale') ) { 103 list_name = 'woo on sale block'; 104 } else if ( list.parentElement.classList.contains('wp-block-product-top-rated') ) { 105 list_name = 'woo top rated block'; 106 } else if ( list.parentElement.classList.contains('wc-block-products-by-attribute') ) { 107 list_name = 'woo products by attribute block'; 108 } else { 109 list_name = 'woo products'; // default 110 }; 111 }; 112 113 list.classList.add('fupi_products_list'); 114 115 // save list name in the list's data attrib 116 if ( list && list_name ) list.dataset.fupi_list_name = list_name; 117 118 return list_name; 119 } 120 121 function process_new_html_prod_data(){ 122 123 let prod_data_els = FP.findAll(".fupi_prod_data:not(.fupi_ready)"); 124 125 if ( prod_data_els.length == 0 ) return false; 126
127 prod_data_els.forEach( el => { 128 129 let list_name = 'single', 130 id = el.dataset.id, 131 prod_data = JSON.parse ( el.dataset.data ), 132 prod_type = el.dataset.type || prod_data.type, 133 prod_wrap = false, 134 closest_form_element = el.closest("form.cart, form.wp-block-add-to-cart-with-options"); 135 136 // add prod data to fpdata.woo.products 137 fpdata.woo.products[id] = fpdata.woo.products[id] ? { ...fpdata.woo.products[id], ...prod_data } : prod_data; 138 139 // mark as processed 140 el.classList.add("fupi_ready"); 141 142 // skip variants 143 if ( prod_type == 'variant' ) return; 144 145 // add classes to prod wrapper 146 147 // if is product 148 if ( closest_form_element ) { 149 150 prod_wrap = closest_form_element.parentElement.classList.contains("summary") ? closest_form_element.parentElement : closest_form_element.parentElement.parentElement; 151 prod_wrap.classList.add('fupi_woo_single_product', 'fupi_woo_product'); 152 153 // probably is teaser 154 } else { 155 156 let wrapper_selector = 'li'; 157 158 if ( fp.woo.teaser_wrapper_sel ) { 159 wrapper_selector += ', ' + fp.woo.teaser_wrapper_sel; 160 } 161 162 prod_wrap = el.closest( wrapper_selector ) || el.parentElement; 163 list_name = get_teaser_list_name( prod_wrap, el ); 164 prod_wrap.classList.add("fupi_woo_teaser", "fupi_woo_product"); 165 166 let btns_and_links = FP.findAll('a, button', prod_wrap ); 167 168 // add class to ATC buttons inside the wrapper 169 btns_and_links.forEach( btn => { 170 171 if ( btn.classList.contains('single_add_to_cart') ) return; 172 if ( fp.woo.wishlist_btn_sel && btn.classList.contains( fp.woo.wishlist_btn_sel ) ) return; 173 174 let href = btn.getAttribute('href'); 175 176 if ( btn.classList.contains('add_to_cart_button') && ( btn.tagName == 'BUTTON' || ( href && href.includes('add-to-cart=') ) ) ) { 177 btn.classList.add('fupi_add_to_cart_button'); 178 } 179 180 } ); 181 } 182 183 // save product ID in the fpdata.woo.lists 184 if ( prod_wrap ) { 185 prod_wrap.dataset.fupi_woo_prod_id = id; 186 fpdata.woo.lists[list_name] = fpdata.woo.lists[list_name] || []; 187 if ( ! fpdata.woo.lists[list_name].includes(id) ) fpdata.woo.lists[list_name].push( id ); 188 } 189 }); 190 191 return true; 192 } 193 194 function process_new_allprods_block_data(){ 195 196 if ( ! fp.woo.products_from_all_products_block || fp.woo.products_from_all_products_block.length == 0 ) return false; 197 198 fp.woo.products_from_all_products_block.forEach( prod => { 199 200 // get cats 201 let price = parseInt( prod.prices.price ) / 100, 202 categories = []; 203 204 prod.categories.forEach( cat => categories.push( cat.name ) ); 205 206 // get all data 207 let data = { 208 'categories' : categories, 209 'id' : prod.id, 210 'name' : prod.name, 211 'sku' : prod.sku, 212 'price' : price, 213 'type' : prod.type, 214 // 'parent_id', parent_sku' and 'brand' are not available in the data obj 215 } 216 217 // update prod in the fpdata obj 218 fpdata.woo.products[prod.id] = fpdata.woo.products[prod.id] ? { ...fpdata.woo.products[prod.id], ...data } : data; 219 220 // mark prod in HTML 221 222 let prod_link = FP.findFirst('div.wp-block-woocommerce-all-products li.wc-block-grid__product:not(.fupi_ready) a[href="' + prod.permalink + '"]'); 223 224 if ( prod_link ) { 225 226 let prod_wrap = prod_link.closest("li.wc-block-grid__product"), 227 list = prod_link.closest('ul'); 228 229 if ( ! prod_wrap ) return; 230 231 list_name = 'woo all products block'; 232 list.dataset.fupi_list_name = list_name; 233 list.classList.add('fupi_products_list'); 234 235 prod_wrap.classList.add("fupi_woo_teaser", "fupi_woo_product"); 236 prod_wrap.dataset.fupi_woo_prod_id = prod.id; 237 238 let add_to_cart_btns = FP.findAll('.add_to_cart_button, .ajax_add_to_cart', prod_wrap ); 239 240 add_to_cart_btns.forEach( btn => btn.classList.add('fupi_add_to_cart_button') ); 241 242 // save product data in the fpdata.woo.lists 243 fpdata.woo.lists[list_name] = fpdata.woo.lists[list_name] || []; 244 if ( ! fpdata.woo.lists[list_name].includes(prod.id) ) fpdata.woo.lists[list_name].push( prod.id ); 245 }; 246 }); 247 248 return true; 249 } 250 251 function prepare_prod_data(){ 252 253 let new_prods_added = process_new_html_prod_data(); 254 let new_allprods_blocks_added = process_new_allprods_block_data(); 255 256 if ( ! new_prods_added && ! new_allprods_blocks_added ) return; 257 if ( fp.woo.dont_track_views_after_refresh && fpdata.refreshed ) return; 258 259 FP.doActions( "woo_impress" ); 260 } 261 262 function track_def_variant_view(){ 263 264 if ( fp.woo.variable_tracking_method != 'track_def_var' ) return; 265 if ( fp.woo.dont_track_views_after_refresh && fpdata.refreshed ) return; 266 267 let variant_id_field = FP.findFirst( 'form.variations_form input[name="variation_id"]' ); 268 269 if ( ! variant_id_field ) return; 270 271 let timer = 1000; 272 273 let variant_check_interval = setInterval( () => { 274 275 if ( variant_id_field.value != '0' ) { 276 clearInterval( variant_check_interval ); 277 278 let variant_id = get_current_variant_id(); 279 280 if ( variant_id && fpdata.woo.products[variant_id] && FP.hasActions( 'woo_def_variant_view' ) ) { 281 FP.doActions( "woo_def_variant_view", variant_id ); 282 } 283 } 284 285 if ( timer >= 4000 ) { 286 clearInterval( variant_check_interval ); 287 return; 288 } 289 290 timer += 1000; 291 292 }, 1000 ); 293 } 294 295 // Prepare teasers and single products for tracking and init tracking 296 if ( document.readyState === "complete" ) { 297 prepare_prod_data(); 298 track_def_variant_view(); 299 } else { 300 document.addEventListener('DOMContentLoaded', ()=>{ 301 prepare_prod_data(); 302 track_def_variant_view(); 303 } ); 304 } 305 306 setInterval( ()=>{prepare_prod_data();}, 1000 ); 307 308 // TRACKING HELPERS 309 310 function get_teaser_data(){ 311 312 let teaser_el = fpdata.clicked.element.closest('.fupi_woo_teaser'), 313 prod_id = teaser_el.dataset.fupi_woo_prod_id; 314 315 if ( prod_id && fpdata.woo.products[prod_id] ) { 316 317 let prod = { ...fpdata.woo.products[prod_id] }, 318 teasers_wrapper = teaser_el.closest('.fupi_products_list'); 319 320 if ( ! teasers_wrapper ) return false; 321 322 let list_name = teasers_wrapper.dataset.fupi_list_name || 'woo products', 323 index = fpdata.woo.lists[list_name] && fpdata.woo.lists[list_name].includes(prod_id) ? fpdata.woo.lists[list_name].indexOf(prod_id) + 1 : 1; 324 325 prod['list_name'] = list_name; 326 prod['index'] = index; 327 328 return prod; 329 } 330 331 return false; 332 } 333 334 // TRACK CLICKS IN TEASER 335 336 FP.addAction( ['click'], () => { 337 338 if ( ! FP.hasActions( 'woo_teaser_click' ) ) return; 339 340 let selector = '.fupi_woo_teaser a:not(.fupi_add_to_cart_button)' + ( fp.wo
340o.wishlist_btn_sel ? ':not(' + fp.woo.wishlist_btn_sel + ')' : '' ); 341 342 if ( FP.isClickTarget( selector ) ) { 343 let teaser_data = get_teaser_data(); 344 if ( teaser_data ) FP.doActions( 'woo_teaser_click', { 'products' : [[teaser_data, 1]], 'value' : teaser_data.price } ); 345 } 346 } ); 347 348 // TRACK ADD TO CART FROM A TEASER 349 350 FP.addAction( ['click'], function(){ 351 352 // Skip if tracking should happen in cart instead 353 if ( fp.woo.where_track_addtocart === 'in_cart' ) return; 354 355 if ( ! FP.hasActions( 'woo_add_to_cart' ) || ( !! fp.woo.disable_woo_events && fp.woo.disable_woo_events.includes('add_to_cart_teaser') ) ) return; 356 357 // prevent "add to cart" event from being fired twice - 1st time when the Add to cart button is clicked and the other one when cart is updated (tracked by a different script) 358 // if ( document.body.classList.contains('woocommerce-cart') ) return; 359 360 if ( FP.isClickTarget( '.fupi_add_to_cart_button, .fupi_add_to_cart_button *' ) ) { 361 let teaser_data = get_teaser_data(); 362 if ( teaser_data ) FP.doActions( 'woo_add_to_cart', { 'products' : [[teaser_data, 1]], 'value' : teaser_data.price } ); 363 } 364 } ); 365 366 // TRACK ADD TO CART FOR A SIMPLE, VARIABLE OR GROUPED PRODUCT 367 368 FP.addAction( ['click'], function(){ 369 370 // Skip if tracking should happen in cart instead 371 if ( fp.woo.where_track_addtocart === 'in_cart' ) return; 372 373 if ( ! FP.hasActions( 'woo_add_to_cart' ) || ( !! fp.woo.disable_woo_events && fp.woo.disable_woo_events.includes('add_to_cart_full') ) ) return; 374 375 if ( FP.isClickTarget( '.single_add_to_cart_button:not(.disabled), .single_add_to_cart_button:not(.disabled) *' ) ) { 376 377 let form_el = fpdata.clicked.element.closest('form.cart'); 378 form_found = false; 379 380 // if no classic form element 381 if ( ! form_el ) { 382 383 let atc_plus_opts_form = fpdata.clicked.element.closest('form.wp-block-add-to-cart-with-options') 384 385 // if this is a form element from "Add to cart + options (beta) block" 386 if ( atc_plus_opts_form ){ 387 388 let atc_single_btn = FP.findFirst('.add_to_cart_button.product_type_simple', atc_plus_opts_form) 389 390 // check what type of product is being clicked 391 if ( atc_single_btn ) { 392 393 // we can get prod id from the button itself 394 let prod_id = atc_single_btn.dataset.product_id, 395 prod = prod_id ? fpdata.woo.products[prod_id] : false; 396 397 if ( prod ) { 398 399 let qty_el = FP.findFirst( '.qty', atc_plus_opts_form ), 400 qty = qty_el ? qty_el.value : 1, 401 value = Math.round( prod.price * qty * 100 ) / 100; 402 403 FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty]], 'value': value } ); 404 } 405 406 } else { 407 if ( fp.main.debug ) console.log('[FP] At the moment only tracking single products is supported in the "Add to cart + Options (beta) block!'); 408 } 409 410 } else { 411 412 // this is a single button 413 if ( fpdata.clicked.element?.href && fpdata.clicked.element.href.includes('add-to-cart=') ) { 414 415 let prod_id = FP.getUrlParamByName( 'add-to-cart', fpdata.clicked.element.href ); 416 417 if ( fpdata.woo.products[prod_id] ) { 418 419 let qty_from_url = FP.getUrlParamByName( 'quantity', fpdata.clicked.element.href ), 420 qty = qty_from_url || 1, 421 prod = fpdata.woo.products[prod_id], 422 value = Math.round( prod.price * qty * 100 ) / 100; 423 424 FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty]], 'value': value } ); 425 FP.setCookie( 'fp_last_atc', prod_id, 0 ); 426 } 427 } 428 } 429 430 // track variable product 431 } else if ( form_el.classList.contains('variations_form') ) { 432 433 let prod_id = FP.findFirst( 'input.variation_id', form_el ).value; 434 435 if ( fpdata.woo.products[prod_id] ) { 436 437 let qty_el = FP.findFirst( 'input.qty', form_el ), 438 qty = qty_el && qty_el.value && qty_el.value > 0 ? parseInt( qty_el.value ) : 1, 439 prod = fpdata.woo.products[prod_id], 440 value = Math.round( prod.price * qty * 100 ) / 100; 441 442 FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty]], 'value': value } ); 443 } 444 445 // track grouped product 446 } else if ( form_el.classList.contains('grouped_form') ) { 447 448 let prods_data_arr = form_el.dataset.fupi_products ? JSON.parse( form_el.dataset.fupi_products ) : [], 449 items_a = [], 450 value = 0; 451 452 if ( prods_data_arr.length > 0 ) { 453 454 prods_data_arr.forEach( prod_a => { 455 456 let prod_id = prod_a[0], 457 qty = parseFloat( prod_a[1] ); 458 459 if ( fpdata.woo.products[prod_id] ) { 460 461 let prod = fpdata.woo.products[prod_id]; 462 463 value += prod.price * qty; 464 items_a.push( [prod, qty] ); 465 } 466 } ); 467 468 value = Math.round( value * 100 ) / 100; 469 470 if ( items_a.length > 0 ) FP.doActions( 'woo_add_to_cart', { 'products' : items_a, 'value' : value } ); 471 } 472 473 474 // track simple product 475 } else if ( form_el ) { 476 477 let atc_btn = FP.findFirst( '.single_add_to_cart_button', form_el ), 478 prod_id = atc_btn?.value || atc_btn.dataset?.prod_id || atc_btn.dataset?.prodid || atc_btn.dataset?.product_id || atc_btn.dataset?.pid; 479 480 if ( !! prod_id && fpdata.woo.products[prod_id] ) { 481 482 let qty_el = FP.findFirst( 'input.qty', form_el ), 483 qty = qty_el && qty_el.value && qty_el.value > 0 ? parseInt( qty_el.value ) : 1, 484 prod = fpdata.woo.products[prod_id], 485 value = Math.round( prod.price * qty * 100 ) / 100; 486 487 FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty]], 'value': value } ); 488 }; 489 } 490 }; 491 } ); 492 493 // TRACK REMOVE ITEMS FROM CLASSIC MINI-CART 494 495 FP.addAction( ['click'], function(){ 496 497 // Skip if tracking should happen in cart instead 498 if ( fp.woo.where_track_addtocart === 'in_cart' ) return; 499 500 if ( ! FP.hasActions( 'woo_remove_from_cart' ) ) return; 501 502 if ( FP.isClickTarget( 'a.remove.remove_from_cart_button' ) ) { 503 504 let product_wrap = fpdata.clicked.element.closest('li'), 505 fupi_product_data_el = FP.findFirst('.fupi_cart_item_data', product_wrap), 506 prod_id = fupi_product_data_el.dataset.product_id; 507 508 if ( fpdata.woo.cart.items[prod_id] ) { 509 510 let prod = { ...fpdata.woo.cart.items[prod_id] }, 511 value = Math.round( prod.price * prod.qty * 100 ) / 100; 512 513 FP.doActions( 'woo_remove_from_cart', { 'products' : [[prod, false]], 'value' : value } ); 514 } 515 } 516 } ); 517 518 // TRACK ADD/REMOVE ITEMS IN THE CLASSIC CART 519 520 // PREPARE CLASSIC CART FOR TRACKING QUANTITY CHANGES 521 // makes sure that adding and removing products in cart is tracked 522 523 function prepare_classic_cart( ajax_update ){ 524 525 let cart_data_el = FP.findFirst('span.fupi_cart_data:not(.fupi_ready)'); 526 527 if ( cart_data_el && !! cart_data_el.innerHTML ) { 528 529 let cart_data = JSON.parse( cart_data_el.innerHTML ); 530
531 if ( ajax_update ) fpdata.woo.cart_old = {...fpdata.woo.cart}; 532 533 fpdata.woo.cart = cart_data; 534 cart_data_el.classList.add('fupi_ready'); // Mark as processed 535 536 if ( fpdata.page_type == "Woo Cart" && fpdata.woo.cart.items && fp.woo.where_track_addtocart === 'in_cart' ) do_addToCart_in_cart(); 537 } 538 }; 539 540 function compare_old_and_new_carts(){ 541 542 let old_cart_items = fpdata.woo.cart_old.items, 543 old_cart_item_keys = Object.keys( old_cart_items ), 544 new_cart_items = fpdata.woo.cart.items, 545 new_cart_item_keys = Object.keys( new_cart_items ), 546 removed = [], 547 removed_val = 0; 548 added = [], 549 added_val = 0; 550 551 old_cart_item_keys.forEach( old_item_key => { 552 553 let old_prod = fpdata.woo.cart_old.items[old_item_key]; 554 555 if ( new_cart_item_keys.includes( old_item_key ) ) { 556 557 let old_qty = old_cart_items[old_item_key].qty, 558 new_qty = new_cart_items[old_item_key].qty; 559 560 if ( old_qty > new_qty ) { 561 let qty_change = old_qty - new_qty; 562 removed.push( [old_prod, qty_change ] ); 563 removed_val += old_prod.price * qty_change; 564 } else if ( old_qty < new_qty ) { 565 let qty_change = new_qty - old_qty; 566 added.push( [old_prod, qty_change ] ); 567 added_val += old_prod.price * qty_change; 568 }; 569 570 } else { 571 removed.push([old_prod, false]); 572 removed_val += old_prod.price * old_prod.qty; 573 }; 574 } ); 575 576 new_cart_item_keys.forEach( new_item_key => { 577 if ( ! old_cart_item_keys.includes( new_item_key ) ) { 578 let new_prod = fpdata.woo.cart.items[new_item_key]; 579 added.push( [new_prod, false ] ); 580 added_val += new_prod.price * new_prod.qty; 581 }; 582 } ); 583 584 if ( added.length > 0 ) { 585 added_val = Math.round( added_val * 100 ) / 100; 586 FP.doActions( 'woo_add_to_cart', { 'products' : added, 'value' : added_val } ); 587 } 588 589 if ( removed.length > 0 ) { 590 removed_val = Math.round( removed_val * 100 ) / 100; 591 FP.doActions( 'woo_remove_from_cart', { 'products' : removed, 'value' : removed_val } ); 592 } 593 }; 594 595 // puts <span>cart_data</span> into fpdata.woo.cart. Must run on page load. We add timeout to wait for the cart contents to load 596 setTimeout( prepare_classic_cart, 300 ); 597 598 // Bind event handler immediately (no timeout) 599 if ( jQuery ) jQuery(document).ready(function($) { 600 $('body').on('updated_cart_totals', function(){ 601 prepare_classic_cart(true);
602 compare_old_and_new_carts(); 603 update_cart_cookie(); 604 }); 605 }); 606 607 function update_cart_cookie(){ 608 // update or create an "fp_woo_cart" cookie, create it based on the value of fpdata.woo.cart.items - it should contain an object of elements, where the keys are the IDs of products and the values are their quantities 609 let cart_cookie_data = {}; 610 for ( let prod_id in fpdata.woo.cart.items ) { 611 cart_cookie_data[prod_id] = fpdata.woo.cart.items[prod_id].qty; 612 } 613 FP.setCookie( 'fp_woo_cart', JSON.stringify(cart_cookie_data), 90 ); 614 } 615 616 // This is used when add to cart events are to be sent only in cart 617 // It creates a "fp.woo.cart_to_track" object with all products that should be added to cart > this is later processed by tracking tools when they load 618 function do_addToCart_in_cart(){ 619 620 // check if there is an "fp_woo_cart" cookie with FP.readCookie() function 621 let fp_woo_cart_cookie = FP.readCookie('fp_woo_cart'), 622 added = [], 623 added_val = 0, 624 cart_to_track = {}; 625 626 // if there is a cookie 627 if ( fp_woo_cart_cookie ) { 628 629 let saved_cart = JSON.parse(fp_woo_cart_cookie); 630 631 // compare ids and quantities of products currently in fpdata.woo.cart.items and those saved in a cookie 632 // get an array of all product IDs and quantities of products which are now in cart but were not in the cookie 633 for ( let prod_id in fpdata.woo.cart.items ) { 634 635 let current_qty = fpdata.woo.cart.items[prod_id].qty; 636 let saved_qty = saved_cart[prod_id] || 0; 637 638 if ( current_qty > saved_qty ) { 639 let qty_change = current_qty - saved_qty; 640 let prod = fpdata.woo.cart.items[prod_id]; 641 added.push( [prod, qty_change] ); 642 added_val += prod.price * qty_change; 643 } 644 } 645 646 // Track added products 647 if ( added.length > 0 ) { 648 added_val = Math.round( added_val * 100 ) / 100; 649 cart_to_track = { 'products' : added, 'value' : added_val }; 650 } 651 652 // if there is no cookie 653 } else { 654 655 // change object with cart items into an array 656 for ( let prod_id in fpdata.woo.cart.items ) { 657 let prod = fpdata.woo.cart.items[prod_id]; 658 added.push( [prod, prod.qty] ); 659 } 660 661 cart_to_track = { 'products' : added, 'value' : fpdata.woo.cart.value }; 662 } 663 664 update_cart_cookie(); 665 FP.doActions( 'woo_add_to_cart', cart_to_track ); 666 } 667 668 // TRACK ADD TO WISHLIST 669 670 if ( fp.woo.wishlist_btn_sel ){ 671 672 FP.addAction( ['click'], function(){ 673 674 if ( ! FP.hasActions( 'woo_add_to_wishlist' ) ) return; 675 676 if ( FP.isClickTarget( fp.woo.wishlist_btn_sel + ', ' + fp.woo.wishlist_btn_sel + ' *' ) ) { 677 678 let product_el = fpdata.clicked.element.closest('.fupi_woo_product'); 679 680 if ( product_el ) { 681 let prod_id = product_el.dataset.fupi_woo_prod_id, 682 prod = fpdata.woo.products[prod_id]; 683 684 if ( prod ) FP.doActions( 'woo_add_to_wishlist', { 'products' : [[prod, 1]], 'value' : prod.price } ); 685 } 686 }; 687 } ); 688 }; 689 690 // TRACK VIEWED VARIANTS AS PRODUCT VIEWS 691 692 if ( fp.woo.variable_tracking_method != 'track_parents' && fp.woo.track_variant_views ) { 693 694 jQuery(document).ready(function($) { 695 696 // Listen for variation change events on the variations form 697 $( 'form.variations_form' ).on( 'woocommerce_variation_has_changed' , function() { // !! do NOT change into an arrow function 698 699 let variant_id = get_current_variant_id(); 700 701 if ( variant_id && fpdata.woo.products[variant_id] && FP.hasActions( 'woo_variant_view' ) ) { 702 FP.doActions( 'woo_variant_view', variant_id ); 703 } 704 } ) 705 } ) 706 } 707 708 function update_fpdata_cart_items( id, new_qty ){ 709 let prod = fpdata.woo.cart.items[id]; 710 if ( prod ) { 711 if ( ! new_qty ) { // product removed 712 // make sure we have a backup of this prod in "products" 713 fpdata.woo.products[id] = structuredClone(prod); 714 delete fpdata.woo.cart.items[id]; 715 } else if ( new_qty != prod.qty ) { 716 prod.qty = new_qty; 717 } 718 } 719 }; 720 721 function add_block_hooks(){ 722 723 // "All products" block (deprecated but still used on some sites) 724 if ( window.wp && window.wp.hooks ) window.wp.hooks.addAction( 725 "experimental__woocommerce_blocks-product-list-render", 726 "fupi-tracking", 727 ( {products, listName } ) => { 728 if ( products.length > 0 && listName == 'woocommerce/all-products' ) { 729 fp.woo.products_from_all_products_block = products; 730 } 731 } 732 ); 733 734 // Use wp.data to subscribe to product changes in the BIG cart block 735 if ( window.wp && window.wp.data && window.wp.data.select( 'wc/store/cart' ) ) { 736 737 const { subscribe } = wp.data;
738 const cartStore = wp.data.select( 'wc/store/cart' ); 739 740 // Initialize with current cart items 741 // The selector is getCartData(), which returns an object containing 'items' 742 let previousCartItems = cartStore.getCartData().items; 743 744 subscribe( () => { 745 const newCartItems = cartStore.getCartData().items; 746 747 // If the array reference is the same, nothing changed 748 if ( newCartItems === previousCartItems ) return; 749 750 // Map items by key for easy comparison 751 const prevMap = new Map( previousCartItems.map( i => [ i.key, i ] ) ); 752 const newMap = new Map( newCartItems.map( i => [ i.key, i ] ) ); 753 754 // 1. Check for REMOVED items 755 for ( const [ key, prevItem ] of prevMap ) { 756 if ( ! newMap.has( key ) ) { 757 let prod = get_prod_from_store_item( prevItem ); 758 let value = Math.round( prod.price * prevItem.quantity * 100 ) / 100; 759 // Trigger remove action 760 FP.doActions( 'woo_remove_from_cart', { 'products' : [[prod, false]], 'value' : value } ); 761 if ( fpdata.page_type == 'Woo Cart' ) update_fpdata_cart_items(prod.id, false); 762 } 763 } 764 765 // 2. Check for QUANTITY changes 766 for ( const [ key, newItem ] of newMap ) { 767 const prevItem = prevMap.get( key ); 768 769 if ( prevItem && newItem.quantity !== prevItem.quantity ) { 770 771 let prod = get_prod_from_store_item( newItem ); 772 let qty_change = Math.abs( newItem.quantity - prevItem.quantity ); 773 let value = Math.round( prod.price * qty_change * 100 ) / 100; 774 775 if ( newItem.quantity > prevItem.quantity ) { 776 // Quantity Increased 777 FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty_change]], 'value': value } ); 778 if ( fpdata.page_type == 'Woo Cart' ) update_fpdata_cart_items(prod.id, newItem.quantity); 779 } else { 780 // Quantity Decreased 781 FP.doActions( 'woo_remove_from_cart', { 'products' : [[prod, qty_change]], 'value' : value } ); 782 if ( fpdata.page_type == 'Woo Cart' ) update_fpdata_cart_items(prod.id, newItem.quantity); 783 } 784 } 785 } 786 787 update_cart_cookie(); 788 // Update previous items for next change 789 previousCartItems = newCartItems; 790 } ); 791 } 792 793 // Helper function to normalize product data from the store item 794 function get_prod_from_store_item( item ) { 795 // Prefer data from fpdata if available (contains more info like categories) 796 if ( fpdata.woo.cart.items && fpdata.woo.cart.items[item.id] ) { 797 return { ...fpdata.woo.cart.items[item.id] }; 798 } 799 800 // Fallback to data available in the store item 801 return { 802 id: item.id, 803 name: item.name, 804 sku: item.sku, 805 price: parseInt( item.prices.price ) / 100, // Price is in minor units 806 qty: item.quantity 807 }; 808 } 809 } 810 811 add_block_hooks(); 812 813 FP.loaded('woo'); // fp.loaded.push('woo'); is at the top 814}; 815 816FP.load('woo', 'fupi_woo', ['footer_helpers']);
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.