Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
calogxro committed Dec 10, 2021
0 parents commit 047dd0c
Show file tree
Hide file tree
Showing 9 changed files with 1,183 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO.md
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

199 changes: 199 additions & 0 deletions README.md
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)
31 changes: 31 additions & 0 deletions example-1.html
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>
43 changes: 43 additions & 0 deletions example-2.html
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>
74 changes: 74 additions & 0 deletions example-todo.html
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>
7 changes: 7 additions & 0 deletions localforage.min.js

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions sugar.js
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)
})
}
}
}
Loading

0 comments on commit 047dd0c

Please sign in to comment.