Managing Customizations using AJAX (CRUD operations)

Last edit: Jun 05, 2019

This guide will help you create, list, update, and delete Customizations using JavaScript and GraphQL.

Requirements

Steps

Managing Customizations using AJAX by implementing the full CRUD cycle is a five-step process:

Step 1: Define prerequisites: Custom Model Type, Form Configuration, Page, javascript functions used in multiple places

Create required files for your implementation:

custom_model_types/feedback.yml

name: feedback
properties:
- name: message
  type: string
- name: rate
  type: string

form_configurations/feedback.liquid

---
name: feedback_form
resource: feedback
resource_owner: anyone
fields:
  properties:
    rate:
      validation: { presence: true }
    message:
      validation: { presence: false }
---
{% include 'modules/feedback/create' %}
{% include 'modules/feedback/read' %}
{% include 'modules/feedback/update' %}
{% include 'modules/feedback/delete' %}

Warning
For example purposes resource_owner is set to anyone, which means on live example page you will be able to edit any record. In real life scenario it is recommended to set Authorization Policy to prevent even not logged in user to submit a form.

assets/Feedback.js

const request = ({ url, form }) => {
  fetch(url, {
    credentials: "same-origin", // make sure safari understands what we are doing
    method: "POST",
    body: new FormData(form) // create FormData from form passed or empty if undefined
  })
};

[...]

Step 2: Implement Create

First, create a simple form that works without JavaScript:

views/partials/create.liquid


{% form html-data-form: "create" %}
  {% assign p = form_builder.fields.properties %}

  <h2>Create new feedback (customization)</h2>
  <label>
    <input type="radio" name='{{ p.rate.name }}' value="Excellent">
    Excellent
  </label>
  <label>
    <input type="radio" name='{{ p.rate.name }}' value="Meh">
    Meh
  </label>
  <label>
    <input type="radio" name='{{ p.rate.name }}' value="Very bad">
    Very bad
  </label>

  <label for="create_message">Message</label>
  <textarea type='text' id="create_message" name='{{ p.message.name }}'></textarea>

  <button>Create</button>
{% endform %}

Then add JavaScript to make it work in the background:

assets/Feedback.js

[...]

const Create = event => {
  event.preventDefault();

  request({
    url: event.target.getAttribute("action"),
    form: event.target
  })
};

const createForm = document.querySelector('[data-form="create"]');
createForm.addEventListener("submit", Create);

Step 3: Implement Read

First, you'll need a GraphQL query to pull out the data:

graph_queries/feedback.graphql

query feedback($per_page: Int = 10) {
  customizations(
    name: "feedback"
    per_page: $per_page
    sort: { name: "created_at", order: "desc" }
  ) {
    results {
      id
      created_at
      updated_at
      rate: property(name: "rate")
      message: property(name: "message")
    }
  }
}

Then you need to create a page that returns JSON with the data:

views/pages/feedback_list.liquid

---
slug: feedback_list
layout_name: ''
format: json
---
{%- graphql recent = "modules/feedback/feedback", per_page: 10 -%}
{{ recent.customizations.results | json }}

You also need some HTML that will contain the data, and a button that will trigger data download:

views/partials/read.liquid

<button type="button" data-button="refreshRead">Refresh content from the server</button>
<table>
  <thead>
    <th>ID</th>
    <th>Created at</th>
    <th>Updated at</th>
    <th>Rating</th>
    <th>Message</th>
  </thead>
  <tbody data-body="readTable"></tbody>
</table>

You also need JavaScript to get the data from the server and render it into the HTML:

assets/Feedback.js

const updateReadTable = data => {
  const readBody = document.querySelector('[data-body="readTable"]');
  const html = data.map(
    feedback => `<tr>
      <td>${feedback.id}</td>
      <td>${feedback.created_at}</td>
      <td>${feedback.updated_at}</td>
      <td>${feedback.rate}</td>
      <td>${escape(feedback.message)}</td>
    </tr>`
  ).join('');
  readBody.innerHTML = html;
};

const Read = () => {
  fetch("/feedback_list.json")
    .then(response => response.json())
    .then(updateReadTable)
};

const refreshReadButton = document.querySelector('[data-button="refreshRead"]');
refreshReadButton.addEventListener("click", Read);

Step 4: Implement Update

The update form is similar to Create, with a couple of differences:

  1. Add hidden input with _method set to PUT
  2. Send ID of Customization you want to edit

views/partials/update.liquid


{% form html-data-form: "update" %}
  {% comment %} Set method using hidden input to tell the server we are updating {% endcomment %}
  <input type="hidden" name="_method" value="PUT" />

  {% comment %} Let user decide which customization to edit {% endcomment %}
  <label for="customization_id">Customization ID</label>
  <input type="text" name='customization_id' value="" required>

  <label>
    <input type="radio" name='{{ p.rate.name }}' value="Excellent">
    Excellent
  </label>
  <label>
    <input type="radio" name='{{ p.rate.name }}' value="Meh">
    Meh
  </label>
  <label>
    <input type="radio" name='{{ p.rate.name }}' value="Very bad">
    Very bad
  </label>

  <label for="update_message">Message</label>
  <textarea type='text' id="update_message" name='{{ p.message.name }}'></textarea>

  <button>Update</button>
{% endform %}

JavaScript is also very similar, but it takes ID from input defined above and puts it into the URL.

assets/Feedback.js

[...]

const Update = event => {
  event.preventDefault();
  const id = event.target.querySelector('[name="customization_id"]').value;

  request({
    url: `${event.target.getAttribute("action")}/${id}`,
    form: event.target
  })
};

const updateForm = document.querySelector('[data-form="update"]');
updateForm.addEventListener("submit", Update);

Step 5: Implement Delete

The delete operation also needs method and id because underneath it all, it is just an update.

views/partials/delete.liquid


{% form html-data-form: "delete" %}
  <input type="hidden" name="_method" value="DELETE" />
  <label for="customization_id">Customization ID</label>
  <input type="text" name='customization_id' value="" required>
  <button>Delete</button>
{% endform %}

assets/Feedback.js

const Delete = event => {
  event.preventDefault();
  const id = event.target.querySelector('[name="customization_id"]').value;

  request({
    url: `${event.target.getAttribute("action")}/${id}`,
    form: event.target
  })
};

const deleteForm = document.querySelector('[data-form="delete"]');
deleteForm.addEventListener("submit", Delete);

Live example and source code

To play with a live example (in much fuller form and with additional information) go to feedback example page.

Source code can be found on GitHub.

Next steps

Congratulations! You know how to implement CRUD operations for Customizations.

Questions?

We are always happy to help with any questions you may have.