npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@the-oz/app-swatches

v3.1.1

Published

Custom multidimensional swatches for The Oz

Downloads

28

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):

  1. Swatch text

    • Name: Swatch i - text
    • Namespace and key: swatch_i.text
    • Description: Product swatch text
    • Type: Single-line text
    • Maximum length: 25
  2. 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:

Swatch text metafields

For a color swatch, create the following metafields definitions (i stands for the number of the swatch):

  1. Swatch image

    • Name: Swatch i - image
    • Namespace and key: swatch_i.image
    • Description: Product swatch image
    • Type: url
  2. 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
  3. 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
  4. Swatch name

    • Name: Swatch i - label
    • Namespace and key: swatch_i.label
    • Description: (optional) Product swatch label
    • Type: Single-line text
    • Maximum length: 25
  5. 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:

Swatch color metafields

1. Include the custom swatches script to the project

  1. Copy and paste the snippet file app_swatches.liquid (from the doc/shopify folder) into the snippets folder of the destination site.
  2. Render the snippet app_swatchesin the <head> of theme.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

  1. Add oz-related-products-swatches.liquid to your snippets folder
  2. Locate the liquid file where your product options are currently displayed (usually product-form.liquid or product-template.liquid)
  3. Add this variable at the top of the file {% assign custom_related_product_swatches_enabled = settings.enable_custom_related_products_swatches %}
  4. 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 %}
  1. (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 :

Product list metafield

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

  1. In templates, add search.oz-related-product-block-html.liquid.
  2. Locate the snippet where your product block on the collection page is defined (usually product-thumbnail.liquid, product-block.liquid or product-grid-item.liquid)
  3. 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

  1. 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 %}
  1. Add the cs-product-container class to the very top HTML container of the product block.
  2. 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 %}
  1. In the hierarchy just above that product block file, locate where your snippet is called (usually collection.liquid and search.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 %}
  1. 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 %}
  1. 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}}">
  1. 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

Swatches number setting

8. Add custom swatches on collection page - Integration with USF

8.1. Make relevant product data available to USF

  1. In templates, add search.oz-usf-related-products-json.liquid.
  2. 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

  1. In searchResultsGridViewItem:

    1. Add the oz-cs-product-title class to the HTML element that contains the product title.

    2. Add the oz-cs-product-link class to all <a> elements that contain a link to the product page.

    3. Add the oz-cs-product-price-container class to to the HTML element that contains the product price (goes next to vp-original-prices if VPs are installed).

    4. In the updateAvailabilityCurrentItem function, add the right value for 'wording'. It needs to point to the translation of "sold out".

    5. (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.
    6. (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.
    7. (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.
    8. 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.
  2. (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

  1. 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>
  1. 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.

Deactivate module

  • Give names to swatches

Options names setting

  • Choose which swatches to display in product page

Options to display in product page setting

  • Choose which swatches to display in collection page and cross-sell sections

Options to display elsewhere setting

  • 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;