255 lines
9.0 KiB
HTML
Executable File
255 lines
9.0 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Storadons</title>
|
|
<link rel="icon" href="https://fav.farm/🦕">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@800&family=Roboto&display=swap" rel="stylesheet">
|
|
<style>
|
|
/* JMU Slate Gray: #333333 Ice Blue: #8EE4D7 JMU Purple: #450084 Teal: #009698 JMU Light Gold: #F4EFE1 */
|
|
body {
|
|
margin: 0;
|
|
display: flex;
|
|
height: 100vh;
|
|
font-family: Roboto;
|
|
}
|
|
|
|
main {
|
|
background-color: #F4EFE1;
|
|
color: #333;
|
|
flex-grow: 1;
|
|
padding: 1rem;
|
|
}
|
|
|
|
aside {
|
|
border-inline-start: 1rem solid #009698;
|
|
background-color: #450084;
|
|
color: #8EE4D7;
|
|
max-width: 33vw;
|
|
padding: 1rem;
|
|
}
|
|
|
|
main,
|
|
aside {
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
aside a {
|
|
color: #F4EFE1;
|
|
}
|
|
|
|
aside a:visited {
|
|
color: #009698;
|
|
}
|
|
|
|
header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 1rem;
|
|
}
|
|
|
|
h1 {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
#downloads {
|
|
display: none;
|
|
}
|
|
|
|
code {
|
|
background-color: #00000099;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<main>
|
|
<header>
|
|
<h1>🦕 Storadons</h1>
|
|
<button id="share" type="button">Get Sharable URL</button>
|
|
<button id="export" type="button">Export</button>
|
|
<form action="#" id="import-form">
|
|
<input type="file" name="import-file" id="import-file" required>
|
|
<button id="import" type="submit">Import</button>
|
|
</form>
|
|
</header>
|
|
<form id="new-item-form" action="#">
|
|
<label for="item">New todo item</label>
|
|
<input type="text" name="item" id="item">
|
|
<button type="submit">Add</button>
|
|
</form>
|
|
<ol id="items"></ol>
|
|
<div id="downloads"></div>
|
|
</main>
|
|
<aside>
|
|
<section>
|
|
<h2>Overview</h2>
|
|
<p>Look at this excellent little TODO app! 🤩 The code as provided implements these features:</p>
|
|
<ul>
|
|
<li>adding items to the todo list</li>
|
|
<li>listing the new item at the end of the existing to do list items</li>
|
|
</ul>
|
|
</section>
|
|
<hr>
|
|
<section>
|
|
<p>Know what might be neato? I think it needs MOAR ... 🔔</p>
|
|
<h2>Part 1: Persistodon</h2>
|
|
<p>It'd be nice if we could close the window and not lose all of our data.</p>
|
|
<h3>TODO (Part 1)</h3>
|
|
<p>There are <code>// TODO (Part 1)</code> comments in the JavaScript code to guide your way while you add
|
|
these features:</p>
|
|
<ol>
|
|
<li><em>persist</em> the todo items to <code>localStorage</code> as they are added to the to do list</li>
|
|
<li><em>load any todo items from <code>localStorage</code> when the page loads </em></li>
|
|
</ol>
|
|
<h2>Part 2: Sharasaurus</h2>
|
|
<p>How could we view the same list on our phone as we are viewing on our laptop? Share the list with a friend?
|
|
</p>
|
|
<h3>TODO (Part 2)</h3>
|
|
<p>There are <code>// TODO (Part 2)</code> comments in the JavaScript to guide you in your way while you add these
|
|
features:</p>
|
|
<ol>
|
|
<li>notice if the page was passed a query string and to populate <code>localStorage</code>
|
|
initially with the items in the query string</li>
|
|
<li>modify the <code>Get Sharable URL</code> button so that it updates the url so that all of the todo
|
|
items currently stored in <code>localStorage</code> are encoded in the <code>query string</code> in the
|
|
browser's address bar</li>
|
|
</ol>
|
|
</section>
|
|
<hr>
|
|
<section>
|
|
<h2>Part 3: Transportodon</h2>
|
|
<p>URLs have a pretty short length limit. What if we add too much stuff to fit into the URL? How can we share a
|
|
file version?</p>
|
|
<h3>TODO (Part 3)</h3>
|
|
<p>There are <code>// TODO (Part 3)</code> comments in the JavaScript to guide you to add these features:</p>
|
|
<ol>
|
|
<li>modify the <code>Export</code> button so that it creates a Blob containing the items from
|
|
<code>localStorage</code>
|
|
</li>
|
|
<li>modify the <code>Import</code> button to replace the items in <code>localStorage</code> with the file's text
|
|
content</li>
|
|
</ol>
|
|
</section>
|
|
</aside>
|
|
<script>
|
|
// get the DOM elements
|
|
const form = document.getElementById('new-item-form');
|
|
const input = document.getElementById('item');
|
|
const list = document.getElementById('items');
|
|
|
|
function addItemToList(item) {
|
|
const newLi = document.createElement('li')
|
|
newLi.textContent = item;
|
|
list.append(newLi);
|
|
}
|
|
|
|
function populateFromLocalStorage() {
|
|
// TODO (Part 1)
|
|
// Retrieve the stored items from localStorage. Parse the returned string using JSON.parse
|
|
// and pass each item in the array to addItemToList.
|
|
const todosString = localStorage.getItem('todos')
|
|
if (!todosString) return
|
|
|
|
const todos = JSON.parse(todosString)
|
|
todos.forEach(addItemToList)
|
|
}
|
|
|
|
form.addEventListener('submit', (ev) => {
|
|
// TODO (Part 1)
|
|
// Get the new item from the input field and pass it to addItemToList. Get the array of
|
|
// items currently stored in localStorage and add the new item to the end of the list.
|
|
// If there aren't any items in localStorage (because the page was just loaded for the
|
|
// first time), make an array of just the new item. P.S. don't forget to prevent the
|
|
// page from reloading when the form is submitted and clear the input field.
|
|
ev.preventDefault()
|
|
const newTodo = input.value
|
|
|
|
const todosString = localStorage.getItem('todos')
|
|
const todos = todosString ? JSON.parse(todosString) : []
|
|
|
|
todos.push(newTodo)
|
|
localStorage.setItem('todos', JSON.stringify(todos))
|
|
addItemToList(newTodo)
|
|
})
|
|
|
|
function populateLocalStorageFromURLSearch() {
|
|
// TODO (Part 2)
|
|
// Given the URL index.html?first&second, the query string is ?first&second and can be
|
|
// accessed as location.search. If a query string is passed as part of the URL, replace
|
|
// the localStorage items with an array created by splitting the query string. In the
|
|
// previous example, the new array should be [ 'first', 'second' ].
|
|
const queryParams = new URLSearchParams(location.search)
|
|
if (queryParams.size == 0) return
|
|
|
|
localStorage.setItem('todos', JSON.stringify(Array.from(queryParams.keys())))
|
|
}
|
|
|
|
// get url
|
|
document.getElementById('share').addEventListener('click', (ev) => {
|
|
// TODO (Part 2)
|
|
// Generate a sharable link of the current items. The URL should consist of the
|
|
// current location (window.location), but add on '?' and a string version of the
|
|
// items with an '&' between each. For instance, if the URL is http://foo.com/index.html
|
|
// and the list consists of [ 'first', 'second' ], the sharable URL should be
|
|
// http://foo.com/index.html?first&second
|
|
// Update window.location to this URL so you can copy and paste from the browser's
|
|
// URL bar.
|
|
// CAUTION: If the URL already has a query string, that should NOT be included in
|
|
// this new version. As a hint, use console.log to print out the window.location
|
|
// object and see what parts you actually need.
|
|
const sharableURL = new URL(location.toString())
|
|
const todosString = localStorage.getItem('todos')
|
|
todosString ? sharableURL.search = `?${JSON.parse(todosString).join('&')}` : ''
|
|
window.location = sharableURL.toString()
|
|
});
|
|
|
|
// export blob
|
|
document.getElementById('export').addEventListener('click', (ev) => {
|
|
// TODO (Part 3)
|
|
// Generate a Blob of the localStorage items. See Week 11 slides for creating
|
|
// a blob, a link, and a download action. You'll create the Blob from the
|
|
// items in localStorage.
|
|
const blob = new Blob([localStorage.getItem('todos')], { type: 'text/plain' })
|
|
const url = window.URL.createObjectURL(blob)
|
|
|
|
const link = document.createElement('a')
|
|
link.href = url
|
|
link.download = 'data.json'
|
|
link.click()
|
|
|
|
window.URL.revokeObjectURL(url)
|
|
link.remove()
|
|
});
|
|
|
|
// import from file
|
|
let importForm = document.getElementById('import-form');
|
|
importForm.addEventListener('submit', (ev) => {
|
|
// TODO (Part 3)
|
|
// Get files[0] from the import-file input when the form is submitted.
|
|
// Use .text() on the file to get a Promise that resolves to the file
|
|
// contents. Store the resolved data as the items in localStorage, then
|
|
// use populateFromLocalStorage() to load the items. Do not forget to
|
|
// stop the page from reloading.
|
|
ev.preventDefault()
|
|
const file = importForm['import-file'].files[0]
|
|
file.text().then((text) => {
|
|
localStorage.setItem('todos', text)
|
|
populateFromLocalStorage()
|
|
})
|
|
});
|
|
|
|
// initialize the page
|
|
(function () {
|
|
populateLocalStorageFromURLSearch();
|
|
populateFromLocalStorage();
|
|
})();
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html> |