Skip to content

Instantly share code, notes, and snippets.

@jaspertandy
Last active May 8, 2021 07:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaspertandy/e99c0d503f19510bc28a293d358ff274 to your computer and use it in GitHub Desktop.
Save jaspertandy/e99c0d503f19510bc28a293d358ff274 to your computer and use it in GitHub Desktop.
import { Controller } from "stimulus"
export default class extends Controller {
// this method is called when Stimulus attaches this
// class to an instance of a controller. It's like a
// constructor but I think it's when all the setup is done
// and we're ready to get started. That's how I use it anyway!
connect() {
this.updating = false // by default, we're not updating anything
this.timer = false // when updating on keypresses, we only do this on a timeout to reduce the number of requests sent
this.waiter = false // if a request is being sent at the same time as another, we need to wait but run anyway so we will check until this.updating is false again
this.lastSetQuantity = -1 // set the default to a value we never send, so that the first one is always successful
}
// this method is called directly by the blur event
updateQuantity(event) {
this.clearTimer() // any timers that didn't run yet can be cancelled in favour of this update
this.setCurrentQuantity() // try to set the current quantity
}
// this method is called directly by the keypress event
startTimer() {
this.clearTimer() // clear our old timers
this.timer = setTimeout(this.setCurrentQuantity.bind(this), 500) // try to set the current quantity, same as blur
}
// clear all running timers
// this may seem odd, but if we have waiters, we want the incoming timer to supersede
// them so they're safe to be trashed
clearTimer() {
clearInterval(this.timer)
clearInterval(this.waiter)
}
setCurrentQuantity() {
if (this.updating) {
this.waitThenRun() // if an update is already running, we spawn our waiter
return // then we're done here - this method will be re-entered when our waiter is done
}
// we aren't updating, but the value we're trying to set is the same
// as the last one, so running again would be redundant
if (this.lastSetQuantity == this.currentQuantity) {
return
}
this.updating = true // we're now updating
// build the data we're going to send to the server
const data = {
quantity: this.currentQuantity,
}
// I hate configuring URLs in JS. I always prefer to read them
// from data attributes controlled by my server, since the server
// is responsible for defining routes
fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
}).then(r => r.json())
.then(this.doneUpdating.bind(this))
}
// shorthand to get my URL for fetch
get endpoint() {
return this.quantityTarget.dataset.endpoint
}
// shorthand to read the current value of the field we're editing
get currentQuantity() {
return this.quantityTarget.value
}
// method to update my UI
doneUpdating(response) {
this.updating = false // we're done updating - let anyone who's waiting run
this.lastSetQuantity = response.data.quantity // the last successfully-set value
// any other UI updates here
}
waitThenRun() {
// there's already a timer running, we don't need another
if (this.waiter) {
return false
}
// wait for the update to be done, checking occasionally
this.waiter = setInterval(this.checkAndUpdate.bind(this), 1000)
}
checkAndUpdate() {
// updates we were waiting for are done
if (!this.updating) {
// we only clear the waiter here because the other timer might be
// waiting for typing to be finished and it's irrelevant to clear
// that here
clearInterval(this.waiter)
// and this is what we've been waiting for!
this.setCurrentQuantity()
}
}
// Stimulus stuff
static get targets() {
return [
'quantity',
'total',
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment