Learning together challenge: Alpine.js and Fake API (first time use)

Learning together challenge: Alpine.js and Fake API (first time use)

You probably know better

Good day, fellow reader.

My name is Marcelo Zarate and on my first post I stated that one of the reasons I've started this blog is to learn. So today, instead of the classic blog entry with very neat and detailed explanations of what to do here, and there, we are going to learn together. Or, if you already know the subject, you are going to watch and enjoy the experiment. I'm going to try learning Alpine.js alongside you. I have some experience with frontend frameworks, some good memories with jQuery. After reading state of Js 2021 I've decided to give Alpine a go, at least for small projects.

confused.webp

The mission

Let's build a simple app just by using

The conditions

I've never used any of the above. If you also haven't, then this post is probably be more interesting for you. Conditions actually are:

  • No cheating. I will start from Alpine.js documentation, and try to work it out from there.
  • No googling. Or at least, being honest if I get stuck and I need to search something.

Let's start

Introduction

Ok, first we go to Alpine.js homepage and the first screen is an example of usage

<script src="//unpkg.com/alpinejs" defer></script>

<div x-data="{ open: false }">
    <button @click="open = true">Expand</button>

    <span x-show="open">
      Content...
    </span>
</div>

We just try it out locally, and it works!

example.gif

Image 1: Example from documentation in action

So, deducting from the example it seems that x-show attachs some visibility attribute to the block. x-data defines the (data/model?) and @click really looks like Vue v-on, triggering the assignment open = true when the button is clicked.

Fetching data

Ok, from the homepage example, we are going to preserve only the "importing"

<script src="//unpkg.com/alpinejs" defer></script>

We need to fetch the data from the Fake API, following its guide

I found this service after reading Roger Camargo's blog article 3 ways to mock an API in JAvascript. Great article if you have any interest in that topic!

fetch('https://jsonplaceholder.typicode.com/posts')
  .then((response) => response.json())
  .then((json) => console.log(json));

So, it seems that our main div is going to be

<div x-data="{ users: [] }"
    x-init="fetch('https://jsonplaceholder.typicode.com/users')
    .then(response => response.json())
    .then(data => users = data)">
</div>

Now, since we are populating x-data with the information from the fetch, and users is an array, we need a frontend sintax to loop through that array, looking down in alpine documentation we can find the x-for directive that states "Repeat a block of HTML based on a data set". Fits perfectly.

<template x-for="user in users">
    <h2 x-text="user.name"></h2>
    <span x-text="user.username"></span> - <span x-text="user.email"></span>
</template>

only-names-showing-up.png

Image 2: Only names are showing up

Seems to kinda work? I suspect something here, but we better go to the x-for documenation (remember, I've never used Alpine)

Documentation says:

There are two rules worth noting about x-for:

  • x-for MUST be declared on a element
  • That element MUST have only one root element

Ok, so confirmed, we need to wrap our tags with one root element. I'll be using a div tag, should be something more meaningful, I don't know if the fragment concept from React is available in Alpine, but let's keep it simple for now:

<template x-for="user in users">
    <div>
        <h2 x-text="user.name"></h2>
        <span x-text="user.username"></span> - <span x-text="user.email"></span>
    </div>
</template>

And the result:

list-of-names-and-emails.png

Deleting

Ok, so what if we want to delete one user? Fake API allow us to simulate a delete request, quoting the guide

fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'DELETE',
});

Then we might need to add a button like this

<button x-on:click="fetch('https://jsonplaceholder.typicode.com/users/'+user.id, {
    method: 'DELETE',
});">
    Delete user
</button>

Editing

Well, instead of editing in another page, we want to actually toggle editing in the same page, so we probably need something to hide or display inputs. Quickly scrapping we can see x-show and x-if there, they don't look too much different so we are going with the first one that doesn't use the template tag.

<div x-show="open">
  ...
</div>

And we will need to populate and probably sync the data, seems that x-model is going to serve us well.

<div x-data="{ search: '' }">
  <input type="text" x-model="search">

  Searching for: <span x-text="search"></span>
</div>

So we are thinking of something like (we will allow to edit only the name for now, we could just extend the behaviour for other parameters)

<div x-show="editUser">
    <input type="text" x-model="user.name">
</div>

And declaring the boolean editUser in x-data. But that variable is going to be shared with all users, so, if I program a button to edit some user, all the users are going to show their edit inputs (I tried at first, but didn't liked it). So, we are going to need a way to identify in the frontend WHICH user are we currently editing.

We will change the initial x-data

<!-- BEFORE -->
<div x-data="{ users: [] }" ...>
<!-- AFTER -->
<div x-data="{ users: [], editingUser: 0}" ...>

and we will set editingUser with the id of the current user to edit.

<button x-on:click="editingUser = user.id;">Edit Records</button>

Now, in the x-show, if the user.id matches the editingUser, show the edit box

<div x-show="user.id === editingUser">
    <input type="text" x-model="user.name">
</div>

Now, inside that block, we will also need some buttons to actually save the edit, and another one to cancel the edition

<button x-on:click="console.log('saving user: '+user.id);">Save</button><button x-on:click="editingUser = null;">Cancel</button>

purists will come after us for that editingUser = null

Now, an extra, hitting on cancel would be nice to make the value as the original one, but since x-model syncs the input with the actual value, we will need to store the original name in case the user hits cancel and wants it back

So our div becomes

<div x-data="{ users: [], editingUser: 0, originalName: ''}" ...>

And the cancel button

<button x-on:click="editingUser = null; user.name = originalName;">Cancel</button>

Updating

Here, we have two options, to send a PUT request or PATCH request. We are going with the latter

According to the guide on JSON Placeholder:

fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'PATCH',
  body: JSON.stringify({
    title: 'foo',
  }),
  headers: {
    'Content-type': 'application/json; charset=UTF-8',
  },
})
  .then((response) => response.json())
  .then((json) => console.log(json));

Now our "save" button on editing changes from this

<button x-on:click="console.log('saving user: '+user.id);">Save</button><button x-on:click="editingUser = null;">Cancel</button>

to this:

<button x-on:click="fetch('https://jsonplaceholder.typicode.com/posts/1', {
    method: 'PATCH',
    body: JSON.stringify({
      name: 'Test',
    }),
    headers: {
      'Content-type': 'application/json; charset=UTF-8',
    },
    })
    .then((response) => response.json())
    .then((json) => console.log(json));">
    Save
</button>

It works, well, kind of. If we inspect the network tab in developer tools we can see that the endpoint is called correctly, but, in the case of creating/updating/deleting, according to the JSON Placeholder guide:

Important: resource will not be really updated on the server but it will be faked as if.

Finishing touches

So well. This is our final "functional" CRUD with Alpine.js and JSON Placeholder.

no-style-final-result.png

Looks pretty boring. Let's spend no more than 5 minutes with style touches using Tailwind CSS classes and DaisyUI default themes.

styled-final-result.png

The code

See you around!