@the-oz/app-swatches
v3.1.1
Published
Custom multidimensional swatches for The Oz
Downloads
7
Keywords
Readme
The Oz - App Swatches
Custom multidimensional swatches for The Oz
Documentation
Here is the documentation for adding custom multidimensional swatches on a client's website.
0. Create metafields definitions
In Shopify BO, go to Settings > Metafields > Products > Add definition.
There are two possible types of swatches: text or color.
For a text swatch, create the following metafields definitions (i stands for the number of the swatch):
Swatch text
- Name: Swatch i - text
- Namespace and key:
swatch_i.text
- Description: Product swatch text
- Type: Single-line text
- Maximum length: 25
Swatch related products
- Name: Swatch i - related products
- Namespace and key:
swatch_i.products
- Description: Swatch related products
- Type: Product (List)
Lastly, make sure the Definition pinned option (the orange drawing pin) is selected for all metafields, so that they appear directly on product pages.
End result should look like this:
For a color swatch, create the following metafields definitions (i stands for the number of the swatch):
Swatch image
- Name: Swatch i - image
- Namespace and key:
swatch_i.image
- Description: Product swatch image
- Type: url
Swatch color
- Name: Swatch i - color
- Namespace and key:
swatch_i.color
- Description: Product swatch color (won't be displayed if a "Swatch Image" is selected)
- Type: color
Swatch color
- Name: Swatch i - base64 image
- Namespace and key:
swatch_i.base64_image
- Description: Product swatch color (won't be displayed if a "Swatch Image" or a "Swatch Color" is selected)
- Type: Single-line text
Swatch name
- Name: Swatch i - label
- Namespace and key:
swatch_i.label
- Description: (optional) Product swatch label
- Type: Single-line text
- Maximum length: 25
Swatch related products
- Name: Swatch i - related products
- Namespace and key:
swatch_i.products
- Description: Swatch related products
- Type: Product (List)
Lastly, make sure the Definition pinned option (the orange drawing pin) is selected for all metafields, so that they appear directly on product pages.
End result should look like this:
1. Include the custom swatches script to the project
- Copy and paste the snippet file
app_swatches.liquid
(from the doc/shopify folder) into thesnippets
folder of the destination site. - Render the snippet
app_swatches
in the<head>
oftheme.liquid
:
<head>
<!-- ... -->
<script src="{{ 'theme.js' | asset_url }}" defer></script>
<!-- It's a good idea to render the snippet after `theme.js` -->
{% render 'app_swatches' %}
<!-- ... -->
</head>
2. Add CSS file
Copy and paste the CSS file swatches.scss into the destination site.
3. Add Swatch-related settings in settings_schema.json
In settings_schema.json
, add the following settings.
4. Add custom swatches on the product page
- Add oz-related-products-swatches.liquid to your
snippets
folder - Locate the liquid file where your product options are currently displayed (usually
product-form.liquid
orproduct-template.liquid
) - Add this variable at the top of the file
{% assign custom_related_product_swatches_enabled = settings.enable_custom_related_products_swatches %}
- Add this line where you want your swatches to be displayed:
{% if custom_related_product_swatches_enabled %}{% render 'oz-related-products-swatches', current_product: product %}{% endif %}
- (optional) Use the
custom_related_product_swatches_enabled
variable to hide natively displayed color options (necessary if color options are present in the product BO)
Example :
{% assign custom_related_product_swatches_enabled = settings.enable_custom_related_products_swatches %} // STEP 1
{% assign color_option_labels = 'color,colour,couleur,colore,farbe,색,色,färg,farve' %} // necessary if color options are present in BO
[...]
<div class="ProductForm__Variants">
{% if custom_related_product_swatches_enabled %}{% render 'oz-related-products-swatches', current_product: product %}{% endif %} // STEP 2
{% for option in product.options_with_values %}
{% assign downcase_option = option.name | downcase %}
<div class="ProductForm__Option">
{% if color_option_labels contains downcase_option and custom_related_product_swatches_enabled == false %} // STEP 3
<ul class="ColorSwatchList HorizontalList HorizontalList--spacingTight">
[...]
</ul>
{% endif %}
</div>
{% endfor %}
</div>
5. Update some test products in BO
We're now ready to start playing around with swatches on the product pages in the BO! Head to some product pages in the BO and fill-in swatches-related metafields to start seeing it in action.
For Swatch i - related products metafields, please make sure to add the product from the current BO product page.
Example :
6. Add custom swatches on collection, search pages and cross-sells
Make sure to also follow the steps in 8. Integration with USF for the collection and search pages if USF is installed and active on the theme.
6.1. Create a product search template
- In
templates
, add search.oz-related-product-block-html.liquid. - Locate the snippet where your product block on the collection page is defined (usually
product-thumbnail.liquid
,product-block.liquid
orproduct-grid-item.liquid
) - In search.oz-related-product-block-html.liquid, replace
product-thumbnail
with the right product block snippet name.
6.2. Add HTML elements to product block
- At the top of your product block snippet file (
product-thumbnail.liquid
or else), add this variable:
{% assign custom_related_product_swatches_enabled = settings.enable_custom_related_products_swatches %}
- Add the
cs-product-container
class to the very top HTML container of the product block. - Add this line where you want your swatches to be displayed:
{% if custom_related_product_swatches_enabled %}{% render 'oz-related-products-swatches', current_product: product %}{% endif %}
6.3. Make relevant parameters accessible
With this app, the product will be loaded through an independent HTTP request from this alternative search template so outside of the context of any section. This means that variables passed to the product block will need to be made available differently.
The only parameters we already have at this stage are the product
and the collection
.
{% include 'product-thumbnail', product: product, collection: collection %}
- In the hierarchy just above that product block file, locate where your snippet is called (usually
collection.liquid
andsearch.liquid
for eg.) and whether it comes with parameters. Example:
{% include 'product-thumbnail', product: product, collection: collection, sidebar: sidebar, display_secondary_image: display_secondary_image %}
- Parameters that come from general settings (ex:
settings.my_variable
) are accessible in every liquid file, so we can simply pass their values along in our alternative search template.
Example for display_secondary_image
:
{% include 'product-thumbnail', display_secondary_image: settings.collection_secondary_image, product: product %}
- Parameters that are calculated in previous sections or snippets won't be accessible, and the same goes for parameters that come from section settings (ex:
section.settings.my_variable
).
For those, in the product block snippet (product-thumbnail.liquid
for eg.), in the same HTML element we added the cs-product-container
class, chain those remaining parameters in an attribute called data-cs-params
. Parameters should be separated by a |
. The first one is mandatory and should always be the collection.handle
.
Example:
<div class="cs-product-container thumbnail product-{{ product.id }}"
data-cs-params="{{collection.handle}}|{{products_per_row}}|{{sidebar}}">
- Retrieve those parameters
In search.related-product-block-html
, these parameters will be available from the URL, in the query_params
variable.
They will all be in a String format.
Unchain them and pass them onto the product snippet. The first one will always already be used for the product handle, used to retrieve the product
.
Note 1: Booleans should be interpreted as below as they arrive in this template as Strings.
Note 2: The order in which params are listed in data-cs-params
matter.
Example :
{%- layout none -%}
{% assign query_params = search.terms | split: '|' %}
{% assign product_handle = query_params[0] | strip %} // PARAMETER 0 is the product handle
{% assign collection_handle = query_params[1] | strip %}
{% assign product = all_products[product_handle] %}
{% assign collection = collections[collection_handle] %}
{% assign products_per_row = query_params[2] | plus: 0 %} // CUSTOM PARAMETER 1 (the 'plus: 0' transforms the String into and Integer)
{% assign sidebar = false %} // CUSTOM PARAMETER 2 (Booleans arrive as String so also need to be converted)
{% if query_params[3] == 'true' %}
{% assign sidebar = true %}
{% endif %}
{% comment %}
PARAMETERS (example from PDS)
<products_per_row> - comes from section.settings
<sidebar> - comes from collection-template and is calculated in liquid
<display_secondary_image> - comes from section.settings
{% endcomment %}
{%- capture swatch_block_html -%}
{% include 'product-thumbnail', product: product, collection: collection, products_per_row: products_per_row, sidebar: sidebar, display_secondary_image: settings.collection_secondary_image %}
{%- endcapture -%}
{
"product_block": {{ swatch_block_html | json }}
}
7. Input the number of swatches to display in the settings of the theme
8. Add custom swatches on collection page - Integration with USF
8.1. Make relevant product data available to USF
- In
templates
, add search.oz-usf-related-products-json.liquid. - In
snippets
, add search-oz-usf-related-product-json.liquid.
8.2. Insert Custom Components
In theme.liquid
, add a usesCustomRelatedProductsSwatches
property in window.theme.settings
.
<script>
window.theme.settings = {
usesCustomRelatedProductsSwatches: {{ settings.enable_custom_related_products_swatches | json }}
}
</script>
8.3. Define swatch custom components
Important: make sure npm run dev
is not running for these (-:
In Apps > Ultimate Search > Customization, select the correct theme and insert the custom components between the opening and closing tags of the function created in here: usf.event.add
.
(usf.event.add('init', function () { PLACE THEM IN HERE });
).
8.4. Call custom swatch component with relevant flags
In searchResultsGridViewItem
and searchResultsListViewItem
, add the following element where the swatches need to be.
<oz-custom-related-products-swatches :product="product" v-if="window.theme.settings.usesCustomRelatedProductsSwatches" />
Several flags most likely need be added to that <oz-custom-related-products-swatches ... />
line to ensure the custom component works seamlessly with available features in the theme.
Flag list:
| Type | Default value | Name | Description |
| :--: | :-----------: | ------------------------------------- | ---------------------------------------------------------------------- |
| prop | false
| canHaveVideo
| If product can have a video as media |
| prop | false
| showAvailableSizes
| Shows available sizes but doesn't have quick add to cart functionality |
| prop | false
| hasInternalSlidehow
| If product item on collection page has slideshow |
| prop | false
| hasSmartWishlist
| If the Smart Wishlist app is installed and used on collection page |
| prop | false
| hasQuickShop
| If product has quick shop |
| prop | false
| hasQuickView
| If product has quick view |
Examples:
<!-- Basic implementation -->
<oz-custom-product-swatches :product="product" />
<!-- Product that has videos in product media, shows available sizes but does not have quick add to Cart -->
<oz-custom-product-swatches :product="product" v-if="window.theme.settings.usesCustomRelatedProductsSwatches" canHaveVideo showAvailableSizes />
8.5. Add relevant classes in USF components/templates
In
searchResultsGridViewItem
:Add the
oz-cs-product-title
class to the HTML element that contains the product title.Add the
oz-cs-product-link
class to all<a>
elements that contain a link to the product page.Add the
oz-cs-product-price-container
class to to the HTML element that contains the product price (goes next tovp-original-prices
if VPs are installed).In the
updateAvailabilityCurrentItem
function, add the right value for 'wording'. It needs to point to the translation of "sold out".(optional) If product doesn't have internal slideshow but has first and second image (on hover):
- Add the
oz-cs-featured-image
class to the<img>
element that contains the featured image. - Add the
oz-cs-hover-image
class to the<img>
element that contains the hover image.
- Add the
(optional) If product shows available sizes left:
- Add the
oz-cs-available-sizes-container
class to to the HTML element that contains the available sizes. -> If HTML element doesn't exist yet, add the oz-product-sizes component.
- Add the
(optional) If product has internal slideshow:
- Add the
oz-cs-slideshow-images
class to to the HTML element that contains the slideshow images. - Add the
oz-cs-slideshow-current-image
class to to the HTML element that contains the first slideshow image.
- Add the
If product can have videos displayed in collection page
- Add the
oz-cs-media-container
class to to the HTML element that contains the product video. - Add the
oz-cs-mmedia-container
class to the HTML element that contains the product images or slideshow.
- Add the
(optional) If product has a quick shop feature:
- Add the
oz-cs-quickshop-container
class to to the HTML element that contains all quickshop-related elements, (which should be situated in the quick-shop component).
SUMMARY
| Type | Condition | Name | To be placed in |
| :--: | :-----------: | ------------------------------------- | ---------------------------------------------------------------------- |
| class | Mandatory | oz-cs-product-title
| the product title
| class | Mandatory | oz-cs-product-link
| all <a>
elements that contain a link to the product page |
| class | Mandatory | oz-cs-product-price-container
| the product price (min. 2x if VPs) |
| class | no slideshow | oz-cs-featured-image
| the <img>
that contains the main image (when there's no slideshow) |
| class | no slideshow | oz-cs-hover-image
| the <img>
that contains the second image (when there's no slideshow) |
| class | shows available sizes + no quickAddToCart | oz-cs-available-sizes-container
| the wrapper of available sizes |
| class | has slideshow | oz-cs-slideshow-images
| the wrapper of slideshow images |
| class | has slideshow | oz-cs-slideshow-current-image
| the wrapper of the first slideshow image |
| class | has quickshop | oz-cs-quickshop-container
| the wrapper of the quickshop |
| class | can have video | oz-cs-media-container
| the wrapper of the video |
EXAMPLES
- Example for
oz-cs-media-container
:
<div class="ProductItem__ImageWrapper" :class="{'ProductItem__ImageWrapper--soldOut': isSoldOut}">
<a class="oz-cs-product-link oz-cs-media-container" v-if="__index == lastVideoProduct" :href="productUrl"> // HERE 1
<video v-if="(_video_url = product.metafields.find(field => field.key == 'catalog_video_bg'))" class="ProductItem__Video lazy" preload="none" autoplay muted loop playsinline>
<source :data-src="_video_url.value" type="video/mp4">
</video>
<span class="Video__Loader"></span>
</a>
<a :href="productUrl" v-else class="oz-cs-product-link oz-cs-media-container"> // HERE 2
<div class="Carousell__Cell is-selected" :key="product.images[0].url" >
<img class="ProductItem__Image Image--lazyLoad Image--fadeIn oz-cs-slideshow-current-image" :data-widths="'[' + product.images[0].width + ']'" data-sizes="auto" :data-src="_usfGetScaledImageUrl(window.setImgSize(product.images[0].url, 400))">
<span class="Image__Loader"></span>
</div>
</a>
</div>
- Example for
oz-cs-quickshop-container
:
<div class="oz-cs-quickshop-wrapper"> // HERE
<div class="ProductItem__Quickshop" :class="{'ProductItem__Quickshop--active' : isActive }" v-if="sizeVariants.length > 0">
<button class="ProductItem__QuickshopClose" @click="activateQuickshop">✕</button>
<p class="ProductItem__QuickshopTitle" v-html="window._translations.quickshop.title"></p>
<div class="ProductItem__QuickshopSizesList">
<span class="ProductItem__QuickShopSizeTitle" v-html="window._translations.quickshop.size"></span>
<div class='ProductItem__QuickShopSizesListWrapper'>
<div v-for="variant in sizeVariants" :key="variant.id +'-'+ variant.size" >
<form v-if="variant.available > 0" :data-form-variant-id="variant.id" method="POST" enctype="multipart/form-data" :action="usf.platform.addToCartUrl">
<input type="hidden" name="form_type" value="product">
<input type="hidden" name="utf8" value="✓">
<input type="hidden" name="quantity" value="1">
<input type="hidden" name="id" :value="variant.id">
<button class="ProductItem__QuickshopSize" type="submit" name="add" @click="previewPopupSubmit" v-html="variant.size"></button>
</form>
</div>
</div>
</div>
</div>
<button v-if="sizeVariants" class="ProductItem__QuickshopButton Button Button--secondary" @click="activateQuickshop" v-html="window._translations.quickshop.title"></button>
</div>
8.6. Add proper HTML for price
In updateProductPrice()
in usf-custom.js
, update the value of template
to reflect the current site's look.
Note 1: don't forget to handle the vp-prices
!
Note 2: don't forget to handle any custom product tags
Note 3: don't forget to handle the case where the product price can vary (product.price_varies
)
8.7. Add proper HTML for price
In initialiseInternalSlideshow()
in usf-custom.js
, adapt the value of template
to reflect the current site's look.
8.8. Handle available sizes
If product item displays available sizes, when a swatch is clicked, an oz-swatch-swapped
event is emitted. This need to be listened to in the USF component that manages available sizes.
In the USF-made product-sizes
component, handle the oz-swatch-swapped
event in the mounted()
function.
Example:
RVue.component('product-sizes', {
// (props, data)
mounted() {
this.$parent.$on('oz-swatch-swapped', data => {
if(data.sizes.length > 0) {
this.sizes = data.sizes;
this.hasSizes = true;
} else {
this.hasSizes = false;
}
});
}
// (methods, template)
});
Full component code example can be found here.
9. (optional) Refresh swatches logic on collection page when needed
In some cases (filtering, sorting, infinite scrolling,...) where products are dynamically changing on the page, the swatch logic needs to be re-rendered.
Calling document.dispatchEvent(new Event('cs-rerender'));
will go over all the non-initialised swatches.
10. (optional) Refresh uninitialised elements after swatches has been refreshed
Some elements on the collection and search pages or on the cross-selles need to be manually initialised when the product block content is loaded (ex: the heart widget in Smart Wishlist).
For those cases, we can listen to the cs-rerendered
event, which is dispatched when the HTML of a product block has been replaced with the new product's content.
Example:
export function onSwatchClickedReinitSmarwishlist() {
document.addEventListener('cs-rerendered', reloadSmartWishlist);
}
11. Make sure to remove any legacy swatch code!
Or to make old version administrable, according to site's needs.
Notes
- It is possible to deactivate the entire module from the settings of the theme.
- Give names to swatches
- Choose which swatches to display in product page
- Choose which swatches to display in collection page and cross-sell sections
- If something is not working properly, make sure jquery is available (note: soon-to-be-deprecated-as-all-of-jquery-will-be-removed-very-very-soon :-) )
import jquery from 'jquery';
window.$ = window.jQuery = jquery;