skio
Search…
Liquid (skio-plan-picker.liquid)
The Plan Picker Liquid file renders the entire plan picker element server-side prior to page load, allowing nearly zero performance impact on your store. No delayed loading, no flashing on load!
Skio Plan Picker Element

Usage

Usage required:
{% render 'skio-plan-picker' %}
Usage conditionally required:
{% render 'skio-plan-picker', product: product, form_id: form_id, key: key %}
Usage optional:
{% render 'skio-plan-picker', product: product, key: key, form_id: form_id, start_onetime: false, onetime_first: false, discount_format: 'absolute' %}

Parameters

Parameter
Type
Requirement
product
Product
conditionally required
form_id
String
conditionally required
key
String
conditionally required
Boolean
optional
Boolean
optional
String
optional

product

Required on non-product pages, snippets only have access to the globally available product variable by default. If the product variable is defined locally, for example inside a for each product in a collection, the snippet will not have access to the product variable unless explicitly passed.

form_id

Required when plan picker isn't nested in a form[action="/cart/add"]. The form_id has to match the id on the form element. It is used to connect a form field that is outside of a form to it using the form attribute.
<input form="form-123"/>
<form id="form-123">
...
</form>

key

Passed in for uniqueness; defaults to product.id this is particularly useful when using multiple plan pickers on the same page.

start_onetime

for choosing whether the one-time option is selected on page load; defaults to true

onetime_first

for choosing whether the one-time option displays above the selling plan option; defaults to true

discount_format

for formatting selling plan discounts; either 'percent' or 'absolute'; defaults to 'percent'

Page-load & Asynchronous Javascript

In order to have a near-zero impact on store performance and page load speed, the plan picker runs a script to asynchronously load our larger JS file after the DOMContentLoaded event. An eventListener is set on the window to wait until mouseover or focus on one of the following: the plan picker element (fieldset[skio-plan-picker]), a product variant selector on the page ([name="id"]), or .product-form__input for Dawn-based themes. Once triggered, it fires the window.SkioLoadJS function, which injects a <script> tag for the plan picker JS file into <head>, initializing the plan picker's JS functionality. The script will only auto-inject once, and any instances of the plan picker element that load after the initial injection will be auto-initialized once loaded.
skio-plan-picker.liquid
{% comment %} SCRIPT {% endcomment %}
<script>
const selector = 'fieldset[skio-plan-picker], [id="name"], .product-form__input';
// Only loads the script if a plan picker is on the page, and only once
// the main element is hovered over. doesn't load multiple times if there is are multiple plan pickers.
// Defer loading to reduce load impact
if (window.skio_plan_picker_script_load === undefined) {
window.skio_plan_picker_script_load = true;
const script = document.createElement('script');
script.type = 'text/javascript';
script.defer = true;
script.src = '{{ 'skio-plan-picker.js' | asset_url }}';
window.SkioLoadJS = (e) => {
document.head.append(script);
document.querySelectorAll(selector).forEach(el => {
el.removeEventListener('mouseover', window.SkioLoadJS);
el.removeEventListener('focus', window.SkioLoadJS, { capture: true });
});
}
window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll(selector).forEach(el => {
el.addEventListener('mouseover', window.SkioLoadJS);
el.addEventListener('focus', window.SkioLoadJS, { capture: true });
});
});
}
// Handling async load of skio-plan-picker.liquid
if (window.SkioPlanPickerAutoInit) {
window.SkioPlanPickerAutoInit();
} else {
document.querySelectorAll(selector).forEach(el => {
el.addEventListener('mouseover', window.SkioLoadJS);
el.addEventListener('focus', window.SkioLoadJS, { capture: true });
});
}
</script>

CSS Styles

The CSS file skio-plan-picker.css is included immediately after the script, and should be placed in the assets/ folder of your theme. You can make changes to this file if you would like to customize the CSS stylings used.
skio-plan-picker.liquid
{{ 'skio-plan-picker.css' | asset_url | stylesheet_tag }}

Optional Liquid Parameters

Liquid logic determines the current_variant prior to page load via a URL parameter, as well as handling the optional snippet parameters: key, start_onetime, onetime_first, and discount_format .
skio-plan-picker.liquid
{% liquid
assign current_variant = product.selected_or_first_available_variant
unless key
assign key = product.id
endunless
if start_onetime == nil
assign start_onetime = true
endif
if product.requires_selling_plan
assign start_onetime = false
endif
if product.selected_selling_plan
assign start_onetime = false
endif
if onetime_first == nil
assign onetime_first = true
endif
if discount_format == nil
assign discount_format = 'percent'
endif
%}

Plan Picker Element

The fieldset.skio-plan-picker is the plan picker element. It assigns a unique key as an identifier ([skio-plan-picker="key"]) to differentiate between other plan pickers on the page. [skio-auto-init] is an identifier used within the larger JS file.
skio-plan-picker.liquid
<fieldset class="skio-plan-picker" skio-plan-picker="{{ key }}" skio-auto-init skio-discount-format="{{ discount_format }}">
...
</fieldset>

Product JSON

In order to load all product JSON data in the JS file, the below line is required:
skio-plan-picker.liquid
{% comment %} PRODUCT JSON {% endcomment %}
<script type="text/json" skio-product-json>{{ product | json }}</script>

Selling Plan and Discount logic

To properly display the plan picker on page load, Liquid is used to determine the correct selling plan and related discount (if applicable). Using the Product object's selected_selling_plan variable, it checks if there is a selling_plan_id specified in the URL. If so, that is the selling plan to render; else, it defaults to the first compatible selling plan. The selling_plan_id is stored as the value in a hidden input[name="selling_plan"], which is connected to the form[action="/cart/add"] via being embedded in the form or including the form_id Liquid parameter. With the current selling plan determined, Liquid is used to calculate the discounted price (if any) of the selling plan checking the discount_format Liquid parameter to determine how to render the discounted value (i.e. % or using the Liquid money filter). The discount text to display in the cart and at checkout is stored as the value in a hidden input[name=“properties[Discount]”] , which is disabled unless the subscription option is selected and is connected to the form[action="/cart/add"] via being embedded in the form or including the form_id Liquid parameter.
skio-plan-picker.liquid
{% comment %} SELLING PLAN {% endcomment %}
{% liquid
if product.selected_selling_plan
assign selected_selling_plan_id = product.selected_selling_plan.id
else
assign selected_selling_plan_id = product.selling_plan_groups.first.selling_plans.first
endif
%}
<input name="selling_plan" type="hidden" value="{% unless start_onetime %}{{ selected_selling_plan_id }}{% endunless %}"
{% if form_id %}form="{{ form_id }}"{% endif %}
/>
{% liquid
if product.selected_selling_plan
assign price_adjustment = product.selected_selling_plan.price_adjustments.first
elsif start_onetime == false
assign price_adjustment = product.selling_plan_groups.first.selling_plans.first.price_adjustments.first
endif
if price_adjustment
case price_adjustment.value_type
when 'percentage'
assign discount_percent = price_adjustment.value
assign discount_absolute = current_variant.price | times: price_adjustment.value | divided_by: 100.0
when 'fixed_amount'
assign discount_percent = price_adjustment.value | times: 1.0 | divided_by: current_variant.price | times: 100.0
assign discount_absolute = price_adjustment.value
when 'price'
assign discount_percent = current_variant.price | minus: price_adjustment.value | times: 1.0 | divided_by: current_variant.price | times: 100.0
assign discount_absolute = current_variant.price | minus: price_adjustment.value
endcase
if discount_percent == 0
assign discount_text = ''
elsif discount_format == 'percent' or discount_format == blank
assign discount_text = discount_percent | append: '%'
else
assign discount_text = discount_absolute | money
endif
endif
%}
<input name="properties[Discount]" type="hidden" value="{{ discount_text }}" {% if start_onetime %}disabled{% endif %}
{% if form_id %}form="{{ form_id }}"{% endif %}
/>

One-time and Subscription Radio buttons

The primary HTML and Liquid of the Onetime and Subscription radio buttons are captured as Liquid variables, the order of which gets determined by the value of the onetime_first Liquid parameter. The onetime radio button isn’t rendered if the product requires a selling plan regardless of the other Liquid parameters. The entire snippet isn't rendered if the product has no selling plans. The subscription selector uses Liquid logic to generate the selling plan selector for the variant loaded on page load. The same discount logic as earlier determines the amount to display, along with the slashed out non discounted price.
skio-plan-picker.liquid
{% comment %} ONE TIME {% endcomment %}
{% capture onetimeHTML %}
{% unless product.requires_selling_plan %}
<div class="skio-group-container skio-group-container--available{% if start_onetime %} skio-group-container--selected{% endif %}" skio-group-container>
<input id="skio-one-time-{{ key }}" class="skio-group-input" name="skio-group-{{ key }}"
type="radio" value="" skio-one-time {% if start_onetime %}checked{% endif %}>
<label class="skio-group-label" for="skio-one-time-{{ key }}">
<div class="skio-radio-svg__container">
<svg class="skio-radio-svg skio-radio-svg--unchecked" height="18" width="18" viewBox="0 0 24 24" aria-hidden="true" aria-label="" role="img"><path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.6 0 12 0zm0 22.2C6.45 22.2 1.8 17.7 1.8 12 1.8 6.3 6.3 1.8 12 1.8c5.7 0 10.2 4.5 10.2 10.2 0 5.7-4.65 10.2-10.2 10.2z"></path></svg>
<svg class="skio-radio-svg skio-radio-svg--checked" height="18" width="18" viewBox="0 0 24 24" aria-hidden="true" aria-label="" role="img"><path d="M12 0a12 12 0 100 24 12 12 0 000-24zM6.37 11.61a1.25 1.25 0 011.76 0l2.37 2.36 5.37-5.35a1.25 1.25 0 011.76 1.76L10.5 17.5l-4.13-4.13a1.25 1.25 0 010-1.76z"></path></svg>
</div>
<div>
One-time purchase:
<span skio-onetime-price>{{ current_variant.price | money }}</span>
</div>
</label>
</div>
{% endunless %}
{% endcapture %}
{% comment %} SUBSCRIPTIONS {% endcomment %}
{% capture subscriptionHTML %}
{% assign firstSelected = false %}
{% for group in product.selling_plan_groups %}
{% if group.name == 'Subscription' or group.name == 'Prepaid' %}
{% liquid
assign group_available = false
for allocation in current_variant.selling_plan_allocations
if allocation.selling_plan_group_id == group.id
assign group_available = true
endif
endfor
assign group_selected = false
unless start_onetime
if product.selected_selling_plan
if group.selling_plan_selected
assign group_selected = true
endif
elsif firstSelected == false and group_available == true
assign group_selected = true
assign firstSelected = true
endif
endunless
%}
<div class="skio-group-container{%- if group_available %} skio-group-container--available{% endif -%}
{%- if group_selected %} skio-group-container--selected{% endif %}" skio-group-container>
<input id="skio-selling-plan-group-{{ forloop.index }}-{{ key }}" class="skio-group-input"
name="skio-group-{{ key }}"
type="radio" value="{{ group.id }}" {% if group_selected %}checked{% endif %}
skio-selling-plan-group="{{ group.id }}">
<label class="skio-group-label" for="skio-selling-plan-group-{{ forloop.index }}-{{ key }}">
<div class="skio-radio-svg__container">
<svg class="skio-radio-svg skio-radio-svg--unchecked" height="18" width="18" viewBox="0 0 24 24" aria-hidden="true" aria-label="" role="img"><path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.6 0 12 0zm0 22.2C6.45 22.2 1.8 17.7 1.8 12 1.8 6.3 6.3 1.8 12 1.8c5.7 0 10.2 4.5 10.2 10.2 0 5.7-4.65 10.2-10.2 10.2z"></path></svg>
<svg class="skio-radio-svg skio-radio-svg--checked" height="18" width="18" viewBox="0 0 24 24" aria-hidden="true" aria-label="" role="img"><path d="M12 0a12 12 0 100 24 12 12 0 000-24zM6.37 11.61a1.25 1.25 0 011.76 0l2.37 2.36 5.37-5.35a1.25 1.25 0 011.76 1.76L10.5 17.5l-4.13-4.13a1.25 1.25 0 010-1.76z"></path></svg>
</div>
<div>
<span>
{%- if group.name == 'Subscription' -%}
Subscription
{%- else -%}
Prepaid
{%- endif -%}
</span>
{% liquid
for selling_plan in group.selling_plans
assign selected = false
if group.selling_plan_selected
if product.selected_selling_plan.id == selling_plan.id
assign selected = true
endif
elsif forloop.first
assign selected = true
endif
if selected
assign price_adjustment = selling_plan.price_adjustments.first
case price_adjustment.value_type
when 'percentage'
assign discount_percent = price_adjustment.value
assign discount_absolute = current_variant.price | times: price_adjustment.value | divided_by: 100.0
when 'fixed_amount'
assign discount_percent = price_adjustment.value | times: 1.0 | divided_by: current_variant.price | times: 100.0
assign discount_absolute = price_adjustment.value
when 'price'
assign discount_percent = current_variant.price | minus: price_adjustment.value | times: 1.0 | divided_by: current_variant.price | times: 100.0
assign discount_absolute = current_variant.price | minus: price_adjustment.value
endcase
if discount_percent == 0
assign discount_text = ''
elsif discount_format == 'percent' or discount_format == blank
assign discount_text = discount_percent | append: '%'
else
assign discount_text = discount_absolute | money
endif
assign price = current_variant.price | minus: discount_absolute
endif
endfor
%}
<span skio-discount>{{ discount_text }}</span>: <span skio-subscription-price>{{ price | money }}</span>
<div class="skio-frequency">
<div>
Delivery {% if group.selling_plans.size == 1 %}{{ group.selling_plans[0].options[0].value }}{% endif %}
</div>
<select skio-selling-plans="{{ group.id }}" {% if group.selling_plans.size == 1 %}style="display: none;"{% endif %}>
{% for plan in group.selling_plans %}
{% liquid
assign selected = false
if product.selected_selling_plan
if product.selected_selling_plan.id == plan.id
assign selected = true
endif
elsif forloop.first
assign selected = true
endif
%}
<option value="{{ plan.id }}" {% if selected %}selected{% endif %}>{{ plan.options[0].value }}</option>
{% endfor %}
</select>
</div>
</div>
</label>
</div>
{% endif %}
{% endfor %}
{% endcapture %}
After the Liquid variables have been captured, there is a check of the onetime_first Liquid parameter; if true, the one-time option is rendered first, else the subscription option is rendered first.
skio-plan-picker.liquid
{% if onetime_first == true %}
{{ onetimeHTML }}
{{ subscriptionHTML }}
{% else %}
{{ subscriptionHTML }}
{{ onetimeHTML }}
{% endif %}