JS-Tech/resj

View on GitHub
app/javascript/controllers/cards/form_controller.js

Summary

Maintainability
A
25 mins
Test Coverage
import { Controller } from "@hotwired/stimulus"

export default class CardsForm extends Controller {
  static targets = ["form", "step", "previous", "next", "confirmation", "error", "timeline"]
  static steps = ["general", "location", "extra"]

  initialize() {
    this.showCurrentStep();
    // set the state according to the browser history
    window.onpopstate = (e) => {
      if ('step' in e.state) {
        this.stepIndex = this.stepToIndex(e.state.step);
        this.showCurrentStep();
      }
    };
  }

  showCurrentStep() {
    this.stepTargets.forEach((e, i) => {
      e.classList.toggle("current", i == this.stepIndex)
    });
    this.showCurrentNav();

    // update the timeline buttons
    this.timelineTarget.querySelectorAll("button").forEach((e, i) => {
      e.classList.remove("current")
    });
    const currentButton = this.timelineTarget.querySelector(`[data-destination-index="${this.stepIndex}"]`);
    currentButton.classList.add("current")
  }

  showCurrentNav() {
    this.previousTarget.classList.toggle("current", this.stepIndex > 0);
    this.nextTarget.classList.toggle("current", this.stepIndex < CardsForm.steps.length - 1);
    this.confirmationTarget.classList.toggle("current", this.stepIndex >= CardsForm.steps.length - 1);
  }

  showErrors(data) {
    this.errorTarget.innerHTML = "";
    data.forEach((e) => {
      this.errorTarget.innerHTML += "<p>" + e + "</p>";
    });
  }

  clearErrors() {
    this.errorTarget.innerHTML = "";
  }

  next() {
    if (this.stepIndex >= CardsForm.steps.length - 1) throw "Can not go further";
    this.update(() => {
      this.stepIndex++;
      this.showCurrentStep();
    });
  }

  previous() {
    if (this.stepIndex <= 0) throw "Can not go back";
    this.stepIndex--;
    this.showCurrentStep();
  }

  confirmation() {
    this.update(() => {
      window.location = window.location.pathname.replace("edit", "confirmation");
    });
  }

  changeStep(event) {
    this.update(() => {
      this.stepIndex = event.target.dataset.destinationIndex;
      this.showCurrentStep();
    });
  }

  get stepIndex() {
    return this.stepToIndex(this.data.get("step"));
  }

  set stepIndex(i) {
    if (i >= CardsForm.steps.length) throw "Step index is out of bound";
    const step = CardsForm.steps[i]
    this.data.set("step", step);
    const url = new URL(window.location.href);
    url.searchParams.set("step", step);
    history.pushState({ step: step }, document.title, url.pathname + url.search);
  }

  stepToIndex(step) {
    const i = CardsForm.steps.indexOf(step);
    if (i === -1) throw "Invalid step provided";
    return i;
  }

  async update(onSuccess) {
    const success = await this.submit();
    if (success && onSuccess) onSuccess();
  }

  async submit() {
    const formData = new FormData(this.formTarget);
    formData.append("card[current_step]", this.data.get("step"));
    try {
      const res = await fetch(this.formTarget.getAttribute("action"), { method: "PATCH", body: formData });
      if (res.status === 200) {
        const errors = await res.json();
        if (errors.length === 0) {
          this.clearErrors();
          return true;
        } else {
          this.showErrors(errors);
          return false;
        }
      } else {
        console.log("Request rejected by the server with the code " + res.status);
        return false;
      }
    } catch (e) {
      console.log(e);
    }
  }
}