Homepage

Managing Records using AJAX (CRUD operations)

Last edit: Jul 14, 2023

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

Requirements

Steps

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

Step 1: Define prerequisites: Table, Form, Page, javascript functions used in multiple places

Create required files for your implementation:

app/schema/feedback.yml
name: feedback
properties:
- name: message
  type: string
- name: rate
  type: string
app/forms/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.

app/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:

app/views/partials/create.liquid

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

  <h2>Create new feedback (record)</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:

app/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:

app/graphql/feedback.graphql
query feedback($per_page: Int = 10) {
  records(
    filter: {
      table: { value: "feedback" }
    },
    per_page: $per_page,
    sort: { 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:

app/views/pages/feedback_list.json.liquid

{%- graphql recent = "modules/feedback/feedback", per_page: 10 -%}
{{ recent.records.results }}

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

app/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:

app/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 Record you want to edit
app/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 record to edit {% endcomment %}
  <label for="record_id">Record ID</label>
  <input type="text" name='record_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.

app/assets/Feedback.js
[...]

const Update = event => {
  event.preventDefault();
  const id = event.target.querySelector('[name="record_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.

Soft and hard delete

Read Data Backup and Removal to get familiar with data retention and backups in platformOS.

app/views/partials/delete.liquid

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

app/assets/Feedback.js
const Delete = event => {
  event.preventDefault();
  const id = event.target.querySelector('[name="record_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 Records.

Questions?

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

contact us