1/** 2 * Front-end js file. 3 * 4 * @package GTM_Server_Side 5 */ 6 7jQuery( document ).ready( 8 function () { 9 jQuery( document ).on( 10 'click', 11 'body.woocommerce-cart [name=update_cart]', 12 function () { 13 pluginGtmServerSide.changeCartQty(); 14 } 15 ); 16 17 jQuery( document ).on( 18 'keypress', 19 'body.woocommerce-cart .woocommerce-cart-form input[type=number]', 20 function () { 21 pluginGtmServerSide.changeCartQty(); 22 } 23 ); 24 25 jQuery( document ).on( 26 'click', 27 '.add_to_cart_button:not(.product_type_variable, .product_type_grouped, .single_add_to_cart_button)', 28 function ( e ) { 29 var el = e.target; 30 if ( ! el.dataset ) { 31 return; 32 } 33 34 if ( ! el.dataset.gtm_item_id ) { 35 return; 36 } 37 38 let gtmData = pluginGtmServerSide.getGtmItemData( el.dataset ); 39 let customData = pluginGtmServerSide.getCustomItemData( el.dataset ); 40 41 pluginGtmServerSide.pushAddToCart( gtmData ); 42 pluginGtmServerSide.pushSelectItem( gtmData, customData ); 43 } 44 ); 45 46 jQuery( document ).on( 47 'click', 48 '.wc-block-grid__product .add_to_cart_button:not(.product_type_variable, .product_type_grouped, .single_add_to_cart_button)', 49 function ( e ) { 50 var $el = jQuery( this ).closest( '.wc-block-grid__product' ); 51 if ( ! $el.length ) { 52 return; 53 } 54 55 if ( ! $el.data( 'gtm_item_id' ) ) { 56 return; 57 } 58 59 let gtmData = pluginGtmServerSide.getGtmItemData( $el.data() ); 60 let customData = pluginGtmServerSide.getCustomItemData( $el.data() ); 61 62 pluginGtmServerSide.pushAddToCart( gtmData ); 63 pluginGtmServerSide.pushSelectItem( gtmData, customData ); 64 } 65 ); 66 67 jQuery( document ).on( 68 'click', 69 '.single_add_to_cart_button:not(.disabled)', 70 function ( e ) { 71 var $elForm = jQuery( this ).closest( 'form.cart' ); 72 if ( ! $elForm.length ) { 73 return true; 74 } 75 76 if ( $elForm.find( '[name=variation_id]' ).length > 0 ) { 77 pluginGtmServerSide.pushVariationProduct( $elForm ); 78 return; 79 } 80 81 if ( $elForm.hasClass( 'grouped_form' ) ) { 82 pluginGtmServerSide.pushGroupProduct( $elForm ); 83 return; 84 } 85 86 pluginGtmServerSide.pushSimpleProduct( $elForm ); 87 } 88 ); 89 90 /** 91 * Delete from minicart 92 */ 93 jQuery( document ).on( 94 'removed_from_cart', 95 function ( e, fragments, cart_hash, $thisbutton ) { 96 if ( ! $thisbutton.length ) { 97 return; 98 } 99 100 if ( ! $thisbutton.data( 'gtm_item_id' ) ) { 101 return; 102 } 103 104 pluginGtmServerSide.removeFromCart( 105 pluginGtmServerSide.getGtmItemData( $thisbutton.data() ) 106 ); 107 } 108 ); 109 110 /** 111 * Delete from page: /cart 112 */ 113 jQuery( document ).on( 114 'click', 115 '.woocommerce-cart-form .product-remove > a', 116 function ( e ) { 117 var $el = jQuery( e.currentTarget ); 118 119 if ( ! $el.data( 'gtm_item_id' ) ) { 120 return; 121 } 122 123 pluginGtmServerSide.removeFromCart( 124 pluginGtmServerSide.getGtmItemData( $el.data() ) 125 ); 126 } 127 ); 128 } 129); 130 131var pluginGtmServerSide = { 132 pushSimpleProduct: function ( $elForm ) { 133 var item = this.convertInputsToObject( 134 $elForm.find( '[name^=gtm_]' ) 135 ); 136 item = this.getGtmItemData( item ); 137 138 var $elQty = $elForm.find( '[name=quantity]' ); 139 if ( $elQty.length ) { 140 item.quantity = $elQty.val(); 141 } 142 143 this.pushAddToCart( item ); 144 }, 145 146 pushVariationProduct: function ( $elForm ) { 147 var item = this.convertInputsToObject( 148 $elForm.find( '[name^=gtm_]' ) 149 ); 150 item = this.getGtmItemData( item ); 151 152 var $elQty = $elForm.find( '[name=quantity]' ); 153 if ( $elQty.length ) { 154 item.quantity = $elQty.val(); 155 } 156 157 var variations = []; 158 $elForm.find( '[name^=attribute_] option:selected' ).each( 159 function () { 160 variations.push( jQuery( this ).text() ); 161 } 162 ); 163 164 if ( variations.length ) { 165 item.item_variant = variations.join( ',' ); 166 } 167 168 this.pushAddToCart( item ); 169 }, 170 171 pushGroupProduct: function ( $elForm ) { 172 var items = []; 173 $elForm.find( '[name^=quantity\\[]' ).each( 174 function () { 175 if ( ! jQuery( this ).val() ) { 176 return; 177 } 178 179 var $elTd = jQuery( this ).closest( 'td' ); 180 if ( ! $elTd.length ) { 181 return; 182 } 183 184 var item = { 185 quantity: jQuery( this ).val(), 186 }; 187 $elTd.find( '[name^=gtm_]' ).each( 188 function () { 189 item[ jQuery( this ).data( 'name' ) ] = jQuery( this ).val(); 190 } 191 ); 192 items.push( item ); 193 } 194 ); 195 this.pushAddToCart( items ); 196 }, 197 198 /** 199 * Remove from cart 200 * 201 * @param object item 202 */ 203 removeFromCart: function ( item ) { 204 item.quantity = item.quantity || 1; 205 var eventData = { 206 'event': this.getDataLayerEventName( 'remove_from_cart' ), 207 'ecommerce': { 208 'currency': varGtmServerSide.currency, 209 'items': [ 210 item, 211 ], 212 }, 213 }; 214 215 if ( varGtmServerSide.user_data ) { 216 eventData.user_data = {}; 217 for ( var key in varGtmServerSide.user_data ) { 218 eventData.user_data[ key ] = varGtmServerSide.user_data[ key ]; 219 } 220 } 221 222 if ( 'yes' === varGtmServerSide.is_custom_event_name ) { 223 this._pushWithStateCartData( eventData ); 224 225 return; 226 } 227 228 dataLayer.push( { ecommerce: null } ); 229 dataLayer.push( eventData ); 230 }, 231 232 /** 233 * Change product quantity in cart 234 */ 235 changeCartQty: function () { 236 var $this = this; 237 238 document.querySelectorAll( '.product-quantity input.qty' ).forEach( 239 function ( el ) { 240 var originalValue = el.defaultValue; 241 242 var currentValue = parseInt( el.value ); 243 if ( isNaN( currentValue ) ) { 244 currentValue = originalValue; 245 } 246 247 if ( originalValue != currentValue ) { 248 var elCartItem = el.closest( '.cart_item' ); 249 var elDataset = elCartItem && elCartItem.querySelector( '.remove' ); 250 if ( ! elDataset ) { 251 return; 252 } 253 254 if ( originalValue < currentValue ) { 255 var item = $this.getGtmItemData( elDataset.dataset ); 256 item['quantity'] = currentValue - originalValue; 257 258 $this.pushAddToCart( item ); 259 } 260 } 261 } 262 ); 263 }, 264 265 /** 266 * Return gtm custom data. 267 * 268 * @param object items List items. 269 * @returns object 270 */ 271 getGtmItemData: function ( items ) { 272 return this._getItemData( items, 'gtm_' ); 273 }, 274 275 /** 276 * Return gtm custom data. 277 * 278 * @param object items List items. 279 * @returns object 280 */ 281 getCustomItemData: function ( items ) { 282 return this._getItemData( items, 'custom_gtm_' ); 283 }, 284 285 /** 286 * Return item data. 287 * 288 * @param object items List items. 289 * @returns object 290 */ 291 _getItemData: function ( items, prefix ) { 292 var result = {}; 293 for ( var key in items ) { 294 if ( 0 !== key.indexOf( prefix ) ) { 295 continue; 296 } 297
298 var itemKey = key.replace( prefix, '' ) 299 result[ itemKey ] = items[ key ]; 300 } 301 return result; 302 }, 303 304 /** 305 * Convert input elements to object. 306 * 307 * @param object $els Elements. 308 * @returns object 309 */ 310 convertInputsToObject( $els ) { 311 var data = {}; 312 if ( ! $els.length ) { 313 return data; 314 } 315 316 $els.each( 317 function () { 318 data[ jQuery( this ).attr( 'name' ) ] = jQuery( this ).val(); 319 } 320 ); 321 return data; 322 }, 323 324 /** 325 * Filter item price. 326 * 327 * @param object item List items. 328 * @returns object 329 */ 330 filterItemPrice: function ( item ) { 331 if ( typeof item.price == 'string' ) { 332 item.price = parseFloat( item.price ); 333 if ( isNaN( item.price ) ) { 334 item.price = 0; 335 } 336 } else if ( typeof item.price != 'number' ) { 337 item.price = 0; 338 } 339 item.price = item.price.toFixed( 2 ); 340 341 return item; 342 }, 343 344 /** 345 * Push add_to_cart to dataLayer. 346 * 347 * @param mixed item List items. 348 */ 349 pushAddToCart: function ( item ) { 350 this._pushToDataLayer( item, 'add_to_cart', 'product' ) 351 }, 352 353 /** 354 * Push add_to_cart to dataLayer. 355 * 356 * @param object item List items. 357 * @param object custom Custom data. 358 */ 359 pushSelectItem: function ( item, custom ) { 360 if ( custom?.pagetype ) { // phpcs:ignore WordPress.WhiteSpace.OperatorSpacing.NoSpaceBefore, WordPress.WhiteSpace.OperatorSpacing.NoSpaceAfter 361 this._pushToDataLayer( 362 item, 363 'select_item', 364 custom.pagetype, 365 { 366 collection_id: custom?.collection_id, // phpcs:ignore WordPress.WhiteSpace.OperatorSpacing.NoSpaceBefore, WordPress.WhiteSpace.OperatorSpacing.NoSpaceAfter 367 item_list_name: custom?.item_list_name, // phpcs:ignore WordPress.WhiteSpace.OperatorSpacing.NoSpaceBefore, WordPress.WhiteSpace.OperatorSpacing.NoSpaceAfter 368 } 369 ) 370 } 371 }, 372 373 /** 374 * Push to dataLayer. 375 * 376 * @param object originalItem List items. 377 * @param string event Event name. 378 * @param string pagetype Page type name. 379 * @param object customEcommerce Ecommerce data. 380 */ 381 _pushToDataLayer: function ( originalItem, event, pagetype, customEcommerce = {} ) { 382 item = Object.assign( {}, originalItem ); 383 if ( item.item_id ) { 384 item = [ item ]; 385 } 386 387 var items = []; 388 var value = 0; 389 var index = 1; 390 for ( var item_loop of item ) { 391 item_loop.index = index++; 392 item_loop.quantity = item_loop.quantity ? parseInt( item_loop.quantity, 10 ) : 1; 393 item_loop = this.filterItemPrice( item_loop ); 394 value = parseFloat( value + ( item_loop.price * item_loop.quantity ) ); 395 items.push( item_loop ); 396 } 397 398 let eventDataEcommerce = { 399 'currency': varGtmServerSide.currency, 400 'value': value.toFixed( 2 ), 401 'items': items, 402 } 403 404 var eventData = { 405 'event': this.getDataLayerEventName( event ), 406 'ecomm_pagetype': pagetype, 407 'ecommerce': Object.assign( {}, eventDataEcommerce, customEcommerce ), 408 }; 409 if ( varGtmServerSide.user_data ) { 410 eventData.user_data = {}; 411 for ( var key in varGtmServerSide.user_data ) { 412 eventData.user_data[ key ] = varGtmServerSide.user_data[ key ]; 413 } 414 } 415 416 if ( 'add_to_cart' === event && 'yes' === varGtmServerSide.is_custom_event_name ) { 417 this._pushWithStateCartData( eventData ); 418 419 return; 420 } 421 422 dataLayer.push( { ecommerce: null } ); 423 dataLayer.push( eventData ); 424 }, 425 426 /** 427 * Return data layer event name. 428 * 429 * @param string $event_name Event name. 430 * @return string 431 */ 432 getDataLayerEventName: function( event_name ) { 433 if ( 'yes' === varGtmServerSide.is_custom_event_name ) { 434 return event_name + varGtmServerSide.DATA_LAYER_CUSTOM_EVENT_NAME; 435 } 436 return event_name; 437 }, 438 439 /** 440 * Internal helper: actually performs the AJAX request and pushes to the dataLayer. 441 * 442 * @param {object} eventData eventData object. 443 * @return {void} 444 */ 445 _sendStateCartDataAjax: function( eventData ) { 446 jQuery.post( 447 varGtmServerSide.ajax, 448 { 449 action: 'gtm_server_side_state_cart_data', 450 security: varGtmServerSide.security, 451 }, 452 function ( response ) { 453 if ( ! response || ! response.success ) { 454 dataLayer.push( { ecommerce: null } ); 455 dataLayer.push( eventData ); 456 457 return false;
458 } 459 460 eventData['cart_state'] = response.data; 461 462 dataLayer.push( { ecommerce: null } ); 463 dataLayer.push( eventData ); 464 } 465 ); 466 }, 467 468 /** 469 * Push dataLayer with state cart data 470 * 471 * @param {object} eventData Event data object. 472 * @return {void} 473 */ 474 _pushWithStateCartData: function( eventData ) { 475 let self = this; 476 let fired = false; 477 478 var handler = function( event, xhr, settings ) { 479 if ( fired ) { 480 return; 481 } 482 483 if ( ! settings || ! settings.url ) { 484 return; 485 } 486 487 let url = settings.url; 488 489 if ( 490 url.indexOf( 'wc-ajax=add_to_cart' ) !== -1 || 491 url.indexOf( 'wc-ajax=remove_from_cart' ) !== -1 492 ) { 493 fired = true; 494 495 jQuery( document ).off( 'ajaxComplete', handler ); 496 497 self._sendStateCartDataAjax( eventData ); 498 } 499 }; 500 501 jQuery( document ).on( 'ajaxComplete', handler ); 502 503 setTimeout( 504 function() { 505 if ( fired ) { 506 return; 507 } 508 509 jQuery( document ).off( 'ajaxComplete', handler ); 510 self._sendStateCartDataAjax( eventData ); 511 }, 512 1500 513 ); 514 } 515};
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.