1/** 2 * WP EasyCart - default option selections on the product detail page. @since 6.0.0 3 * 4 * The product templates already mark an option item as chosen in the markup when the merchant 5 * flagged it "selected by default", or when a ?<url_var>= / ?o<option_id>= URL variable matches 6 * one of its names. Nothing ever ran the selection routine for that pre-chosen item on load, so: 7 * 8 * - a second level field ( the sizes for the chosen color ) stayed empty and disabled until 9 * the shopper clicked a swatch that already looked selected, 10 * - the hidden ec_option1 field stayed at 0, so add to cart reported a missing option, 11 * - Your Price, the stock count and the option item image kept the product defaults, 12 * - on a single level swatch field, ec_option1_init_swatches( ) in ec-store.js cleared the 13 * ec_selected class the template had just written, so the default lost its highlight. 14 * 15 * This file snapshots what the server rendered before any other script touches it, then applies 16 * the selection once the rest of the store script has finished loading. Levels two and up are 17 * applied by firing the same click / change the shopper would, so there is exactly one request 18 * for the next level's stock - the same one a click makes - and no duplicate of it. A single 19 * level field needs no request at all and is applied directly from the variation data the 20 * template already printed. 21 * 22 * It lives in its own file, enqueued ahead of ec-store.js, so that stores running the minified 23 * build or a theme copy of ec-store.js in wp-easycart-data still receive the fix. 24 * 25 * 6.0.2: window.wpeasycart_option_defaults( container ) does the same for one part of the page, so 26 * product widgets drawn after the page loaded ( Elementor's editor, popups, Loop Grid "load more", 27 * quick views ) get their default selections too. ec-store.js calls it at the start of 28 * wpeasycart_init( container ), before that sets the swatches up. Every option block is looked at 29 * once ( a flag on the element ), so the page's own pass on ready and those later calls never 30 * apply a block twice; on a normal page the ready pass below runs first and nothing changes. 31 */ 32( function() { 33 'use strict'; 34 35 if ( 'undefined' === typeof jQuery ) { 36 return; 37 } 38 39 /* Levels two and up wait for their stock request to come back before being selected. */ 40 var wpeasycart_option_defaults_retries = 40; 41 var wpeasycart_option_defaults_interval = 125; 42 43 /** 44 * Option item quantity tracking decides whether picking a level triggers a stock request. 45 * Backorders turn tracking off, exactly as the handlers in ec-store.js read it. 46 */ 47 function wpeasycart_option_defaults_tracking( product_id, rand_id ) { 48 var backorders = jQuery( document.getElementById( 'ec_allow_backorders_' + product_id + '_' + rand_id ) ); 49 if ( backorders.length && '1' == backorders.val( ) ) { 50 return false; 51 } 52 return '1' == jQuery( document.getElementById( 'use_optionitem_quantity_tracking_' + product_id + '_' + rand_id ) ).val( ); 53 } 54 55 function wpeasycart_option_defaults_swatches( block, product_id, rand_id, level ) { 56 return block.find( 'li.ec_option' + level + '_' + product_id + '_' + rand_id ); 57 } 58 59 function wpeasycart_option_defaults_combo( block, product_id, rand_id, level ) { 60 return block.find( 'select.ec_details_combo.ec_option' + level + '_' + product_id + '_' + rand_id ).first( ); 61 } 62 63 /** 64 * Record the option item each level was rendered with, before ec-store.js gets a chance to 65 * rewrite the swatch classes. Blocks without a default on level one are skipped. 6.0.2: only 66 * the blocks inside container ( and container itself ) not looked at before; returns their entries. 67 */ 68 function wpeasycart_option_defaults_snapshot( container ) { 69 var list = []; 70 var scope = jQuery( container || document ); 71 scope.find( '.ec_details_options_basic' ).add( scope.filter( '.ec_details_options_basic' ) ).each( function( ) { 72 if ( this.wpeasycartOptionDefaults ) { 73 return; 74 } 75 this.wpeasycartOptionDefaults = true; 76 var block = jQuery( this );
77 var product_id = block.attr( 'data-product-id' ); 78 var rand_id = block.attr( 'data-rand-id' ); 79 if ( ! product_id || ! rand_id ) { 80 return; 81 } 82 83 var levels = [ false, false, false, false, false, false ]; 84 var deepest = 0; 85 for ( var level = 1; level <= 5; level++ ) { 86 var swatches = wpeasycart_option_defaults_swatches( block, product_id, rand_id, level ); 87 var combo = wpeasycart_option_defaults_combo( block, product_id, rand_id, level ); 88 if ( ! swatches.length && ! combo.length ) { 89 continue; 90 } 91 deepest = level; 92 93 var selected = swatches.filter( '.ec_selected' ).first( ); 94 if ( selected.length && selected.attr( 'data-optionitem-id' ) ) { 95 levels[ level ] = { type: 'swatch', optionitem_id: String( selected.attr( 'data-optionitem-id' ) ) }; 96 } else if ( combo.length && combo.val( ) && '0' !== String( combo.val( ) ) ) { 97 levels[ level ] = { type: 'combo', optionitem_id: String( combo.val( ) ) }; 98 } 99 } 100 101 if ( levels[1] ) { 102 list.push( { 103 block: block, 104 product_id: product_id, 105 rand_id: rand_id, 106 levels: levels, 107 deepest: deepest, 108 cancelled: false 109 } ); 110 } 111 } ); 112 return list; 113 } 114 115 /** 116 * The element for one level's default, or false while it is not selectable yet - a swatch the 117 * store marked out of stock or hid, or a dropdown still disabled and waiting on its stock 118 * request. Levels two and up are retried until their request lands. 119 */ 120 function wpeasycart_option_defaults_element( entry, level ) { 121 var target = entry.levels[ level ]; 122 if ( 'swatch' === target.type ) { 123 var swatch = wpeasycart_option_defaults_swatches( entry.block, entry.product_id, entry.rand_id, level ) 124 .filter( '[data-optionitem-id="' + target.optionitem_id + '"]' ).first( ); 125 if ( ! swatch.length || ! swatch.hasClass( 'ec_active' ) || 'none' === swatch[0].style.display ) { 126 return false; 127 } 128 return swatch; 129 } 130 131 var combo = wpeasycart_option_defaults_combo( entry.block, entry.product_id, entry.rand_id, level ); 132 if ( ! combo.length || combo.is( ':disabled' ) ) { 133 return false; 134 } 135 var option = combo.find( 'option' ).filter( '[value="' + target.optionitem_id + '"]' ).first( ); 136 if ( ! option.length || option.is( ':disabled' ) || 'none' === option[0].style.display ) { 137 return false; 138 } 139 return combo; 140 } 141 142 /** 143 * Level one of a product whose only option field is that level. Nothing further has to be 144 * loaded, so this applies the selection straight from the variation data already on the page 145 * rather than asking the server for option items that do not exist. 146 */ 147 function wpeasycart_option_defaults_apply_single( entry, element ) { 148 var product_id = entry.product_id; 149 var rand_id = entry.rand_id; 150 var target = entry.levels[1]; 151 var quantity; 152 153 if ( 'swatch' === target.type ) { 154 element.addClass( 'ec_selected' ); 155 jQuery( document.getElementById( 'ec_option1_' + product_id + '_' + rand_id ) ).val( target.optionitem_id ); 156 quantity = element.attr( 'data-optionitem-quantity' ); 157 158 var label = element.closest( '.ec_details_option_row' ).find( '.ec_details_option_label_selected' ).first( ); 159 if ( label.length ) { 160 var image = element.find( 'img' ).first( ); 161 if ( image.length ) { 162 if ( ! image.attr( 'title' ) && image.attr( 'pac_da_title' ) ) { 163 label.html( image.attr( 'pac_da_title' ) ); 164 } else { 165 label.html( image.attr( 'title' ) ); 166 } 167 } else { 168 label.html( element.attr( 'title' ) ); 169 } 170 } 171 } else { 172 quantity = element.find( 'option:selected' ).attr( 'data-optionitem-quantity' ); 173 } 174 175 if ( wpeasycart_option_defaults_tracking( product_id, rand_id ) ) { 176 var variations = window[ 'varitation_data_' + product_id + '_' + rand_id ]; 177 var variation = ( variations && variations[ target.optionitem_id + '0000' ] ) ? variations[ target.optionitem_id + '0000' ] : false; 178 if ( variation ) { 179 quantity = ( variation.tracking ) ? variation.quantity : 'inf'; 180 quantity = ( variation.enabled ) ? quantity : 0; 181 } 182 if ( undefined !== quantity ) { 183 jQuery( document.getElementById( 'ec_details_stock_quantity_' + product_id + '_' + rand_id ) ).html( quantity ); 184 if ( 'inf' != quantity ) { 185 jQuery( '.ec_details_stock_total' ).show( ); 186 } else { 187 jQuery( '.ec_details_stock_total' ).hide( ); 188 } 189 var quantity_input = jQuery( document.getElementById( 'ec_quantity_' + product_id + '_' + rand_id ) ); 190 quantity_input.attr( 'max', quantity ); 191 if ( Number( quantity_input.val( ) ) > Number( quantity ) ) { 192 quantity_input.val( quantity ); 193 } 194 } 195 } 196 197 if ( 'function' === typeof ec_option1_image_change ) { 198 ec_option1_image_change( product_id, rand_id, target.optionitem_id, quantity ); 199 } 200 jQuery( '.ec_option1_' + product_id + '_' + rand_id + '.ec_details_option_row_error' ).hide( ); 201 if ( 'function' === typeof ec_details_base_adjust_price ) { 202 ec_details_base_adjust_price( product_id, rand_id ); 203 } 204 } 205 206 /** 207 * Select one level's default and move on to the next one. Levels are fired as the click or 208 * change the shopper would make, so price, stock, images, the hidden field and the next 209 * level's option items all update through the routines already in ec-store.js. 210 */ 211 function wpeasycart_option_defaults_apply( entry, level, tries ) { 212 if ( entry.cancelled || level > 5 || ! entry.levels[ level ] ) { 213 return; 214 } 215 /* 6.0.2: a block redrawn away meanwhile ( the Elementor editor draws a widget again ) is left alone. */ 216 if ( ! jQuery.contains( document.documentElement, entry.block[0] ) ) { 217 entry.cancelled = true; 218 return; 219 } 220 221 var element = wpeasycart_option_defaults_element( entry, level ); 222 if ( ! element ) { 223 /* Level one is settled by the time the page is ready; deeper levels wait on a request. */ 224 if ( level > 1 && tries < wpeasycart_option_defaults_retries ) { 225 window.setTimeout( function( ) { 226 wpeasycart_option_defaults_apply( entry, level, tries + 1 ); 227 }, wpeasycart_option_defaults_interval ); 228 } 229 return; 230 } 231 232 if ( 1 === level && 1 === entry.deepest ) { 233 wpeasycart_option_defaults_apply_single( entry, element ); 234 return; 235 } 236 237 if ( 'swatch' === entry.levels[ level ].type ) { 238 element.trigger( 'click' ); 239 } else { 240 element.val( entry.levels[ level ].optionitem_id ).trigger( 'change' ); 241 } 242
243 wpeasycart_option_defaults_apply( entry, level + 1, 0 ); 244 } 245 246 /** 247 * Anything the shopper does themselves wins: stop applying the remaining defaults so a 248 * pending level is never forced on top of their own choice. Events raised by this file 249 * carry isTrigger and are ignored here. 250 */ 251 function wpeasycart_option_defaults_watch( entry ) { 252 entry.block.on( 'click.wpeasycartOptionDefaults change.wpeasycartOptionDefaults', 'li, select', function( event ) { 253 if ( ! event.isTrigger ) { 254 entry.cancelled = true; 255 } 256 } ); 257 } 258 259 /** 260 * 6.0.2: snapshot the option blocks inside container now, and select their defaults once the 261 * code running now ( the ready handlers, or the wpeasycart_init() call that asked ) has finished. 262 * 263 * @param {Element|jQuery|Document} container Where to look ( default: the page ). 264 */ 265 function wpeasycart_option_defaults( container ) { 266 var list = wpeasycart_option_defaults_snapshot( container ); 267 if ( ! list.length ) { 268 return; 269 } 270 271 /* Runs once every other ready handler ( or the rest of wpeasycart_init() ), ec-store.js included, has finished. */ 272 window.setTimeout( function( ) { 273 for ( var i = 0; i < list.length; i++ ) { 274 wpeasycart_option_defaults_watch( list[ i ] ); 275 wpeasycart_option_defaults_apply( list[ i ], 1, 0 ); 276 } 277 }, 0 ); 278 } 279 window.wpeasycart_option_defaults = wpeasycart_option_defaults; 280 281 jQuery( document ).ready( function( ) { 282 wpeasycart_option_defaults( document ); 283 } ); 284}( ) );
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.