-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 047dd0c
Showing
9 changed files
with
1,183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
TODO.md |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
# Sugar | ||
|
||
Sugar is a tool for rapid web UI prototyping with no server. | ||
|
||
It makes available a **virtual REST API** that accepts JSON requests, returns JSON responses and persists data between requests like a real API. | ||
|
||
Most importantly, its usage is transparent. It doesn't matter how requests are done: you can use `fetch`, [axios](https://github.com/axios/axios), or wathever | ||
library or method you prefer. | ||
|
||
## Usage | ||
|
||
Simply wrap your code with `sugar`. | ||
|
||
``` | ||
sugar(() => { | ||
// your code | ||
}) | ||
``` | ||
|
||
### No setup is needed | ||
|
||
For example, just making a POST request to the `api/todos` *not-yet-existing* endpoint along with sending a JSON object will create the resource. | ||
|
||
``` | ||
sugar(() => { | ||
let todo = { | ||
"text": "thing to do" | ||
} | ||
fetch('api/todos', { | ||
method: 'POST', | ||
body: JSON.stringify(todo) | ||
}) | ||
.then(response => response.json()) | ||
.then(todo => { | ||
console.log(todo) | ||
/* | ||
output: | ||
{ | ||
"text": "thing to do", | ||
"id": 1 | ||
} | ||
*/ | ||
}) | ||
}) | ||
``` | ||
|
||
File: [example-1.html](example-1.html) | ||
|
||
### The resource is persisted | ||
|
||
`GET api/todos/{id}` will return an object previously posted. | ||
|
||
``` | ||
sugar(() => { | ||
var ENDPOINT = 'api/todos' | ||
var todo = { | ||
"text": "thing to todo" | ||
} | ||
/* | ||
* POST api/todos - create a todo | ||
*/ | ||
fetch(ENDPOINT, { | ||
method: 'POST', | ||
body: JSON.stringify(todo) | ||
}) | ||
.then(response => response.json()) | ||
.then(todo => { | ||
/* | ||
* GET api/todo/{id} - retrieve the todo by id | ||
*/ | ||
fetch(`${ENDPOINT}/${todo.id}`) | ||
.then(response => response.json()) | ||
.then(todo => { | ||
console.log(todo) | ||
/* | ||
output: | ||
{ | ||
"text": "thing to todo", | ||
"id": 1 | ||
} | ||
*/ | ||
}) | ||
}) | ||
}) | ||
``` | ||
|
||
File: [example-2.html](example-2.html) | ||
|
||
### Virtual endpoints | ||
|
||
Sugar endpoints are scoped to the `api/` URL. | ||
|
||
Collection endpoints: | ||
- `GET api/{resource}` - Retrieve all items | ||
- `POST api/{resource}` - Create a new item | ||
|
||
Single resource endpoints: | ||
- `GET api/{resource}/{id}` - Get an item | ||
- `PUT api/{resource}/{id}` - Replace an item | ||
- `PATCH api/{resource}/{id}` - Update an item's fields | ||
- `DELETE api/{resource}/{id}` - Delete an item | ||
|
||
### Things to know | ||
|
||
`sugar.js`, `sweetness.js` and `localforage.min.js` must be in the same folder as html files. | ||
|
||
## How it works | ||
|
||
Under the hood, Sugar makes use of a service worker acting like a remote server. | ||
|
||
The service worker (`sweetness.js`): | ||
- intercepts the JSON requests to `api/{resource}` | ||
- persists the received JSON objects | ||
|
||
For the persistence part, IndexedDB API is used through [localForage](https://github.com/localForage/localForage) library. | ||
|
||
## Sample todo app | ||
|
||
``` | ||
<!DOCTYPE HTML> | ||
<html> | ||
<head> | ||
<script src="sugar.js"></script> | ||
</head> | ||
<body> | ||
<div> | ||
<h1>Todo list</h1> | ||
<input id="input" type="text" placeholder="Thing to do" /> | ||
<button id="addBtn">Add</button> | ||
<ul id="todos"></ul> | ||
</div> | ||
<script> | ||
sugar(() => { | ||
var ENDPOINT = 'api/todos' | ||
var input = document.getElementById("input") | ||
var addBtn = document.getElementById("addBtn") | ||
var todoList = document.getElementById("todos") | ||
addBtn.addEventListener('click', addTodo) | ||
renderTodoList() | ||
function renderTodoList() { | ||
return fetch(ENDPOINT) | ||
.then(response => response.json()) | ||
.then(todos => { | ||
todoList.innerHTML = "" | ||
for (const todo of todos) { | ||
renderTodo(todo.id, todo.text) | ||
} | ||
}) | ||
} | ||
function renderTodo(id, text) { | ||
var li = document.createElement("li") | ||
li.appendChild(document.createTextNode(text)) | ||
li.onclick = () => { | ||
deleteTodo(id) | ||
} | ||
todoList.appendChild(li) | ||
} | ||
function addTodo() { | ||
if (input.value.trim() === "") { | ||
return | ||
} | ||
fetch(ENDPOINT, { | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
"text": input.value | ||
}) | ||
}) | ||
.then(response => response.json()) | ||
.then(todo => { | ||
input.value = "" | ||
renderTodo(todo.id, todo.text) | ||
}) | ||
} | ||
function deleteTodo(id) { | ||
fetch(ENDPOINT+'/'+id, { | ||
method: 'DELETE' | ||
}) | ||
.then(response => { | ||
renderTodoList() | ||
}) | ||
} | ||
}) | ||
</script> | ||
</body> | ||
</html> | ||
``` | ||
|
||
File: [example-todo.html](example-todo.html) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<!DOCTYPE HTML> | ||
<html> | ||
<head> | ||
<script src="sugar.js"></script> | ||
</head> | ||
<body> | ||
<script> | ||
sugar(() => { | ||
let todo = { | ||
"text": "thing to do" | ||
} | ||
|
||
fetch('api/todos', { | ||
method: 'POST', | ||
body: JSON.stringify(todo) | ||
}) | ||
.then(response => response.json()) | ||
.then(todo => { | ||
console.log(todo) | ||
/* | ||
output: | ||
{ | ||
"text": "thing to do", | ||
"id": 1 | ||
} | ||
*/ | ||
}) | ||
}) | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<!DOCTYPE HTML> | ||
<html> | ||
<head> | ||
<script src="sugar.js"></script> | ||
</head> | ||
<body> | ||
<script> | ||
sugar(() => { | ||
var ENDPOINT = 'api/todos' | ||
|
||
var todo = { | ||
"text": "thing to todo" | ||
} | ||
|
||
/* | ||
* POST api/todos - create a todo | ||
*/ | ||
fetch(ENDPOINT, { | ||
method: 'POST', | ||
body: JSON.stringify(todo) | ||
}) | ||
.then(response => response.json()) | ||
.then(todo => { | ||
/* | ||
* GET api/todo/{id} - retrieve the todo by id | ||
*/ | ||
fetch(`${ENDPOINT}/${todo.id}`) | ||
.then(response => response.json()) | ||
.then(todo => { | ||
console.log(todo) | ||
/* | ||
output: | ||
{ | ||
"text": "thing to todo", | ||
"id": 1 | ||
} | ||
*/ | ||
}) | ||
}) | ||
}) | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<!DOCTYPE HTML> | ||
<html> | ||
<head> | ||
<script src="sugar.js"></script> | ||
</head> | ||
<body> | ||
<div> | ||
<h1>Todo list</h1> | ||
<input id="input" type="text" placeholder="Thing to do" /> | ||
<button id="addBtn">Add</button> | ||
<ul id="todos"></ul> | ||
</div> | ||
|
||
<script> | ||
sugar(() => { | ||
var ENDPOINT = 'api/todos' | ||
|
||
var input = document.getElementById("input") | ||
var addBtn = document.getElementById("addBtn") | ||
var todoList = document.getElementById("todos") | ||
|
||
addBtn.addEventListener('click', addTodo) | ||
|
||
renderTodoList() | ||
|
||
function renderTodoList() { | ||
return fetch(ENDPOINT) | ||
.then(response => response.json()) | ||
.then(todos => { | ||
todoList.innerHTML = "" | ||
for (const todo of todos) { | ||
renderTodo(todo.id, todo.text) | ||
} | ||
}) | ||
} | ||
|
||
function renderTodo(id, text) { | ||
var li = document.createElement("li") | ||
li.appendChild(document.createTextNode(text)) | ||
li.onclick = () => { | ||
deleteTodo(id) | ||
} | ||
todoList.appendChild(li) | ||
} | ||
|
||
function addTodo() { | ||
if (input.value.trim() === "") { | ||
return | ||
} | ||
fetch(ENDPOINT, { | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
"text": input.value | ||
}) | ||
}) | ||
.then(response => response.json()) | ||
.then(todo => { | ||
input.value = "" | ||
renderTodo(todo.id, todo.text) | ||
}) | ||
} | ||
|
||
function deleteTodo(id) { | ||
fetch(ENDPOINT+'/'+id, { | ||
method: 'DELETE' | ||
}) | ||
.then(response => { | ||
renderTodoList() | ||
}) | ||
} | ||
}) | ||
</script> | ||
</body> | ||
</html> |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/*! | ||
* Sugar v0.1.0- Rapid web UI prototyping with no server | ||
* https://github.com/calogxro/sugar | ||
* Copyright (C) 2021 Calogero Miraglia <calogero.miraglia@gmail.com> | ||
* Licensed under GNU General Public License v3.0 | ||
* See file https://github.com/calogxro/sugar/LICENSE | ||
*/ | ||
|
||
// Register the service worker | ||
function sugar(callback) { | ||
if ('serviceWorker' in navigator) { | ||
if (navigator.serviceWorker.controller) { | ||
//console.log("controller") | ||
callback() | ||
} else { | ||
navigator.serviceWorker.oncontrollerchange = function() { | ||
this.controller.onstatechange = function() { | ||
if (this.state === 'activated') { | ||
//console.log("onstatechange: activated") | ||
callback() | ||
} | ||
} | ||
} | ||
|
||
navigator.serviceWorker.register('sweetness.js', { | ||
scope: '/' | ||
}) | ||
.then(function(reg) { | ||
// registration worked | ||
//console.log('Registration succeeded. Scope is ' + reg.scope) | ||
}) | ||
.catch(function(error) { | ||
// registration failed | ||
//console.log('Registration failed with ' + error) | ||
}) | ||
} | ||
} | ||
} |
Oops, something went wrong.