Javascript (skio-plan-picker.js)

Parameters

ParameterTypeRequirement

Node

Required

Object

Optional

$planPicker

The DOM element to be used as the plan picker

overrides

For overriding specific values (i.e. overrides.product would become the default product)

Events

skio:plan-picker:loaded

(window) when skio script is loaded

skio:plan-picker:init

($planPicker) when skio instance is initialized

skio:plan-picker:updated

($planPicker) when skio instance is updated

File loading & multiple instances

The script starts with a check to ensure that the script has not already been loaded on the page, returning an Error if the script has already loaded; this is in the case of multiple plan picker instances on the page, which may attempt to re-trigger the lazy loading of the JS file. Once we are sure the script hasn't yet loaded, we define a window variable of window.SkioPlanPickerInstances as an empty list, which we will populate with all instances of the plan picker element. With the check complete and array initialized, it initializes the main instance, window.SkioPlanPicker, which immediately initializes the skio object.

skio-plan-picker.js
if (window.SkioPlanPicker) return Error('Skio: Skio already loaded');

  // Skio Instances
  window.SkioPlanPickerInstances = [];
  
  window.SkioPlanPicker = ({ $planPicker, overrides = {} } = {}) => {
    const skio = {};
    ...
    return skio;
  }

Error Reporting

Our built-in error reporting contains 3 levels (info, warn, error) that can provide varying levels of detail for use in debugging potential issues.

For example, the assert() method is immediately used to confirm the following:

  1. The required $planPicker parameter was passed in,

  2. The $planPicker's [skio-plan-picker] property contains a unique key value not used by any other window.SkioPlanPicker instances,

  3. The $planPicker's [skio-discount-format] property contains a valid discount format ('percent' or 'absolute')

skio-plan-picker.js
      // Log level
      skio.logLevel = 'info'; // set to error after setup
      const logLevels = { info: 1, warn: 2, error: 3 };

      function assert(condition, message, { messageType = 'error', exit = false, docsLink }) {
        if (!condition) {
          const formattedMessage = 'Skio' + (skio.key ? `-${skio.key}` : '') + ': ' + message + 
            (docsLink ? `\n\nCheck out ${docsLink}` : ''); 
          if (exit) throw Error(formattedMessage);
          if (!console[messageType]) messageType = 'error';
          if (logLevels[messageType] >= logLevels[skio.logLevel])
            console[messageType](formattedMessage);
          return true;
        }
        return false;
      }
      
      assert($planPicker, 'No $planPicker provided', { exit: true });
      skio.$planPicker = $planPicker;
      
      // KEY
      skio.key = $planPicker.getAttribute('skio-plan-picker');
      const keyCount = document.querySelectorAll(`[skio-plan-picker="${skio.key}"]`).length;
      assert(
        keyCount === 1,
        `Key needs to be unique, key: '${skio.key}' used ${keyCount} times`,
        { exit: true }
      );

      // DISCOUNT FORMAT	
      skio.discountFormat = $planPicker.getAttribute('skio-discount-format');	
      assert(
        skio.discountFormat === 'percent' || skio.discountFormat === 'absolute', 	
        'Invalid discount format', 	
        { exit: true }	
      );

Selectors

The following selectors are utilized to access various elements in the DOM as needed for various pieces of functionality:

skio-plan-picker.liquid
      // SELECTORS
      skio.selectors = {
        sellingPlanId: 'input[name="selling_plan"]',
        variantId: '[name="id"]',
        productJson: '[skio-product-json]',
        onetime: '[skio-one-time]',
        groupContainer: '[skio-group-container]',
        sellingPlanGroup: (id) => `[skio-selling-plan-group${id ? `="${id}"` : ''}]`,
        groupInput: `input[name="skio-group-${skio.key}"]`,
        sellingPlans: (id) => `[skio-selling-plans${id ? `="${id}"` : ''}]`,
        onetimePrice: '[skio-onetime-price]',
        subscriptionPrice: '[skio-subscription-price]',
        discount: '[skio-discount]',
        discountProperty: '[name="properties[Discount]"]',
      };

sellingPlanId

Default value: 'input[name="selling_plan"]' Use: access element inside associated form[action="/cart/add"] to get/update the element's value as variant / selling plan selections change

variantId

Default value: '[name="id"]' Use: access element inside associated form[action="/cart/add"] to get/update the element's value as variant / selling plan selections change

productJson

Default value: '[skio-product-json]' Use: load in the product data that was stored as a JSON script in skio-plan-picker.liquid

onetime

Default value: '[skio-one-time]' Use: access element for the plan picker's one-time radio button / selector

groupContainer

Default value: '[skio-group-container]' Use: access elements used as the parent element / container for the plan picker's one-time radio buttons / selectors

sellingPlanGroup method

Parameter: id of a selling plan group (optional) Default value: `[skio-selling-plan-group${id ? `="${id}"` : ''}]` Use: access elements used as selling plan radio buttons / selectors in order to get the group.id of the selected selling plan / update the checked value of the element

groupInput

Default value: `input[name="skio-group-${skio.key}"]` Use: access input elements used to identify which selling plan is currently selected; if a new groupInput is checked, updateSellingPlan will fire

sellingPlans method

Parameter: id of a selling plan group (optional) Default value: `[skio-selling-plans${id ? `="${id}"` : ''}]` Use: access the select element that displays frequency options

onetimePrice

Default value: '[skio-onetime-price]' Use: access the element containing the value of the one-time price

subscriptionPrice

Default value: '[skio-subscription-price]' Use: access the element containing the value of the subscription price

discount

Default value: '[skio-discount]' Use: access the element containing the value of the discount (absolute or percent, depending on configuration)

discountProperty

Default value: '[name="properties[Discount]"]' Use: access a hidden input element that stores the current value of the discount as an attribute

Form

The $planPicker needs to be associated with a form[action="/cart/add"] element. To associate a form, the $planPicker can either be placed:

  1. Inside a form[action="/cart/add"],

  2. Outside a form if the skio-plan-picker.liquid snippet had the form_id Liquid parameter passed in to indicate the form[action="/cart/add"] to be associated with the $planPicker (i.e. LINK TO LIQUID DOCS HERE ),

  3. Outside a form if there is a form[action="/cart/add"] on the page, in which case the closest form[action="/cart/add"] will be selected

The above selectors are used to assert that there is only 1 selling plan input element and that the associated form[action="/cart/add"] exists.

skio-plan-picker.js
      // FORM
      const sellingPlanEls = $planPicker.querySelectorAll(skio.selectors.sellingPlanId);
      assert(sellingPlanEls.length !== 0, 'No selling plan input element found', { exit: true });
      assert(sellingPlanEls.length === 1, 'More than 1 selling plan input element found', 
        { messageType: 'warn' });
      const sellingPlanEl = sellingPlanEls[0];
      skio.formAttr = sellingPlanEl.getAttribute('form');
  
      if (skio.formAttr) skio.form = document.getElementById(skio.formAttr);
      if (!skio.form) skio.form = $planPicker.closest('form[action*="/cart/add"]');
      assert(
        skio.form, 'Couldn\'t find form, either connect to a form or submit using javascript', 
        { messageType: 'warn' }
      );

Product

If a product object was passed with the overrides parameter, that product data is used for the plan picker functionality. Otherwise, the product data from the productJson selector is loaded in.

skio-plan-picker.js
      // PRODUCT
      if (overrides.product) skio.product = overrides.product;
      else {
        const productJsonEls = $planPicker.querySelectorAll(skio.selectors.productJson);
        assert(productJsonEls.length > 0, 'No product json element found', { exit: true });
        assert(productJsonEls.length === 1, 'Multiple product json elements found', 
          { messageType: 'warn' });
        skio.product = JSON.parse(productJsonEls[0].innerHTML);
      }
      assert(skio.product, 'Product is required', { exit: true });

Last updated