Skip to content

Instantly share code, notes, and snippets.

@panoply
Created May 7, 2021 15:46
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save panoply/241c1fbabd210d110b5290f046ab5f49 to your computer and use it in GitHub Desktop.
Save panoply/241c1fbabd210d110b5290f046ab5f49 to your computer and use it in GitHub Desktop.
Stimulus Shopify Sections Example

Logic

We are using Stimulus.js and Siema Carousel to create a Product slideshow Shopify Section. Because stimulus accepts options from element attributes, we can pass customisations to the carousel from the sections HTML markup and thus we can avoid infusing JavaScript.

The code below can give you some understanding of how it is achieved. It's an extraction from a project I have in production and of course without the related stylesheets and css it will not function as a drop-in solution but hopefully gives you some clarity on how leveraging stimulus in a Shopify theme benefits one greatly.

Couple of key points and takeaways are the the way we wire it up, the "controller" context where we pass in option is set on the first div tag:

<div
  class="row pt-{{ section.settings.gutter }} pb-3"
  data-carousel-draggable-value="{{- section.settings.draggable }}"
  data-carousel-interval-value="{{- section.settings.interval | append: '000' -}}"
  data-carousel-loop-value="{{- section.settings.loop }}"
  data-carousel-per-page-value="{{- section.settings.per_page }}"
  data-controller="carousel">

This option are passed the static class values in the controller, eg:

static values = {
  startIndex: Number,
  draggable: Boolean,
  perPage: Number,
  loop: Boolean,
  interval: Number,
  initHidden: Boolean,
  hydrate: String,
  loadOnClick: Boolean,
  duration: Number
}

You will need to read about Stimulus to understand what is happening, so do that. Nonetheless this code stands as an example of how one would leverage stimulus in a Shopify theme project.

// @ts-nocheck
import { Controller } from 'stimulus'
import Siema from 'siema'
/**
* Carousel
*
* @export
* @class Carousel
* @extends {Controller}
* @typedef {Targets.Carousel} ITargets
* @typedef {Model.IValues<Carousel.values>} IValues
* @typedef {ITargets & IValues & Carousel} ICarousel
*/
export class Carousel extends Controller {
static cache = {
loadOnClick: []
}
/**
* Default Dataset Values
*
* @static
* @memberof Carousel
*/
static values = {
startIndex: Number,
draggable: Boolean,
perPage: Number,
loop: Boolean,
interval: Number,
initHidden: Boolean,
hydrate: String,
loadOnClick: Boolean,
duration: Number
}
/**
* Stimulus Targets
*
* @static
* @memberof Slideshow
*/
static targets = [
'slide',
'siema'
]
/**
* Stimulus Initialize
*/
initialize () {
this.hydrate()
}
/**
* Stimulus Connect
*
* @this {ICarousel}
*/
connect () {
this.slider = new Siema({
selector: this.siemaTarget,
loop: this.loopValue,
startIndex: this.startIndexValue,
duration: 300,
easing: 'ease-out',
onInit: this.onInit.bind(this),
onChange: this.onChange.bind(this),
draggable: this.draggableValue,
perPage: this.perPageValue <= 1 ? 1 : {
0: 2,
480: 2,
768: this.perPageValue - 2,
1024: this.perPageValue - 1,
1400: this.perPageValue
}
})
}
/**
* Stimulus Disconnect
*/
disconnect () {
if (this.loadOnClickValue) {
Carousel.cache.loadOnClick.forEach(slide => {
if (!slide.classList.contains('d-none')) {
slide.classList.add('d-none')
}
})
}
// this.onMouseOver()
this.slider.destroy()
}
/**
* Reset
* Executes on the Turbo `before-cache` event
*/
reset () {
this.slider.destroy(true)
}
/**
* Hydrate
* Modifies the SSR content
*
* @this {ICarousel}
*/
hydrate () {
if (this.siemaTarget.classList.contains('row')) {
this.siemaTarget.classList.remove('row')
this.slideTargets.forEach(({ classList }) => classList.remove('d-none'))
} else if (this.initHiddenValue === true) {
this.slideTargets.forEach(({ classList }) => classList.remove('d-none'))
} else if (this.hasHydrateValue) {
this.slideTargets.forEach(({ classList }) => classList.remove('d-none'))
}
}
/**
* Siema
* Next Button
*/
next () {
this.slider.next()
}
/**
* Siema
* Previous Button
*/
prev () {
this.slider.prev()
}
onMouseOver () {
if (this.intervalValue > 0) {
clearInterval(this.interval)
}
}
onMouseOut () {
this.onInit()
}
onInit () {
if (this.intervalValue > 0) {
this.interval = setInterval(() => this.slider.next(), this.intervalValue)
}
}
/**
* Siema
* onChange event
*/
onChange () {
this.startIndexValue = this.slider.currentSlide
if (this.loadOnClickValue) {
const slide = this.slideTargets[this.slider.currentSlide]
if (slide.classList.contains('d-none')) {
slide.classList.remove('d-none')
Carousel.cache.loadOnClick.push(slide)
} else {
Carousel.cache.loadOnClick.forEach(slide => {
if (!slide.classList.contains('d-none')) {
slide.classList.add('d-none')
}
})
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment