~/ Let's build a CRUD app with the Fetch API
I recently built an application - https://stopgap.store/ which is a simple way to get up and running with an API with almost zero configuration.
In an effort to demo its capabilities, I thought I'd explore working with the fetch API as well.
Fetch is an experimental technology!
- TOC {:toc}
Initial Setup
We're gonna do this thing entirely in a single html file:
<!doctype html>
<html>
<head></head>
<body>
<form>
<label for='todo'>Todo:</label>
<input type='text' id='todo'>
</form>
<ul class='todos'></ul>
</body>
</html>
And a little bit of JavaScript before the closing </body>
tag:
var form = document.querySelector('form')
var input = document.getElementById('todo')
var todos = document.querySelector('.todos')
Setting up the API
In lieu of setting up a local API server to persist our data, or authenticating with a third party provider like Firebase, we can generate a throw-away api at https://stopgap.store/
Click on "Create Project" and copy the URL:
Add the url as a variable:
var apiUrl = 'https://stopgap.store/48277/todos'
Note: You can add whatever resource name you like to the end of the url. I decided to use "todos"
All together now!
<!doctype html>
<html>
<head></head>
<body>
<form>
<label for='todo'>Todo:</label>
<input type='text' id='todo'>
</form>
<ul class='todos'></ul>
<script>
var apiUrl = 'https://stopgap.store/48277/todos'
var form = document.querySelector('form')
var input = document.getElementById('todo')
var todos = document.querySelector('.todos')
</script>
</body>
</html>
Create
A good start would be console.log
ging whatever's in the input
when the user submits the form:
form.addEventListener("submit", create)
function create(event){
event.preventDefault()
console.log(input.value)
}
To create something on the server, we need to send a POST request, with a JSON string of the content we want to send.
function create(event){
event.preventDefault()
fetch(apiUrl,{
method: 'POST',
body: JSON.stringify({
text: input.value
})
}).then(function(response){
response.json().then(function(todo){
console.log(todo)
})
})
}
But instead of logging to the console, we'll need to create a new <li>
and
append it to the existing <ul>
//...
response.json().then(li)
})
}
function li(todo){
var l = document.createElement("li")
l.innerHTML = todo.text
todos.appendChild(l)
}
Read
When the page loads, fetch all the todos from the server, and create <li>
s for each of them.
fetch(apiUrl + ".json").then(function(response){
response.json().then(function(todos){
todos.forEach(li)
})
})
Note: the addition of ".json" is required here because the apiUrl serves HTML as well.
Update
An easy way to allow the user to edit inidividual todos would be to make each li contenteditable
:
function li(todo){
//...
l.setAttribute("contenteditable", true)
and tell the server when the content is no longer being changed on the blur
event:
function li(todo){
//...
l.setAttribute("contenteditable", true)
l.setAttribute("data-id", todo.id)
l.addEventListener("blur", update)
todos.append(l)
}
function update(event){
var id = event.target.getAttribute("data-id")
fetch(apiUrl + "/" + id, {
method: 'PATCH',
body: JSON.stringify({
text: event.target.innerText
})
}).then(function(response){
response.json().then(function(todo){
console.log(todo)
})
})
}
Delete
For each list item, we can add a link to delete, though this will require
a small change or to to our li()
function and PATCH
request.
First, create a <span>
child of the <li>
and make that contenteditable.
function li(todo){
var l = document.createElement("li")
var span = document.createElement("span")
span.setAttribute("contenteditable", true)
span.setAttribute("data-id", todo.id)
span.innerHTML = todo.text
span.addEventListener("blur", update)
l.appendChild(span)
todos.appendChild(l)
}
Then, create a link to delete:
// in function li()
var a = document.createElement("a")
a.innerHTML = '×'
a.addEventListener("click", remove)
a.setAttribute("data-id", todo.id)
l.appendChild(a)
and the function to handle the removal:
function remove(event){
event.preventDefault()
var id = event.target.getAttribute("data-id")
fetch(apiUrl + "/" + id,{
method: 'DELETE'
}).then(function(){
todos.removeChild(event.target.parentNode)
})
}
Add a little css to space things out:
span + a {
display: inline-block;
margin-left: 1em;
cursor: pointer;
}
Complete code
<!doctype html>
<html>
<head>
<style>
span + a {
display: inline-block;
margin-left: 1em;
cursor: pointer;
}
</style>
</head>
<body>
<form>
<label for='todo'>Todo:</label>
<input type='text' id='todo'>
</form>
<ul class='todos'></ul>
<script>
var apiUrl = 'https://stopgap.store/48277/todos'
var form = document.querySelector('form')
var input = document.getElementById('todo')
var todos = document.querySelector('.todos')
form.addEventListener("submit", create)
function create(event){
event.preventDefault()
fetch(apiUrl,{
method: 'POST',
body: JSON.stringify({
text: input.value
})
}).then(function(response){
response.json().then(li)
})
}
function update(event){
var id = event.target.getAttribute("data-id")
fetch(apiUrl + "/" + id + ".json", {
method: 'PATCH',
body: JSON.stringify({
text: event.target.innerText
})
}).then(function(response){
response.json().then(function(todo){
console.log(todo)
})
})
}
function li(todo){
var l = document.createElement("li")
var span = document.createElement("span")
span.setAttribute("contenteditable", true)
span.setAttribute("data-id", todo.id)
span.innerHTML = todo.text
span.addEventListener("blur", update)
l.appendChild(span)
var a = document.createElement("a")
a.innerHTML = '×'
a.addEventListener("click", remove)
a.setAttribute("data-id", todo.id)
l.appendChild(a)
todos.appendChild(l)
}
function remove(event){
event.preventDefault()
var id = event.target.getAttribute("data-id")
fetch(apiUrl + "/" + id,{
method: 'DELETE'
}).then(function(){
todos.removeChild(event.target.parentNode)
})
}
fetch(apiUrl + ".json").then(function(response){
response.json().then(function(todos){
todos.forEach(li)
})
})
</script>
</body>
</html>
~/ Posted by Jesse Shawl on 2016-07-22