Skip to content

Instantly share code, notes, and snippets.

@calogxro
Last active September 10, 2022 11:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save calogxro/6e601e07c2a937df4418d104fb717570 to your computer and use it in GitHub Desktop.
Save calogxro/6e601e07c2a937df4418d104fb717570 to your computer and use it in GitHub Desktop.
ToDo app to sample Caffeine backend (https://github.com/rehacktive/caffeine) - DEMO: http://3.72.37.48:8001/
<!--
# Caffeinated #
ToDo app made with Vue.js
to sample Caffeine backend (https://github.com/rehacktive/caffeine)
Created on Oct 24, 2021
by Calogero Miraglia (https://github.com/calogxro)
GitHub: https://gist.github.com/calogxro/6e601e07c2a937df4418d104fb717570
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
/*sticky footer*/
html, body {
height: 100%;
}
body {
display: flex;
flex-direction: column;
}
.content {
flex: 1 0 auto;
}
footer {
flex-shrink: 0;
}
/*end sticky footer*/
body {
font-family: Arial, Helvetica, sans-serif;
max-width: 700px;
margin: 0px auto;
}
header {
display: inline-block;
}
header h1 {
float: left;
margin: 0;
color: mediumseagreen;
}
header h1 span {
color: orangered;
}
header .logo {
color: white;
background-color: orangered;
text-decoration: none;
display: inline-block;
padding: 10px;
outline: 1px solid orangered;
outline-offset: 1px;
margin: 2px;
}
header .logo {
float: left;
margin-right: 20px;
margin-bottom: 20px;
}
footer, footer a {
font-size: small;
text-align: center;
color: lightseagreen;
}
header, footer, .content {
padding: 20px;
}
button {
padding: 10px;
min-width: 80px;
}
form label {
display: inline-block;
margin-bottom: 5px;
}
form input {
width: 50%;
padding: 10px;
display: block;
margin-bottom: 10px;
}
form#addForm input {
display: inline-block;
}
table#todos {
width: 100%;
margin: 30px auto;
border-collapse: collapse;
}
table#todos tr {
border-top: 1px solid cyan;
border-bottom: 1px solid cyan;
height: 60px;
}
table#todos tr:hover {
background-color: lightblue;
}
table#todos td {
padding: 10px;
}
table#todos td:nth-child(2) {
color: grey;
text-align: right;
font-size: x-large;
}
table#todos tr td:nth-child(2) * {
visibility: hidden;
}
table#todos tr:hover td:nth-child(2) * {
visibility: visible;
}
table#todos .action {
cursor: pointer;
padding-left: 0.5em;
}
table#todos .action:hover {
color: black;
}
table#todos tr.done td:first-child {
text-decoration: line-through;
}
</style>
</head>
<body>
<header>
<a href="https://gist.github.com/calogxro/6e601e07c2a937df4418d104fb717570"
class="logo"><span><i class="fas fa-mug-hot"></i> Caffeinated</span></a>
<h1>My To<span>Do</span> List</h1>
</header>
<div id="app" class="content">
<form id="addForm" v-if="showAddForm" v-on:submit.prevent="addTodo">
<input v-model="newText" type="text" placeholder="Thing to do"/>
<button>Add</button>
</form>
<form v-else v-on:submit.prevent="modifyTodo">
<label>Id</label>
<input v-model="selectedTodo.id" type="text" disabled/>
<label>Text</label>
<input v-model="newText" type="text" placeholder="Thing to do"/><br>
<button value="replace">Modify</button>
<button value="cancel">Cancel</button>
</form>
<table id="todos">
<tr class="sticky">
<td>Learn JavaScript</td>
<td><span class="sticky-label">sticky</span></td>
</tr>
<tr class="sticky">
<td>Learn Vue.js</td>
<td><span class="sticky-label">sticky</span></td>
</tr>
<tr class="sticky">
<td>Build Something Awesome</td>
<td><span class="sticky-label">sticky</span></td>
</tr>
<tr v-for="todo in todos" v-bind:class="{ done: todo.done }">
<td>
{{todo.text}}
</td>
<td class="actions">
<!-- toggle done/undone -->
<i v-if="todo.done" class="fas fa-times-circle action" @click="toggleDoneTodo(todo.id)" title="Mark as undone"></i>
<i v-else class="fas fa-check-circle action" @click="toggleDoneTodo(todo.id)" title="Mark as done"></i>
<!-- end toggle done/undone -->
<i class="fas fa-edit action" @click="editTodo(todo.id)" title="Edit"></i>
<i class="fas fa-trash-alt action" @click="deleteTodo(todo.id)" title="Delete"></i>
</td>
</tr>
</table>
</div>
<footer>
&copy; 2021 <a href="https://github.com/calogxro">Calogero Miraglia</a> |
Made in Sicily <i class="fas fa-umbrella-beach"></i> with Vue.js |
Powered by <a href="https://github.com/rehacktive">aw4y</a>'s
<a href="https://github.com/rehacktive/caffeine">Caffeine</a> |
<a href="https://gist.github.com/calogxro/6e601e07c2a937df4418d104fb717570"
>GitHub</a>
</footer>
<script>
const HOST = document.location.hostname
const ENDPOINT = `http://${HOST}:8000/ns/todos`
const app = new Vue({
el: "#app",
data: {
nextId: 1,
todos: [],
showAddForm: true,
newText: "",
selectedTodo: null
},
mounted() {
axios.get(ENDPOINT)
.then(response => {
for (const data of response.data) {
this.todos.push({
id: data.value.id,
text: data.value.text,
done: data.value.done
})
// update nextId
if (data.value.id >= this.nextId) {
this.nextId = data.value.id + 1
}
}
this.sort()
})
.catch(error => {
// If a "400 Bad Request" error is returned
// it means the collection does not exist
if (error.response.status !== 400) {
this.onError(error)
}
})
},
methods: {
addTodo() {
if (this.newText.trim() === "") {
return
}
axios.post(`${ENDPOINT}/${this.nextId}`, {
id: this.nextId,
text: this.newText,
done: false
})
.then(response => {
this.newText = ""
this.todos.push({
id: response.data.id,
text: response.data.text,
done: response.data.done
})
this.nextId++
this.sort()
})
.catch(error => {
this.onError(error)
})
},
deleteTodo(id) {
axios.delete(`${ENDPOINT}/${id}`)
.then(response => {
this.todos = this.todos.filter(todo => {
return todo.id !== id
})
// the deleted element might be selected
this.resetForm()
})
.catch(error => {
this.onError(error)
})
},
editTodo(id) {
this.showAddForm = false
this.selectedTodo = this.todos.find(todo => {
return todo.id === id
})
this.newText = this.selectedTodo.text
this.scrollToTop()
},
modifyTodo(event) {
const id = this.selectedTodo.id
const action = event.submitter.value
if (action === "cancel") {
this.resetForm()
} else if (action === "replace") {
axios.post(`${ENDPOINT}/${id}`, {
id: id,
text: this.newText,
done: this.selectedTodo.done
})
.then(response => {
this.selectedTodo.text = this.newText
this.resetForm()
})
.catch(error => {
this.onError(error)
})
}
},
toggleDoneTodo(id) {
let todo = this.todos.find(todo => {
return todo.id === id
})
axios.post(`${ENDPOINT}/${id}`, {
id: id,
text: todo.text,
done: ! todo.done
})
.then(response => {
todo.done = response.data.done
this.sort()
})
.catch(error => {
this.onError(error)
})
},
sort() {
this.todos.sort((a, b) => {
if (a.done !== b.done) {
if (a.done) {
return 1
}
return -1
}
return a.id - b.id
})
},
resetForm() {
this.newText = ""
this.selectedTodo = null
this.showAddForm = true
},
scrollToTop() {
window.scrollTo(0, 0)
},
onError(error) {
alert(error)
}
}
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment