Created
March 16, 2023 07:40
-
-
Save panoply/f7b27709fa6e8b525317b8741e60553f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% if type == 'heading' %} | |
<div class="col-12 mt-{{ block.mt }} mb-{{ block.mb }} tc"> | |
<h1 class="mb-4"> | |
{{ block.title }} | |
</h1> | |
<h4 class="my-3 fc-dark-gray italic uncase"> | |
{{ block.subtitle }} | |
</h4> | |
<div class="{{ block.fs }} {{ block.ta }}"> | |
{{ block.description }} | |
</div> | |
</div> | |
{% elsif type == 'hidden' %} | |
<input | |
class="d-none" | |
type="hidden" | |
name="{{ block.name }}" | |
value="{{ block.value }}" | |
data-form-target="field"> | |
{% elsif type == 'text' %} | |
<div class="col-{{ block.xs }} col-md-{{ block.md }} col-xl-{{ block.xl }} mt-{{ block.mt }} mb-{{ | |
block.mb }}"> | |
<div class="fm-float"> | |
<input | |
class="fm-input" | |
data-form-target="field" | |
data-action="form#onInput" | |
type="{{ type }}" | |
id="{{ block.name }}" | |
name="{{ block.name }}" | |
placeholder="{{ block.placeholder }}" | |
autocomplete="{{ block.autocomplete }}" | |
minlength="{{ block.minlength }}" | |
maxlength="{{ block.maxlength }}" | |
required="{{ block.required }}"> | |
<label | |
class="fm-label" | |
for="{{ block.name }}"> | |
{{ block.placeholder }} | |
</label> | |
</div> | |
</div> | |
{% elsif type == 'file' %} | |
<div class="col-{{ block.xs }} col-md-{{ block.md }} col-xl-{{ block.xl }}"> | |
<input | |
class="fm-input" | |
data-form-target="field" | |
data-action="form#onInput" | |
type="{{ type }}" | |
id="{{ block.name }}" | |
name="{{ block.name }}" | |
multiple="{{ block.multiple }}" | |
required="{{ block.required }}"> | |
<label | |
for="{{ block.name }}" | |
class="fm-label"> | |
{{ block.placeholder }} | |
</label> | |
</div> | |
{% elsif type == 'email' %} | |
<div class="col-{{ block.xs }} col-md-{{ block.md }} col-xl-{{ block.xl }} mt-{{ block.mt }} mb-{{ | |
block.mb }}"> | |
<div class="fm-float"> | |
<input | |
class="fm-input" | |
data-form-target="field" | |
data-action="form#onInput" | |
type="{{ type }}" | |
name="{{ block.name }}" | |
id="{{ block.name }}" | |
placeholder="{{ block.placeholder }}" | |
required="{{ block.required }}" | |
autocomplete="{{ block.autocomplete }}"> | |
<label | |
class="fm-label" | |
for="{{ block.name }}"> | |
{{ block.placeholder }} | |
</label> | |
</div> | |
</div> | |
{% elsif type == 'tel' %} | |
<div class="col-{{ block.xs }} col-md-{{ block.md }} col-xl-{{ block.xl }} mt-{{ block.mt }} mb-{{ | |
block.mb }}"> | |
<div class="fm-float"> | |
<input | |
class="fm-input" | |
data-form-target="field" | |
data-action="form#onInput" | |
type="{{ type }}" | |
id="{{ block.name }}" | |
name="{{ block.name }}" | |
required="{{ block.required }}" | |
autocomplete="{{ block.autocomplete }}"> | |
<label | |
class="fm-label" | |
for="{{ block.name }}"> | |
{{ block.placeholder }} | |
</label> | |
</div> | |
</div> | |
{% elsif type == 'textarea' %} | |
<div class="col-12 mt-{{ block.mt }} mb-{{ block.mb }}"> | |
<div class="fm-float"> | |
<textarea | |
class="fm-input" | |
data-form-target="field" | |
data-action="form#onInput" | |
style="height: {{ block.rows }};" | |
name="{{ block.name }}" | |
id="{{ block.name }}" | |
required="{{ block.required }}"></textarea> | |
<label | |
class="fm-label" | |
for="{{ block.name }}"> | |
{{ block.placeholder }} | |
</label> | |
</div> | |
</div> | |
{% elsif type == 'dropdown' %} | |
<div class="col-12 mt-{{ block.mt }} mb-{{ block.mb }}"> | |
<div | |
class="fm-dropdown fm-float" | |
data-controller="dropdown" | |
data-form-target="dropdown"> | |
<button | |
type="button" | |
class="btn btn-dropdown fw-light" | |
data-action="dropdown#toggle" | |
data-dropdown-target="button"> | |
{{ block.placeholder }} | |
</button> | |
<fieldset> | |
{% assign items = block.items | newline_to_br | split: '<br />' %} | |
{% for item in items %} | |
{% liquid | |
# STRIP + CLEAN | |
assign value = item | strip_html | strip | |
assign id = value | handle | |
%} | |
<label | |
class="upper" | |
for="{{ id }}"> | |
{{ value }} | |
</label> | |
<input | |
class="d-none" | |
type="radio" | |
name="{{ block.name }}" | |
id="{{ id }}" | |
value="{{ value }}" | |
aria-label="{{ value }}" | |
data-form-target="field" | |
data-action="dropdown#select form#onInput" | |
{% if forloop.first and block.required %} | |
required | |
{% endif %}> | |
{% endfor %} | |
</fieldset> | |
</div> | |
</div> | |
{% elsif type == 'switch' %} | |
<div class="col-12 mt-{{ block.mt }} mb-{{ block.mb }}"> | |
<div class="fm-switch"> | |
{% capture name %} | |
{% if block.consent == true %} | |
{{ 'forms.field.accept_marketing' | t }} | |
{% else %} | |
{{ block.name }} | |
{% endif %} | |
{% endcapture %} | |
<input | |
type="checkbox" | |
data-form-target="field" | |
data-action="form#onInput" | |
class="fm-check-input" | |
id="{{ name }}" | |
name="{{ name }}" | |
required="{{ block.required }}" | |
{% if block.checked %} | |
checked | |
{% endif %}> | |
<label | |
for="{{ name }}" | |
class="fm-check-label upper fs-sm"> | |
{{ block.label }} | |
</label> | |
</div> | |
</div> | |
{% elsif type == 'paragraph' %} | |
{% capture class %} | |
{{ block.fc }} | |
{{ block.ff }} | |
{{ block.fs }} | |
{{ block.ta }} | |
{% endcapture %} | |
<div class="col-{{ block.xs }} col-md-{{ block.md }} col-xl-{{ block.xl }}"> | |
<div class="fs-xs fw-light fc-mute {{ class }} mt-{{ block.mt }} mb-{{ block.mb }}"> | |
{{ block.content }} | |
</div> | |
</div> | |
{% endif %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div | |
class="col-{{ section.settings.xs }} col-md-{{ section.settings.md }} col-xl-{{ section.settings.xl }}" | |
data-controller="form" | |
data-form-type-value="newsletter:advert" | |
data-form-response-value="toggle" | |
data-form-success-value="{{ section.settings.response }}" | |
data-form-klaviyo-value="{{ section.settings.action }}"> | |
{{ product.collections | append: '' | append: article.image.src }} | |
{% if x == customer.email %}{% endif %} | |
<form | |
class="{{ section.settings.row }} ac-center py-{{ section.settings.py }}" | |
method="post" | |
accept-charset="UTF-8" | |
data-action="form#onSubmit" | |
data-form-target="form" | |
action="{{ request.path }}" | |
novalidate> | |
{% for block in section.blocks %} | |
{% render 'form-fields' with block.settings as block, type: block.type %} | |
{% endfor %} | |
{% if section.blocks and section.settings.btn_label %} | |
<div class="col-12 {{ section.settings.btn_position }}"> | |
<button | |
class="btn btn-black mt-4 py-3 w-100 tc fw-light" | |
type="submit" | |
data-form-target="submit"> | |
{{ section.settings.btn_label }} | |
</button> | |
</div> | |
{% endif %} | |
</form> | |
<div class="tc fs-lg ac-center d-none"> | |
<div data-form-target="response"> | |
{% # FORM RESPONSE > DYNAMICALLY POPULATED %} | |
</div> | |
{% if section.settings.cta_label and section.settings.cta %} | |
<a | |
href="{{ section.settings.cta }}" | |
data-spx-disable="true" | |
class="btn btn-link btn-black fw-light my-3"> | |
{{ section.settings.cta_label }} | |
</a> | |
{% endif %} | |
{% if section.settings.help %} | |
<p class="fs-xs my-3"> | |
If you did not receive the discount code, check your spam mailbox. | |
Contact | |
<a href="mailto:webshop@brixtoltextiles.com"> | |
support | |
</a> | |
if you need assistance. | |
</p> | |
{% endif %} | |
</div> | |
</div> | |
{% schema %} | |
{ | |
"name": "Form", | |
"tag": "section", | |
"class": "row g-0 jc-center", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Form Type", | |
"info": "Define the type of form to generate. This required." | |
}, | |
{ | |
"type": "text", | |
"id": "action", | |
"label": "Action", | |
"info": "The action of the form - Define Klaviyo List ID here (if required)." | |
}, | |
{ | |
"type": "select", | |
"id": "type", | |
"label": "Type", | |
"options": [ | |
{ | |
"value": "klaviyo", | |
"label": "Klaviyo" | |
}, | |
{ | |
"value": "contact", | |
"label": "Contact" | |
}, | |
{ | |
"value": "other", | |
"label": "Other" | |
} | |
] | |
}, | |
{ | |
"type": "header", | |
"content": "Response", | |
"info": "The response to show after the form was submitted successfully." | |
}, | |
{ | |
"type": "richtext", | |
"id": "response", | |
"label": "Response", | |
"info": "Shown when form was successful. Use template ${} literals to target form values." | |
}, | |
{ | |
"type": "text", | |
"id": "cta_label", | |
"label": "CTA Label", | |
"placeholder": "Continue Shopping", | |
"info": "Add a CTA Button to the end of the response" | |
}, | |
{ | |
"type": "url", | |
"id": "cta", | |
"label": "CTA Link", | |
"info": "CTA Link in the response" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "private_sale", | |
"label": "Private Sale", | |
"info": "Whether or not this form should show private sale links", | |
"default": false | |
}, | |
{ | |
"type": "checkbox", | |
"id": "help", | |
"label": "Show Help", | |
"info": "Whether or not to append help text below response." | |
}, | |
{ | |
"type": "header", | |
"content": "Submit Button", | |
"info": "The Submit button which sends the form" | |
}, | |
{ | |
"type": "text", | |
"label": "Label", | |
"id": "btn_label" | |
}, | |
{ | |
"type": "select", | |
"id": "btn_position", | |
"label": "Position", | |
"default": "tr", | |
"options": [ | |
{ | |
"value": "tl", | |
"label": "Left" | |
}, | |
{ | |
"value": "tc", | |
"label": "Center" | |
}, | |
{ | |
"value": "tr", | |
"label": "Right" | |
} | |
] | |
}, | |
{ | |
"type": "header", | |
"content": "Grid Layout", | |
"info": "Control layout and alignment of the grid system" | |
}, | |
{ | |
"type": "select", | |
"id": "align", | |
"label": "Alignment", | |
"default": "m-auto", | |
"options": [ | |
{ | |
"value": "mr-auto", | |
"label": "Left" | |
}, | |
{ | |
"value": "m-auto", | |
"label": "Center" | |
}, | |
{ | |
"value": "ml-auto", | |
"label": "Right" | |
} | |
] | |
}, | |
{ | |
"type": "range", | |
"id": "xl", | |
"label": "Desktop Columns", | |
"info": "The amount of columns in desktop", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "md", | |
"label": "Tablet Columns", | |
"info": "The amount of columns in tablet", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "xs", | |
"label": "Mobile Columns", | |
"info": "The amount of columns in mobile", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
} | |
], | |
"blocks": [ | |
{ | |
"type": "heading", | |
"name": "Heading", | |
"limit": 1, | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Heading", | |
"info": "Provide a heading and description paragraph." | |
}, | |
{ | |
"type": "text", | |
"id": "title", | |
"label": "Title" | |
}, | |
{ | |
"type": "text", | |
"id": "subtitle", | |
"label": "SubTitle" | |
}, | |
{ | |
"type": "richtext", | |
"id": "description", | |
"label": "Description" | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter Y", | |
"info": "Control the margin top and bottom spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
} | |
] | |
}, | |
{ | |
"type": "paragraph", | |
"name": "Paragraph", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Paragraph", | |
"info": "Renders a block of text content" | |
}, | |
{ | |
"type": "richtext", | |
"id": "content", | |
"label": "Content", | |
"info": "The text to render" | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
}, | |
{ | |
"type": "header", | |
"content": "Layout", | |
"info": "Control layout and alignment of the grid system" | |
}, | |
{ | |
"type": "range", | |
"id": "xl", | |
"label": "Desktop Columns", | |
"info": "The amount of columns in desktop", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "md", | |
"label": "Tablet Columns", | |
"info": "The amount of columns in tablet", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "xs", | |
"label": "Mobile Columns", | |
"info": "The amount of columns in mobile", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
} | |
] | |
}, | |
{ | |
"type": "textarea", | |
"name": "Textarea", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Textarea Input", | |
"info": "Renders a Textarea container" | |
}, | |
{ | |
"type": "text", | |
"id": "label", | |
"label": "Label", | |
"info": "Defines the label of the textarea" | |
}, | |
{ | |
"type": "text", | |
"id": "placeholder", | |
"label": "Placeholder", | |
"info": "Describes an expected value for the textarea" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "require", | |
"label": "Require", | |
"default": true, | |
"info": "Whether or input is required" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "required", | |
"info": "The form requires this input to be filled", | |
"label": "Required" | |
}, | |
{ | |
"type": "header", | |
"content": "Layout", | |
"info": "Control layout and alignment of the grid system." | |
}, | |
{ | |
"type": "range", | |
"id": "height", | |
"label": "Height", | |
"info": "The height of the textarea input", | |
"min": 1, | |
"max": 100, | |
"step": 1, | |
"default": 25 | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
} | |
] | |
}, | |
{ | |
"type": "text", | |
"name": "Input", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Input Field", | |
"info": "A text input field to render in the form." | |
}, | |
{ | |
"type": "text", | |
"id": "placeholder", | |
"label": "Placeholder", | |
"info": "Describes an expected value of the input" | |
}, | |
{ | |
"type": "text", | |
"id": "name", | |
"label": "Name", | |
"info": "Defines the name of the input field" | |
}, | |
{ | |
"type": "select", | |
"id": "autocomplete", | |
"label": "Autocomplete", | |
"info": "See [Values](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values) for reference.", | |
"default": "off", | |
"options": [ | |
{ | |
"value": "off", | |
"label": "Disabled" | |
}, | |
{ | |
"value": "on", | |
"label": "Enabled" | |
}, | |
{ | |
"value": "name", | |
"label": "Name" | |
}, | |
{ | |
"value": "given-name", | |
"label": "First Name" | |
}, | |
{ | |
"value": "additional-name", | |
"label": "Middle name" | |
}, | |
{ | |
"value": "family-name", | |
"label": "Last Name" | |
}, | |
{ | |
"value": "nickname", | |
"label": "Nickname or Handle" | |
}, | |
{ | |
"value": "organization", | |
"label": "Company or Oganization name" | |
}, | |
{ | |
"value": "street-address", | |
"label": "Street Address" | |
}, | |
{ | |
"value": "postal-code", | |
"label": "Postal Code" | |
} | |
] | |
}, | |
{ | |
"type": "checkbox", | |
"id": "required", | |
"label": "Required", | |
"default": false, | |
"info": "Whether or not the value is required" | |
}, | |
{ | |
"type": "header", | |
"content": "Validation", | |
"info": "Validation control for expected input text value" | |
}, | |
{ | |
"type": "range", | |
"id": "minlength", | |
"label": "Minimum Length", | |
"info": "The minimum number of characters required", | |
"min": 1, | |
"max": 75, | |
"step": 1, | |
"default": 1 | |
}, | |
{ | |
"type": "range", | |
"id": "maxlength", | |
"label": "Maximum Length", | |
"info": "The maximum number of characters required", | |
"min": 1, | |
"max": 75, | |
"step": 1, | |
"default": 50 | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
}, | |
{ | |
"type": "header", | |
"content": "Layout", | |
"info": "Control layout and alignment of the grid system" | |
}, | |
{ | |
"type": "range", | |
"id": "xl", | |
"label": "Desktop Columns", | |
"info": "The amount of columns in desktop", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "md", | |
"label": "Tablet Columns", | |
"info": "The amount of columns in tablet", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "xs", | |
"label": "Mobile Columns", | |
"info": "The amount of columns in mobile", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
} | |
] | |
}, | |
{ | |
"type": "email", | |
"name": "Email", | |
"limit": 1, | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "E-mail Address", | |
"info": "An E-mail address input field to render in the form." | |
}, | |
{ | |
"type": "text", | |
"id": "placeholder", | |
"label": "Placeholder", | |
"info": "The placeholder (label) value", | |
"default": "Email Address" | |
}, | |
{ | |
"type": "text", | |
"id": "name", | |
"label": "Name", | |
"info": "Defines the name of the input field", | |
"default": "email" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "autocomplete", | |
"label": "Autocomplete", | |
"default": false, | |
"info": "Whether or not to allow autocompletion" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "required", | |
"label": "Required", | |
"default": true, | |
"info": "Whether or not the value is required" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "multiple", | |
"label": "Multiple", | |
"default": false, | |
"info": "Whether to allow multiple emails be defined" | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
}, | |
{ | |
"type": "header", | |
"content": "Layout", | |
"info": "Control layout and alignment of the grid system" | |
}, | |
{ | |
"type": "range", | |
"id": "xl", | |
"label": "Desktop Columns", | |
"info": "The amount of columns in desktop", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "md", | |
"label": "Tablet Columns", | |
"info": "The amount of columns in tablet", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "xs", | |
"label": "Mobile Columns", | |
"info": "The amount of columns in mobile", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
} | |
] | |
}, | |
{ | |
"type": "tel", | |
"name": "Telephone", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Phone Number Field", | |
"info": "A telephone number input field to render in the form." | |
}, | |
{ | |
"type": "text", | |
"id": "name", | |
"label": "Name", | |
"info": "Defines the name of the telephone field", | |
"default": "telephone" | |
}, | |
{ | |
"type": "text", | |
"id": "placeholder", | |
"label": "Placeholder", | |
"info": "Describes an expected value of the input" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "autocomplete", | |
"label": "Autocomplete", | |
"default": false, | |
"info": "Whether or not to allow autocompletion" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "code", | |
"label": "Country Code", | |
"default": false, | |
"info": "Whether or not to provide a country calling code" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "require", | |
"label": "Require", | |
"default": false, | |
"info": "Whether or not the value is required" | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
}, | |
{ | |
"type": "header", | |
"content": "Layout", | |
"info": "Control layout and alignment of the grid system" | |
}, | |
{ | |
"type": "range", | |
"id": "xl", | |
"label": "Desktop Columns", | |
"info": "The amount of columns in desktop", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "md", | |
"label": "Tablet Columns", | |
"info": "The amount of columns in tablet", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "xs", | |
"label": "Mobile Columns", | |
"info": "The amount of columns in mobile", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
} | |
] | |
}, | |
{ | |
"type": "number", | |
"name": "Number", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Number Input", | |
"info": "Renders a Numeric input field" | |
}, | |
{ | |
"type": "text", | |
"id": "name", | |
"label": "Name", | |
"info": "Defines the name of the number field" | |
}, | |
{ | |
"type": "text", | |
"id": "placeholder", | |
"label": "Placeholder", | |
"info": "The placeholder to render" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "required", | |
"info": "The form requires this input to be filled", | |
"label": "Required" | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
} | |
] | |
}, | |
{ | |
"type": "switch", | |
"name": "Switch", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Switch Input", | |
"info": "A toggle switch (checkbox) input field" | |
}, | |
{ | |
"type": "text", | |
"id": "name", | |
"label": "Name", | |
"info": "Defines the name of the switch field" | |
}, | |
{ | |
"type": "text", | |
"id": "label", | |
"label": "Label", | |
"info": "The label which describes the switch" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "consent", | |
"info": "Whether or not the switch is a consent", | |
"label": "Consent (GDRP)", | |
"default": false | |
}, | |
{ | |
"type": "checkbox", | |
"id": "default", | |
"info": "The default state to use", | |
"label": "on/off", | |
"default": false | |
}, | |
{ | |
"type": "header", | |
"content": "Layout and Sizing", | |
"info": "Control layout, style and/or alignment of the switch" | |
}, | |
{ | |
"type": "select", | |
"id": "row", | |
"label": "Position", | |
"default": "jc-between", | |
"options": [ | |
{ | |
"value": "jc-between", | |
"label": "Between" | |
}, | |
{ | |
"value": "jc-start", | |
"label": "Left" | |
}, | |
{ | |
"value": "jc-center", | |
"label": "Center" | |
}, | |
{ | |
"value": "jc-end", | |
"label": "Right" | |
} | |
] | |
}, | |
{ | |
"type": "select", | |
"id": "size", | |
"label": "Sizing", | |
"default": "fm-switch", | |
"options": [ | |
{ | |
"value": "fm-switch-sm", | |
"label": "Between" | |
}, | |
{ | |
"value": "fm-switch", | |
"label": "Medium" | |
}, | |
{ | |
"value": "fm-switch-lg", | |
"label": "Large" | |
} | |
] | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
} | |
] | |
}, | |
{ | |
"type": "checkbox", | |
"name": "Checkbox", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Checkbox Input", | |
"info": "Renders a traditional Checkout input field" | |
}, | |
{ | |
"type": "text", | |
"id": "label", | |
"label": "Label", | |
"info": "The Label to render atop of the input (optional)" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "required", | |
"info": "The form requires this input to be filled", | |
"label": "Required" | |
}, | |
{ | |
"type": "checkbox", | |
"id": "validate", | |
"info": "Whether or not this input should be validated", | |
"label": "Validate" | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
} | |
] | |
}, | |
{ | |
"type": "dropdown", | |
"name": "Dropdown", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Option Dropdown", | |
"info": "A list of options to select from" | |
}, | |
{ | |
"type": "textarea", | |
"id": "items", | |
"label": "items", | |
"default": "Item 1\nItem 2\nItem 3\netc etc", | |
"info": "Separate each item to render on newline" | |
}, | |
{ | |
"type": "text", | |
"id": "name", | |
"label": "Name", | |
"info": "Defines the name of the dropdown." | |
}, | |
{ | |
"type": "text", | |
"id": "placeholder", | |
"label": "Placeholder", | |
"info": "Defines the placeholder of the dropdown." | |
}, | |
{ | |
"type": "checkbox", | |
"id": "required", | |
"label": "Required", | |
"default": true, | |
"info": "Whether or not to require a selection" | |
}, | |
{ | |
"type": "header", | |
"content": "Gutter", | |
"info": "Control gutter spacing" | |
}, | |
{ | |
"type": "range", | |
"id": "mt", | |
"label": "Top", | |
"info": "The margin to apply from top", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 0 | |
}, | |
{ | |
"type": "range", | |
"id": "mb", | |
"label": "Bottom", | |
"info": "The margin to apply from bottom", | |
"min": 0, | |
"max": 5, | |
"step": 1, | |
"default": 3 | |
}, | |
{ | |
"type": "header", | |
"content": "Layout", | |
"info": "Control layout and alignment of the grid system" | |
}, | |
{ | |
"type": "range", | |
"id": "xl", | |
"label": "Desktop Columns", | |
"info": "The amount of columns in desktop", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "md", | |
"label": "Tablet Columns", | |
"info": "The amount of columns in tablet", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
}, | |
{ | |
"type": "range", | |
"id": "xs", | |
"label": "Mobile Columns", | |
"info": "The amount of columns in mobile", | |
"min": 1, | |
"max": 12, | |
"step": 1, | |
"default": 12 | |
} | |
] | |
}, | |
{ | |
"type": "hidden", | |
"name": "Hidden", | |
"settings": [ | |
{ | |
"type": "header", | |
"content": "Hidden Field", | |
"info": "A hidden text field pre-populated with a value" | |
}, | |
{ | |
"type": "text", | |
"id": "value", | |
"label": "Value", | |
"info": "The value of the hidden input field" | |
}, | |
{ | |
"type": "text", | |
"id": "name", | |
"label": "Name", | |
"info": "Defines the name of the input field" | |
} | |
] | |
} | |
] | |
} | |
{% endschema %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-unused-vars */ | |
import { IForm, Types, ResponseType, IFormCache } from 'types/forms'; | |
import { Controller } from '@hotwired/stimulus'; | |
import * as customer from 'application/customer'; | |
import { keys, values } from 'utils/native'; | |
export class Form extends Controller { | |
/** | |
* Data Store | |
*/ | |
static store: Map<string, { [field: string]: IForm }> = new Map(); | |
/* -------------------------------------------- */ | |
/* STIMULUS METHODS */ | |
/* -------------------------------------------- */ | |
/** | |
* Stimulus Values | |
*/ | |
static values = { | |
id: String, | |
type: String, | |
errors: Array, | |
submit: String, | |
focus: String, | |
klaviyo: String, | |
success: String, | |
response: { | |
type: String, | |
default: 'append' | |
}, | |
valid: { | |
type: Boolean, | |
default: false | |
}, | |
session: { | |
type: Boolean, | |
default: true | |
} | |
}; | |
/** | |
* Stimulus Targets | |
*/ | |
static targets = [ | |
'form', | |
'field', | |
'dropdown', | |
'response', | |
'submit' | |
]; | |
/** | |
* Stimulus Initialize | |
*/ | |
initialize () { | |
if (!this.hasIdValue) { | |
this.idValue = Math.random().toString(36).slice(2); | |
} | |
if (!this.hasResponseTarget) { | |
console.error('Missing "data-form-target" element'); | |
} | |
if (this.responseValue === 'toggle' && !this.hasFormTarget) { | |
console.error('When using "toggle" response, a `data-form-target="form"` target is required'); | |
} | |
if (!this.hasTypeValue) { | |
console.error('Missing "data-form-type-value" value'); | |
} else { | |
if ( | |
this.typeValue === 'newsletter:advert' || | |
this.typeValue === 'newsletter:cart' || | |
this.typeValue === 'newsletter:footer' || | |
this.typeValue === 'notification:instock' || | |
this.typeValue === 'notification:restock' | |
) { | |
if (!this.hasKlaviyoValue) { | |
console.error('Missing "data-form-klaviyo-value" attribute'); | |
} | |
} else if ( | |
this.typeValue === 'shopify:contact' || | |
this.typeValue === 'api:discount' | |
) { | |
if (this.hasKlaviyoValue) { | |
console.error('Invalid "data-form-klaviyo-value" attribute provided'); | |
} | |
} | |
} | |
} | |
/** | |
* Stimulus Connect | |
*/ | |
connect (): void { | |
this.validValue = false; | |
this.submitValue = this.submitTarget.innerText; | |
this.submitTarget.addEventListener('click', this.onClickSubmit.bind(this)); | |
this.form = this.setFormState(); | |
this.setSubscribed(); | |
} | |
/** | |
* Stimulus Disconnect | |
*/ | |
disconnect () { | |
} | |
/* -------------------------------------------- */ | |
/* GETTERS / SETTERS */ | |
/* -------------------------------------------- */ | |
/** | |
* Input Element - Returns the currenct active `<input>` element | |
*/ | |
get field (): HTMLInputElement { return this.fieldTargets[this.state.index]; } | |
/** | |
* Form Element - Returns the currenct active `<form>` element | |
*/ | |
get form () { return Form.store.get(this.idValue); } | |
/** | |
* Form Element - Sets the current active `<form>` element | |
*/ | |
set form (state: { [field: string]: IForm }) { Form.store.set(this.idValue, state); } | |
/** | |
* State Reference - Returns the state of the form | |
*/ | |
get state () { return this.form[this.focusValue]; } | |
/** | |
* State Reference - Returns the state of the form | |
*/ | |
get dropdown () { return this.dropdownTargets[this.state.dropdown]; } | |
/** | |
* Set Subscribed state - used when we have known reference of a customer | |
* session. | |
*/ | |
setSubscribed () { | |
if (this.typeValue === 'newsletter:footer') { | |
if (this.isFormValid()) { | |
this.submitTarget.textContent = window.i18n.newsletter.subscribed; | |
this.submitTarget.setAttribute('disabled', 'true'); | |
} | |
} | |
} | |
/** | |
* Creates a workable store of the form which will maintain | |
* a persisted state model which will be used to validate and | |
* determine which actions should take place on each entry. | |
* | |
* _Each form stored in the static `store` Map._ | |
*/ | |
setFormState () { | |
/** The dropdown target index */ | |
let dindex: number = -1; | |
return this.fieldTargets.reduce((form, field, index) => { | |
const { name, type } = field; | |
if (!name) { | |
console.error('Missing "name" attribute on form element:', field); | |
return form; | |
} | |
if (!type) { | |
console.error('Missing "type" attribute on form element:', field); | |
return form; | |
} | |
let dropdown: number = -1; | |
let feedback: HTMLDivElement; | |
const hidden = type === 'hidden'; | |
if (!hidden) { | |
if (!field.hasAttribute('data-action')) { | |
console.error(`Missing "data-action" on form field ${name}`, field); | |
return form; | |
} | |
if (field.autofocus === true) this.focusValue = field.name; | |
if (field.getAttribute('data-action').indexOf('dropdown#select') > -1) { | |
if (name in form) return form; | |
dropdown = dindex = dindex + 1; | |
feedback = this.setValidators(this.dropdownTargets[dindex]); | |
} else { | |
if (name in form) { | |
console.error(`The ${name} is not unique on form element:`, field); | |
return form; | |
} else { | |
feedback = this.setValidators(field.parentElement); | |
} | |
} | |
} | |
const required = field.required; | |
form[name] = { | |
name, | |
index, | |
required, | |
feedback, | |
dropdown, | |
status: 1, | |
type, | |
interacted: false, | |
valid: required === false, | |
message: '', | |
value: undefined | |
}; | |
if (hidden) { | |
form[name].value = field.value; | |
} | |
if (field.hasAttribute('data-form-session')) { | |
const value = field.getAttribute('data-form-session'); | |
if (value in customer.session && typeof customer.session[value] === 'string') { | |
field.value = customer.session[value]; | |
form[name].value = field.value; | |
form[name].valid = true; | |
} | |
} | |
return form; | |
}, <IFormCache>{}); | |
} | |
/** | |
* Generates feedback nodes in each field wrapped element | |
* of _typically_ this will be the parent grid column. The | |
* feedback nodes are appended and can be queried using | |
* `lastElementChild` or alternatively via the `feedback` | |
* property maintained in the form model. | |
* | |
* This can likely be re-thought for input elements which have | |
* no validation. | |
*/ | |
setValidators (field: HTMLElement) { | |
const feedback = document.createElement('div'); | |
// append validation feedback message to the `<label>` node | |
if (!field.contains(feedback)) { | |
feedback.classList.add('feedback'); | |
field.appendChild(feedback); | |
} | |
return feedback; | |
} | |
/** | |
* Replaces the response with form values which reference | |
* fields using template literals via the Section theme editor. | |
*/ | |
setResponse (value?: string) { | |
if (this.hasSuccessValue && typeof value === 'undefined') { | |
const regex = new RegExp('\\$\\{\\b(' + keys(this.form).join('|') + ')\\b\\}', 'g'); | |
this.successValue = this.successValue.replace(regex, (value) => { | |
return this.form[value.slice(2, -1)].value as string; | |
}); | |
} else { | |
this.successValue = value; | |
} | |
} | |
/** | |
* Set and Reset the feedback nodes innerText. This | |
* gives programmatic control and assigns the validation | |
* response to field. | |
*/ | |
setFeedback <T = HTMLElement> (feedback: T, response: string) { | |
if (feedback instanceof HTMLElement) { | |
if (response === null || response === undefined) { | |
feedback.innerText = ''; | |
} else { | |
feedback.innerText = response; | |
} | |
} | |
} | |
/** | |
* Generates feedback nodes in each field wrapped element | |
* of _typically_ this will be the parent grid column. The | |
* feedback nodes are appended and can be queried using | |
* `lastElementChild` or alternatively via the `feedback` | |
* property maintained in the form model. | |
*/ | |
setHidden (field: HTMLElement) { | |
const hidden = document.createElement('input'); | |
// append validation feedback message to the `<label>` node | |
if (!field.contains(hidden)) { | |
hidden.classList.add('d-none'); | |
field.appendChild(hidden); | |
} | |
return hidden; | |
} | |
/* -------------------------------------------- */ | |
/* SETUP */ | |
/* -------------------------------------------- */ | |
/** | |
* This logic will validate the form upon the submit button | |
* being clicked. If `validValue` is `true` then validation | |
* will be skipped and form is passed to `onSubmit`. | |
*/ | |
onClickSubmit (event: SubmitEvent) { | |
if (this.typeValue === 'shopify:contact') { | |
if (this.validValue) { | |
return; | |
} | |
} else { | |
event.preventDefault(); | |
} | |
if (this.validValue) return this.onSubmit(); | |
this.submitTarget.setAttribute('disabled', 'true'); | |
if (this.validValue === false) { | |
for (const field of values(this.form)) { | |
if (field.valid === false && field.required) { | |
if (field.dropdown > -1) { | |
if (!this.dropdownTargets[field.dropdown].classList.contains('is-invalid')) { | |
this.dropdownTargets[field.dropdown].classList.add('is-invalid'); | |
} | |
} else { | |
if (!this.fieldTargets[field.index].classList.contains('is-invalid')) { | |
this.fieldTargets[field.index].classList.add('is-invalid'); | |
} | |
} | |
} | |
} | |
} | |
} | |
private responseType (value?: string) { | |
this.setResponse(value); | |
this.submitTarget.classList.remove('is-loading'); | |
this.responseTarget.innerHTML = this.successValue; | |
if (this.responseValue === 'toggle') { | |
this.submitTarget.classList.add('d-none'); | |
this.formTarget.classList.add('d-none'); | |
this.responseTarget.parentElement.classList.remove('d-none'); | |
} | |
} | |
/** | |
* Fires upon an input change event. `onChange()` is used | |
* on the following input fields: | |
* | |
* - checkbox | |
* - radio | |
* - select | |
*/ | |
public onSubmit () { | |
switch (this.typeValue) { | |
case 'newsletter:advert': | |
case 'newsletter:cart': | |
case 'newsletter:footer': | |
case 'notification:instock': | |
case 'notification:restock': | |
return this.klaviyo(); | |
case 'shopify:contact': | |
return this.contact(); | |
case 'api:discount': | |
return this.discount(); | |
} | |
}; | |
/** | |
* Checks all form fields are valid. Enables / Disables | |
* the submit button state. | |
*/ | |
private isFormValid () { | |
const valid = values(this.form).every(({ valid }) => valid === true); | |
if (valid) { | |
this.submitTarget.removeAttribute('disabled'); | |
this.validValue = true; | |
} else { | |
if (!this.submitTarget.hasAttribute('disabled')) { | |
this.submitTarget.setAttribute('disabled', 'true'); | |
this.validValue = false; | |
} | |
} | |
console.log(this.form); | |
return this.validValue; | |
} | |
/** | |
* Valid class - Toggles the input valid and invalid class | |
* name on the the field element. Also sets the `this.state.valid` | |
* and the hides the feedback node. Passes to `this.isFormValid()` | |
* as the final callback. | |
*/ | |
private valid () { | |
if (!this.state.valid) this.state.valid = true; | |
const field = this.state.dropdown > -1 | |
? this.dropdown | |
: this.field; | |
if (field.classList.contains('is-invalid')) { | |
field.classList.remove('is-invalid'); | |
} | |
if (!field.classList.contains('is-valid')) { | |
field.classList.add('is-valid'); | |
} | |
if (this.state.feedback.classList.contains('is-invalid')) { | |
this.state.feedback.classList.remove('is-invalid'); | |
} | |
if (!this.state.feedback.classList.contains('is-valid')) { | |
this.state.feedback.classList.add('is-valid'); | |
} | |
return this.isFormValid(); | |
} | |
/** | |
* Invalid class - Toggles the input invalid and valid class | |
* name on the the field element. Also sets the `this.state.valid` | |
* and the hides the feedback node. Passes to `this.isFormValid()` | |
* as the final callback. | |
*/ | |
private invalid () { | |
if (this.state.valid === true) this.state.valid = false; | |
const field = this.state.dropdown > -1 | |
? this.dropdown | |
: this.field; | |
if (field.classList.contains('is-valid')) { | |
field.classList.remove('is-valid'); | |
} | |
if (!field.classList.contains('is-invalid')) { | |
field.classList.add('is-invalid'); | |
} | |
if (this.state.feedback.classList.contains('is-valid')) { | |
this.state.feedback.classList.remove('is-valid'); | |
} | |
if (!this.state.feedback.classList.contains('is-invalid')) { | |
this.state.feedback.classList.add('is-invalid'); | |
} | |
return this.isFormValid(); | |
} | |
/** | |
* Checks all form field inputs are valid. Enables / Disables | |
* the submit button state. | |
* | |
* @this {IForm} | |
* @param {HTMLInputElement} target | |
*/ | |
private isInputValid (target: HTMLInputElement) { | |
if (target.checkValidity()) { | |
this.valid(); | |
} else { | |
this.invalid(); | |
} | |
return this.isFormValid(); | |
} | |
/* -------------------------------------------- */ | |
/* ACTIONS */ | |
/* -------------------------------------------- */ | |
/** | |
* Fires upon input entry of text form elements and | |
* will dispatch to appropriate function in this class. | |
* for handling and validation. | |
*/ | |
onInput ({ target }: Event) { | |
if (target instanceof HTMLLabelElement) { | |
this.focusValue = target.getAttribute('for'); | |
this.state.valid = true; | |
this.state.value = target.innerText; | |
this.field.value = this.state.value; | |
if (!this.state.interacted) this.state.interacted = true; | |
return this.isFormValid(); | |
} | |
if (target instanceof HTMLInputElement) { | |
const type = target.getAttribute('type'); | |
// show the validation response | |
this.focusValue = target.name; | |
this.state.value = type === 'checkbox' ? target.checked : target.value; | |
if (this.typeValue === 'newsletter:footer') { | |
if (this.submitTarget.innerText !== this.submitValue) { | |
this.submitTarget.innerText = this.submitValue; | |
} | |
} | |
if (this.state.status !== 1) this.responseTarget.innerText = ''; | |
if (!this.state.interacted) this.state.interacted = true; | |
if (type in this) return this[type](target); | |
} | |
return this.isFormValid(); | |
} | |
/* -------------------------------------------- */ | |
/* FORM ELEMENTS */ | |
/* -------------------------------------------- */ | |
/** | |
* Text Input | |
* | |
* Validate text input type | |
*/ | |
text (target: HTMLInputElement) { | |
if (this.state.interacted && target.minLength >= 0) { | |
if (target.minLength > target.value.length) { | |
return this.invalid(); | |
} else if (target.value.length > target.maxLength) { | |
return this.invalid(); | |
} | |
} | |
this.state.feedback.innerText = target.validationMessage; | |
return target.checkValidity() | |
? this.valid() | |
: this.invalid(); | |
} | |
/** | |
* Validates checkbox input fields. When a checkbox | |
* field contains a `required` attribute the field | |
* must be checked or form submission will be disabled. | |
*/ | |
checkbox (target: HTMLInputElement) { | |
if (!this.state.required) { | |
if (target.required) this.state.valid = target.checked; | |
} else { | |
if (target.hasAttribute('checked')) { | |
target.removeAttribute('checked'); | |
target.checked = false; | |
this.state.valid = false; | |
} else { | |
target.setAttribute('checked', ''); | |
target.checked = true; | |
this.state.valid = true; | |
} | |
} | |
if (this.field.classList.contains('is-invalid')) { | |
this.field.classList.remove('is-invalid'); | |
} | |
return this.isFormValid(); | |
} | |
/** | |
* Validate textarea | |
*/ | |
textarea (target: HTMLInputElement) { | |
return this.isInputValid(target); | |
} | |
/** | |
* Validate email type text input form field. Applies an | |
* extra layer of validation and/or helpers upon user input. | |
*/ | |
email (target: HTMLInputElement) { | |
if (this.state.interacted && target.value.length === 0) return this.invalid(); | |
if (target.value.endsWith('@brixtoltextiles.com') || target.value.endsWith('@sunday-seven.com')) { | |
this.state.feedback.innerText = window.i18n.newsletter.error_team; | |
return this.invalid(); | |
} | |
if (target.validationMessage.length === 0) { | |
if (!/\w+@[0-9a-zA-Z_]+?\.[a-zA-Z]{2,}$/.test(target.value)) { | |
this.state.feedback.innerText = 'Invalid email, please ensure correct email is provided'; | |
return this.invalid(); | |
} else { | |
this.state.feedback.innerText = ''; | |
return target.checkValidity() ? this.valid() : this.invalid(); | |
} | |
} else { | |
this.state.feedback.innerText = target.validationMessage; | |
return target.checkValidity() ? this.valid() : this.invalid(); | |
} | |
} | |
password () { | |
} | |
radio (target: HTMLInputElement) { | |
const field = this.state.dropdown > -1 ? this.dropdown : this.field; | |
if (target.checked) { | |
this.state.value = target.value; | |
this.state.valid = true; | |
} else { | |
this.state.valid = false; | |
} | |
if (field.classList.contains('is-invalid')) { | |
field.classList.remove('is-invalid'); | |
} | |
if (!field.classList.contains('is-valid')) { | |
field.classList.add('is-valid'); | |
} | |
return this.isFormValid(); | |
} | |
/** | |
* Validates Phone Number | |
*/ | |
tel (target: HTMLInputElement) { | |
if (!target.required) this.state.valid = true; | |
return this.isInputValid(target); | |
} | |
file () { | |
} | |
/* -------------------------------------------- */ | |
/* FORM SUMBMISSION */ | |
/* -------------------------------------------- */ | |
async discount () {} | |
async contact () {} | |
hasSubscribed = () => { | |
this.state.feedback.innerText = ''; | |
this.fieldTargets[this.state.index].classList.remove('is-valid'); | |
this.submitTarget.textContent = window.i18n.newsletter.subscribed; | |
this.submitTarget.setAttribute('disabled', 'true'); | |
}; | |
/** | |
* Submits the form inputs to Klaviyo. | |
*/ | |
async klaviyo () { | |
this.submitTarget.classList.add('is-loading'); | |
if (this.typeValue === 'newsletter:footer') { | |
this.field.classList.remove('is-valid'); | |
} | |
const fields = []; | |
const body = new URLSearchParams(); | |
for (const field in this.form) { | |
if (field === 'first_name' || field === 'last_name' || field === 'phone_number') { | |
body.append(`$${field}`, `${this.form[field].value}`); | |
} else { | |
fields.push(field); | |
body.append(field, `${this.form[field].value}`); | |
} | |
} | |
body.append('g', this.klaviyoValue); | |
body.append('$fields', fields.join(',')); | |
try { | |
const response = await fetch(window.shop.api.klaviyo, { | |
body, | |
method: 'POST', | |
headers: { | |
'Access-Control-Allow-Headers': '*', | |
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' | |
} | |
}); | |
const { data, errors } = await response.json(); | |
if (data.is_subscribed === false && typeof data.email === 'string') { | |
this.responseType(); | |
this.state.status = 2; | |
if (this.typeValue === 'newsletter:footer') setTimeout(this.hasSubscribed, 60000); | |
} else if (data.is_subscribed === true) { // Exists | |
this.responseType(window.i18n.newsletter.error_exists); | |
this.state.status = 3; | |
if (this.typeValue === 'newsletter:footer') this.hasSubscribed(); | |
} else if (errors.length > 0) { | |
this.responseType(errors.join('<br>')); | |
this.state.status = 4; | |
} | |
} catch (e) { | |
console.error(e); | |
this.responseType(window.i18n.newsletter.error_404); | |
this.state.status = 4; | |
} | |
} | |
/* -------------------------------------------- */ | |
/* VALUE TYPES */ | |
/* -------------------------------------------- */ | |
/** | |
* Stimulus: The generated form identifer value | |
*/ | |
idValue: string; | |
/** | |
* Stimulus: Whether or not the form passed an id value | |
*/ | |
hasIdValue: boolean; | |
/** | |
* Stimulus: The type of form. | |
*/ | |
typeValue: Types; | |
/** | |
* Stimulus: Whether or not the form has a type value (required) | |
*/ | |
hasTypeValue: boolean; | |
/** | |
* The success response value | |
*/ | |
successValue: string; | |
/** | |
* Whether or not the form has a response value, when `false` then | |
* the `i18n` locales will be used. | |
*/ | |
hasSuccessValue: boolean; | |
/** | |
* How the response value should be handled. Defaults to `append` | |
*/ | |
responseValue: ResponseType; | |
/** | |
* Whether or not an `responseTypeValue` was provided. | |
*/ | |
hasResponseValue: boolean; | |
/** | |
* Stimulus: The Klaviyo List ID to target | |
*/ | |
klaviyoValue: string; | |
/** | |
* Stimulus: Whether or not the form has a klaviyo list id | |
*/ | |
hasKlaviyoValue: boolean; | |
/** | |
* The submit button label | |
*/ | |
submitValue: string; | |
/** | |
* Stimulus: The input to focus, this identifies the current field | |
*/ | |
focusValue: string; | |
/** | |
* Stimulus: Attach known customer session values to the form submission. | |
*/ | |
sessionValue: boolean; | |
/** | |
* Stimulus: Whether session was asserted, will default to `true` if not passed. | |
*/ | |
hasSessionValue: boolean; | |
/* -------------------------------------------- */ | |
/* INTERNAL VALUE TYPES */ | |
/* -------------------------------------------- */ | |
/** | |
* Stimulus (iternal): Whether or not the form is valid | |
*/ | |
validValue: boolean; | |
/* -------------------------------------------- */ | |
/* TARGET TYPES */ | |
/* -------------------------------------------- */ | |
/** | |
* Stimulus: The response element to append text within | |
*/ | |
responseTarget: HTMLElement; | |
/** | |
* Stimulus: Whether or not response targer exists | |
*/ | |
hasResponseTarget: boolean; | |
/** | |
* Stimulus: The form element | |
*/ | |
formTarget: HTMLFormElement; | |
/** | |
* Stimulus: Whether or not te form target exists | |
*/ | |
hasFormTarget: boolean; | |
/** | |
* Stimulus: Dropdown targets within form | |
*/ | |
dropdownTargets: HTMLElement[]; | |
/** | |
* Stimulus: Whether or not dropdown target/s exist | |
*/ | |
hasDropdownTarget: boolean; | |
/** | |
* Stimulus: Field targets | |
*/ | |
fieldTargets: HTMLInputElement[]; | |
/** | |
* Stimulus: Submit button Target | |
*/ | |
submitTarget: HTMLElement; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment