PageSourceSearch

https://www.fluke.com/modules/custom/igcommerce_external_integrati…/storefront_api/shopify-careplan.js?tlqj4u

js fluke.com collected 2026-09-24 08:51:01 UTC 18,513 bytes, 519 lines download raw bytes

1/**
2 * Shopify Care Plan Module
3 * Handles all care plan-related functionality including plan selection and cart operations.
4 * Careplans are currently EN-US only.
5 */
6
7window.ShopifyCareplan = (function () {
8  'use strict';
9
10  let config = null;
11  let elements = null;
12  let carePlanData = null;
13  let allVariants = []; // Store all variants for re-filtering
14  let radioButtonsChecked = false;
15  let dynamicDOM = false; // Whether DOM was built dynamically (NID mode)
16
17  /**
18   * Update care plan options based on filter type
19   * 
20   * @param {string} filterType - The filter type to apply.
21   * 
22   * Example values:
23   * 
24   * - 'all' - Show all variants
25   * - 'bundleplan' - Show only bundle plan variants
26   * - 'onlyplan' - Show only care plan variants
27   */
28  const updateVariantOptions = (filterType = 'all') => {
29    if (!elements.carePlanSelect || !carePlanData) return;
30    
31    // Reset dropdown
32    elements.carePlanSelect.innerHTML = '<option value="">' + Drupal.t('Select a plan:') + '</option>';
33    
34    // Store all variants if not already stored
35    if (allVariants.length === 0) {
36      allVariants = [...carePlanData.variants.edges];
37    }
38    
39    // Filter variants based on type
40    let variants = [];
41    
42    if (filterType === 'all') {
43      variants = allVariants;
44    } else if (filterType === 'bundleplan') {
45      variants = allVariants.filter(edge => {
46        const variant = edge.node;
47        const metafield = variant.frontVariantType || variant.metafield;
48        return metafield && metafield.value.toLowerCase().includes('bundle');
49      });
50    } else if (filterType === 'onlyplan') {
51      variants = allVariants.filter(edge => {
52        const variant = edge.node;
53        const metafield = variant.frontVariantType || variant.metafield;
54        return metafield && metafield.value.toLowerCase().includes('care plan') && !metafield.value.toLowerCase().includes('bundle');
55      });
56    } else if (filterType === 'noplan') {
57      // Reset the dropdown and disable the CTA
58      if (elements.carePlanCTA) {
59        elements.carePlanCTA.disabled = true;
60        elements.carePlanCTA.classList.add('disabled');
61        elements.carePlanCTA.textContent = Drupal.t('Select a plan');
62      }
63      window.selectedCarePlanVariantId = null;
64      return;
65    }
66    
67    // Add variants to dropdown
68    variants.forEach(edge => {
69      const variant = edge.node;
70      const option = document.createElement('option');
71      option.value = variant.id;
72      
73      // Format price
74      const price = parseFloat(variant.price.amount);
75      const formatter = new Intl.NumberFormat(config?.shopifyConfig?.lang || 'en-US', {
76        style: 'currency',
77        currency: 'USD',
78        minimumFractionDigits: 2
79      });
80      const formattedPrice = formatter.format(price);
81      
82      // Create option text
83      let optionText = variant.title;
84      if (variant.title === 'Default Title') {
85        optionText = Drupal.t('Care Plan');
86      }
87      
88      // Check if variant is available
89      if (!variant.availableForSale) {
90        option.disabled = true;
91        optionText += ' ' + Drupal.t('(Out of Stock)');
92      }
93      
94      option.textContent = `${optionText} - ${formattedPrice}`;
95      
96      // Store metafield data as a data attribute for easy access
97      const effectiveMeta = variant.frontVariantType || variant.metafield;
98      if (effectiveMeta) {
99        option.dataset.metafieldValue = effectiveMeta.value;
100      }
101      
102      elements.carePlanSelect.appendChild(option);
103    });
104    
105    // Disable Add to Cart button initially until a selection is made
106    if (elements.carePlanCTA && !window.selectedCarePlanVariantId) {
107      elements.carePlanCTA.disabled = true;
108      elements.carePlanCTA.classList.add('disabled');
109      elements.carePlanCTA.textContent = Drupal.t('Select a plan');
110    }
111  };
112
113  /**
114   * Handle care plan selection change
115   * 
116   * @param {Event} event - The event object.
117   * 
118   * Example values:
119   * 
120   * - 'all' - Show all variants
121   * - 'bundleplan' - Show only bundle plan variants
122   * - 'onlyplan' - Show only care plan variants
123   */
124  const handleCarePlanSelection = (event) => {
125    if (!carePlanData) return;
126    
127    const variantId = event.target.value;
128    
129    if (!elements.carePlanCTA) return;
130    
131    if (!variantId) {
132      // No variant selected, select "No thanks" radio
133      const noPlanRadio = document.getElementById('ecom_careplan_none');
134      if (noPlanRadio) {
135        noPlanRadio.checked = true;
136      }
137      
138      // Disable the CTA
139      elements.carePlanCTA.disabled = true;
140      elements.carePlanCTA.classList.add('disabled');
141      elements.carePlanCTA.textContent = Drupal.t('Select a plan');
142      window.selectedCarePlanVariantId = null;
143      
144      // Show all variants
145      updateVariantOptions('all');
146      return;
147    }
148    
149    // Find selected variant
150    const selectedVariant = carePlanData.variants.edges.find(
151      edge => edge.node.id === variantId
152    )?.node;
153    
154    if (!selectedVariant) {
155      elements.carePlanCTA.disabled = true;
156      elements.carePlanCTA.classList.add('disabled');
157      elements.carePlanCTA.textContent = Drupal.t('Select a plan');
158      window.selectedCarePlanVariantId = null;
159      return;
160    }
161
162    // Check if variant is available
163    if (!selectedVariant.availableForSale) {
164      elements.carePlanCTA.disabled = true;
165      elements.carePlanCTA.classList.add('disabled');
166      elements.carePlanCTA.textContent = Drupal.t('Out of Stock');
167      window.selectedCarePlanVariantId = null;
168      return;
169    }
170    
171    // Update global variable for selected variant
172    window.selectedCarePlanVariantId = selectedVariant.id;
173    
174    // Check metafield to determine which radio button to select
175    let selectedFilterType = 'onlyplan'; // Default to "Premium Care Plan only"
176    
177    const effectiveVariantMeta = selectedVariant.frontVariantType || selectedVariant.metafield;
178    if (effectiveVariantMeta) {
179      const metafieldValue = effectiveVariantMeta.value.toLowerCase();
180      
181      if (metafieldValue.includes('bundle')) {
182        // Select "Product + Premium Care Plan" radio
183        const bundlePlanRadio = document.getElementById('ecom_careplan_bundle');
184        if (bundlePlanRadio) {
185          bundlePlanRadio.checked = true;
186          selectedFilterType = 'bundleplan';
187        }
188      } else if (metafieldValue.includes('care plan')) {
189        // Select "Premium Care Plan only" radio
190        const onlyPlanRadio = document.getElementById('ecom_careplan_only');
191        if (onlyPlanRadio) {
192          onlyPlanRadio.checked = true;
193          selectedFilterType = 'onlyplan';
194        }
195      } else {
196        // If no specific metafield match, default to "Premium Care Plan only"
197        const onlyPlanRadio = document.getElementById('ecom_careplan_only');
198        if (onlyPlanRadio) {
199          onlyPlanRadio.checked = true;
200          selectedFilterType = 'onlyplan';
201        }
202      }
203    } else {
204      // If no metafield, default to "Premium Care Plan only"
205      const onlyPlanRadio = document.getElementById('ecom_careplan_only');
206      if (onlyPlanRadio) {
207        onlyPlanRadio.checked = true;
208        selectedFilterType = 'onlyplan';
209      }
210    }
211    
212    // Filter the dropdown options based on the selected radio button
213    // Save the current selection
214    const currentSelection = variantId;
215    
216    // Update the dropdown options
217    updateVariantOptions(selectedFilterType);
218    
219    // Restore the current selection
220    if (elements.carePlanSelect) {
221      elements.carePlanSelect.value = currentSelection;
222      
223      // Make sure window.selectedCarePlanVariantId is set correctly
224      window.selectedCarePlanVariantId = currentSelection;
225    }
226    
227    // Care plans are always considered available
228    // Enable the button regardless of availableForSale or quantityAvailable
229    elements.carePlanCTA.disabled = false;
230    elements.carePlanCTA.classList.remove('disabled');
231    elements.carePlanCTA.textContent = Drupal.t('Add to Cart');
232  };
233
234  /**
235   * Helper to check and remove a radio button if no options match the filter
236   * @param {string} radioId - The ID of the radio button element
237   * @param {function(HTMLOptionElement): boolean} filterFn - The filter function to test options
238   */
239  const removeRadioIfNoOptions = (radioId, filterFn) => {
240    const radio = document.getElementById(radioId);
241    const hasOption = Array.from(elements.carePlanSelect.options).some(filterFn);
242    if (!hasOption && radio) {
243      radio.parentElement.remove();
244    }
245  };
246
247  /**
248   * Check if radio buttons should be removed based on available options
249   * Only runs once during initialization
250   */
251  const checkAndRemoveUnusedRadioButtons = () => {
252    if (!elements.carePlanSelect || radioButtonsChecked) return;
253    
254    radioButtonsChecked = true; // Set flag to prevent multiple runs
255    
256    // Remove "only plan" radio if no matching options
257    removeRadioIfNoOptions('ecom_careplan_only', option => {
258      const metafieldValue = option.dataset.metafieldValue;
259      return metafieldValue &&
260        metafieldValue.toLowerCase().includes('care plan') &&
261        !metafieldValue.toLowerCase().includes('bundle');
262    });
263    
264    // Remove "bundle plan" radio if no matching options
265    removeRadioIfNoOptions('ecom_careplan_bundle', option => {
266      const metafieldValue = option.dataset.metafieldValue;
267      return metafieldValue && metafieldValue.toLowerCase().includes('bundle');
268    });
269  };
270
271  /**
272   * Handle radio button selection
273   * 
274   * @param {Event} event - The event object.
275   */
276  const handleRadioSelection = (event) => {
277    const filterType = event.target.value;
278    
279    // Update dropdown options based on filter type
280    if (filterType === 'noplan') {
281      // Show all options when "No thanks" is selected
282      updateVariantOptions('all');
283    } else {
284      updateVariantOptions(filterType);
285    }
286    
287    // Reset the selected variant
288    window.selectedCarePlanVariantId = null;
289    
290    // Disable the CTA if "No thanks" is selected
291    if (filterType === 'noplan' && elements.carePlanCTA) {
292      elements.carePlanCTA.disabled = true;
293      elements.carePlanCTA.classList.add('disabled');
294      elements.carePlanCTA.textContent = Drupal.t('Select a plan');
295    }
296  };
297
298  /**
299   * Build careplan DOM dynamically for NID mode (front.legacy_careplan).
300   * Mirrors the Twig-rendered markup from igcommerce-products-sub-category-storefront.html.twig
301   * so existing SCSS for #ecomm-careplan-info and #ecomm-careplan applies.
302   */
303  const buildDynamicDOM = () => {
304    // Place careplan AFTER the ecommerce section (as a sibling), not inside #buybox-addons.
305    // This matches the Twig layout where careplan is a separate box below the main buybox.
306    const ecommSection = document.getElementById('fluke-product-display-ecommerce');
307    const container = ecommSection?.parentNode;
308    if (!container) return false;
309
310    // Detect locale from URL path for the "Learn more" link
311    const pathParts = window.location.pathname.split('/').filter(Boolean);
312    const lang = pathParts[0] || 'en-us';
313
314    const wrapper = document.createElement('div');
315    wrapper.id = 'careplan-dynamic-wrapper';
316
317    // --- Info section: heading + link + radio buttons ---
318    const info = document.createElement('span');
319    info.id = 'ecomm-careplan-info';
320
321    // Heading label with "Learn more" link
322    const headingLabel = document.createElement('label');
323    headingLabel.textContent = Drupal.t('Add a Fluke Premium Care Plan');
324
325    const learnMore = document.createElement('a');
326    learnMore.target = '_blank';
327    learnMore.id = 'careplan-details';
328    learnMore.className = 'fluke-careplan-information';
329    learnMore.href = `/${lang}/support/customer-services/premium`;
330    learnMore.textContent = Drupal.t('Learn more about care plans');
331    headingLabel.appendChild(learnMore);
332
333    info.appendChild(headingLabel);
334
335    // Radio buttons (wrapped in <div> to match Twig structure)
336    const radios = [
337      { id: 'ecom_careplan_bundle', value: 'bundleplan', label: Drupal.t('Product + Premium Care Plan') },
338      { id: 'ecom_careplan_only', value: 'onlyplan', label: Drupal.t('Premium Care Plan only (I already own the product)') },
339      { id: 'ecom_careplan_none', value: 'noplan', label: Drupal.t('No thanks'), checked: true }
340    ];
341
342    radios.forEach(r => {
343      const div = document.createElement('div');
344      const input = document.createElement('input');
345      input.type = 'radio';
346      input.id = r.id;
347      input.name = 'ecom_careplan';
348      input.value = r.value;
349      if (r.checked) input.checked = true;
350      div.appendChild(input);
351      div.appendChild(document.createTextNode(r.label));
352      info.appendChild(div);
353    });
354
355    wrapper.appendChild(info);
356
357    // --- Plan section: select dropdown + CTA button ---
358    // Matches Twig's shopify-buy-frame structure for SCSS targeting
359    const planSection = document.createElement('span');
360    planSection.id = 'ecomm-careplan';
361    planSection.className = 'shopify-buy-frame shopify-buy-frame--product';
362
363    const productDiv = document.createElement('div');
364    productDiv.className = 'has-image shopify-buy__layout-vertical shopify-buy__product';
365
366    // Variant selectors wrapper
367    const variantSelectors = document.createElement('div');
368    variantSelectors.className = 'shopify-buy__product__variant-selectors';
369    const optionSelect = document.createElement('div');
370    optionSelect.className = 'shopify-buy__option-select careplan-select-container';
371
372    const selectWrapper = document.createElement('div');
373    selectWrapper.className = 'shopify-buy__option-select-wrapper';
374
375    const select = document.createElement('select');
376    select.id = 'careplan-select';
377    select.className = 'careplan-select shopify-buy__option-select__select';
378    select.name = 'Size';
379
380    selectWrapper.appendChild(select);
381    optionSelect.appendChild(selectWrapper);
382    variantSelectors.appendChild(optionSelect);
383    productDiv.appendChild(variantSelectors);
384
385    // CTA button wrapper
386    const btnWrapper = document.createElement('div');
387    btnWrapper.className = 'shopify-buy__btn-wrapper';
388
389    const cta = document.createElement('button');
390    cta.id = 'careplan-select-cta';
391    cta.className = 'careplan-atc';
392    cta.disabled = true;
393    cta.textContent = Drupal.t('Select a plan');
394
395    btnWrapper.appendChild(cta);
396    productDiv.appendChild(btnWrapper);
397    planSection.appendChild(productDiv);
398    wrapper.appendChild(planSection);
399
400    // Insert after the ecommerce section, not inside it
401    ecommSection.insertAdjacentElement('afterend', wrapper);
402
403    // Update element references
404    elements.carePlanSelect = select;
405    elements.carePlanCTA = cta;
406
407    return true;
408  };
409
410  // Initialize event listeners
411  const initializeEventListeners = () => {
412    // Care plan radio buttons
413    const carePlanRadios = document.querySelectorAll('input[name="ecom_careplan"]');
414    if (carePlanRadios.length) {
415      carePlanRadios.forEach(radio => {
416        radio.addEventListener('change', handleRadioSelection);
417      });
418      
419      // Set initial filter based on checked radio
420      const checkedRadio = document.querySelector('input[name="ecom_careplan"]:checked');
421      if (checkedRadio) {
422        // If "No thanks" is checked, show all options
423        if (checkedRadio.value === 'noplan') {
424          updateVariantOptions('all');
425        } else {
426          updateVariantOptions(checkedRadio.value);
427        }
428      } else {
429        // Default to 'all' if no radio is checked
430        updateVariantOptions('all');
431      }
432    } else {
433      // No radio buttons found, just show all options
434      updateVariantOptions('all');
435    }
436    
437    // Care plan select change
438    if (elements.carePlanSelect) {
439      elements.carePlanSelect.addEventListener('change', (event) => {
440        handleCarePlanSelection(event);
441      });
442    }
443      // Check and remove unused radio buttons after initial options are populated
444    checkAndRemoveUnusedRadioButtons();
445    
446    // Care plan Add to Cart button
447    if (elements.carePlanCTA) {
448      elements.carePlanCTA.addEventListener('click', async (event) => {
449        // Prevent event propagation to avoid document click handler
450        event.preventDefault();
451        event.stopPropagation();
452        
453        if (!window.selectedCarePlanVariantId) {
454          alert(Drupal.t('Please select a care plan'));
455          return;
456        }
457        
458        try {
459          await window.ShopifyCart.addToCart(window.selectedCarePlanVariantId, 1);
460        } catch (error) {
461          console.error('Error adding care plan to cart:', error);
462        }
463      });
464    }
465  };
466
467  return {
468    init: async function (shopifyConfig, optionalElements) {
469      config = shopifyConfig;
470      elements = optionalElements || {};
471
472      try {
473        let careplanGid = null;
474
475        if (config.legacyCareplanGid) {
476          // NID mode: GID provided directly from front.legacy_careplan metafield
477          careplanGid = config.legacyCareplanGid;
478          dynamicDOM = true;
479        } else if (config.careplanID) {
480          // Legacy ID mode: numeric ID from Solr/drupalSettings
481          careplanGid = `gid://shopify/Product/${config.careplanID}`;
482        } else {
483          return;
484        }
485
486        // Build dynamic DOM if needed (NID mode — no Twig-rendered markup)
487        if (dynamicDOM) {
488          if (!buildDynamicDOM()) {
489            console.warn('Care plan: could not build dynamic DOM (missing #buybox-addons)');
490            return;
491          }
492        }
493
494        // Load care plan data
495        const result = await window.ShopifyAPIClient.getProduct(careplanGid);
496        if (result.data?.product) {
497          carePlanData = result.data.product;
498
499          // Initialize care plan features
500          initializeEventListeners();
501
502          // Ensure the CTA button starts with "Select a plan" text
503          if (elements.carePlanCTA) {
504            elements.carePlanCTA.textContent = Drupal.t('Select a plan');
505          }
506        } else {
507          console.debug('Care plan data not found');
508          // Clean up dynamic DOM if careplan product not found
509          if (dynamicDOM) {
510            const wrapper = document.getElementById('careplan-dynamic-wrapper');
511            if (wrapper) wrapper.remove();
512          }
513        }
514      } catch (error) {
515        console.error('Error initializing care plan:', error);
516      }
517    }
518  };
519})(); 

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.