Viele To-do Liste speichern ihre Daten im Localstorage. Diese Version nutzt den indexedDB Speicher
Code
<!DOCTYPE html>
<!-- saved from url=(0055)https://mdn.github.io/dom-examples/to-do-notifications/ -->
<html lang="en-US" data-lt-installed="true"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=380">
<script>
window.onload = () => {
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
// Hold an instance of a db object for us to store the IndexedDB data in
let db;
// Create a reference to the notifications list in the bottom of the app; we will write database messages into this list by
// appending list items as children of this element
const note = document.getElementById('notifications');
// All other UI elements we need for the app
const taskList = document.getElementById('task-list');
const taskForm = document.getElementById('task-form');
const title = document.getElementById('title');
const hours = document.getElementById('deadline-hours');
const minutes = document.getElementById('deadline-minutes');
const day = document.getElementById('deadline-day');
const month = document.getElementById('deadline-month');
const year = document.getElementById('deadline-year');
const notificationBtn = document.getElementById('enable');
// Do an initial check to see what the notification permission state is
if (Notification.permission === 'denied' || Notification.permission === 'default') {
notificationBtn.style.display = 'block';
} else {
notificationBtn.style.display = 'none';
}
note.appendChild(createListItem('App initialised.'));
// Let us open our database
const DBOpenRequest = window.indexedDB.open('toDoList', 4);
// Register two event handlers to act on the database being opened successfully, or not
DBOpenRequest.onerror = (event) => {
note.appendChild(createListItem('Error loading database.'));
};
DBOpenRequest.onsuccess = (event) => {
note.appendChild(createListItem('Database initialised.'));
// Store the result of opening the database in the db variable. This is used a lot below
db = DBOpenRequest.result;
// Run the displayData() function to populate the task list with all the to-do list data already in the IndexedDB
displayData();
};
// This event handles the event whereby a new version of the database needs to be created
// Either one has not been created before, or a new version number has been submitted via the
// window.indexedDB.open line above
//it is only implemented in recent browsers
DBOpenRequest.onupgradeneeded = (event) => {
db = event.target.result;
db.onerror = (event) => {
note.appendChild(createListItem('Error loading database.'));
};
// Create an objectStore for this database
const objectStore = db.createObjectStore('toDoList', { keyPath: 'taskTitle' });
// Define what data items the objectStore will contain
objectStore.createIndex('hours', 'hours', { unique: false });
objectStore.createIndex('minutes', 'minutes', { unique: false });
objectStore.createIndex('day', 'day', { unique: false });
objectStore.createIndex('month', 'month', { unique: false });
objectStore.createIndex('year', 'year', { unique: false });
objectStore.createIndex('notified', 'notified', { unique: false });
note.appendChild(createListItem('Object store created.'));
};
function displayData() {
// First clear the content of the task list so that you don't get a huge long list of duplicate stuff each time
// the display is updated.
while (taskList.firstChild) {
taskList.removeChild(taskList.lastChild);
}
// Open our object store and then get a cursor list of all the different data items in the IDB to iterate through
const objectStore = db.transaction('toDoList').objectStore('toDoList');
objectStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
// Check if there are no (more) cursor items to iterate through
if (!cursor) {
// No more items to iterate through, we quit.
note.appendChild(createListItem('Entries all displayed.'));
return;
}
// Check which suffix the deadline day of the month needs
const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value;
const ordDay = ordinal(day);
// Build the to-do list entry and put it into the list item.
const toDoText = `${taskTitle} — ${hours}:${minutes}, ${month} ${ordDay} ${year}.`;
const listItem = createListItem(toDoText);
if (notified === 'yes') {
listItem.style.textDecoration = 'line-through';
listItem.style.color = 'rgba(255, 0, 0, 0.5)';
}
// Put the item item inside the task list
taskList.appendChild(listItem);
// Create a delete button inside each list item,
const deleteButton = document.createElement('button');
listItem.appendChild(deleteButton);
deleteButton.textContent = 'X';
// Set a data attribute on our delete button to associate the task it relates to.
deleteButton.setAttribute('data-task', taskTitle);
// Associate action (deletion) when clicked
deleteButton.onclick = (event) => {
deleteItem(event);
};
// continue on to the next item in the cursor
cursor.continue();
};
};
// Add listener for clicking the submit button
taskForm.addEventListener('submit', addData, false);
function addData(e) {
// Prevent default, as we don't want the form to submit in the conventional way
e.preventDefault();
// Stop the form submitting if any values are left empty.
// This should never happen as there is the required attribute
if (title.value === '' || hours.value === null || minutes.value === null || day.value === '' || month.value === '' || year.value === null) {
note.appendChild(createListItem('Data not submitted — form incomplete.'));
return;
}
// Grab the values entered into the form fields and store them in an object ready for being inserted into the IndexedDB
const newItem = [
{ taskTitle: title.value, hours: hours.value, minutes: minutes.value, day: day.value, month: month.value, year: year.value, notified: 'no' },
];
// Open a read/write DB transaction, ready for adding the data
const transaction = db.transaction(['toDoList'], 'readwrite');
// Report on the success of the transaction completing, when everything is done
transaction.oncomplete = () => {
note.appendChild(createListItem('Transaction completed: database modification finished.'));
// Update the display of data to show the newly added item, by running displayData() again.
displayData();
};
// Handler for any unexpected error
transaction.onerror = () => {
note.appendChild(createListItem(`Transaction not opened due to error: ${transaction.error}`));
};
// Call an object store that's already been added to the database
const objectStore = transaction.objectStore('toDoList');
console.log(objectStore.indexNames);
console.log(objectStore.keyPath);
console.log(objectStore.name);
console.log(objectStore.transaction);
console.log(objectStore.autoIncrement);
// Make a request to add our newItem object to the object store
const objectStoreRequest = objectStore.add(newItem[0]);
objectStoreRequest.onsuccess = (event) => {
// Report the success of our request
// (to detect whether it has been succesfully
// added to the database, you'd look at transaction.oncomplete)
note.appendChild(createListItem('Request successful.'));
// Clear the form, ready for adding the next entry
title.value = '';
hours.value = null;
minutes.value = null;
day.value = 01;
month.value = 'January';
year.value = 2020;
};
};
function deleteItem(event) {
// Retrieve the name of the task we want to delete
const dataTask = event.target.getAttribute('data-task');
// Open a database transaction and delete the task, finding it by the name we retrieved above
const transaction = db.transaction(['toDoList'], 'readwrite');
transaction.objectStore('toDoList').delete(dataTask);
// Report that the data item has been deleted
transaction.oncomplete = () => {
// Delete the parent of the button, which is the list item, so it is no longer displayed
event.target.parentNode.parentNode.removeChild(event.target.parentNode);
note.appendChild(createListItem(`Task "${dataTask}" deleted.`));
};
};
// Check whether the deadline for each task is up or not, and responds appropriately
function checkDeadlines() {
// First of all check whether notifications are enabled or denied
if (Notification.permission === 'denied' || Notification.permission === 'default') {
notificationBtn.style.display = 'block';
} else {
notificationBtn.style.display = 'none';
}
// Grab the current time and date
const now = new Date();
// From the now variable, store the current minutes, hours, day of the month, month, year and seconds
const minuteCheck = now.getMinutes();
const hourCheck = now.getHours();
const dayCheck = now.getDate(); // Do not use getDay() that returns the day of the week, 1 to 7
const monthCheck = now.getMonth();
const yearCheck = now.getFullYear(); // Do not use getYear() that is deprecated.
// Open a new transaction
const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList');
// Open a cursor to iterate through all the data items in the IndexedDB
objectStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (!cursor) return;
const { hours, minutes, day, month, year, notified, taskTitle } = cursor.value;
// convert the month names we have installed in the IDB into a month number that JavaScript will understand.
// The JavaScript date object creates month values as a number between 0 and 11.
const monthNumber = MONTHS.indexOf(month);
if (monthNumber === -1) throw new Error('Incorrect month entered in database.');
// Check if the current hours, minutes, day, month and year values match the stored values for each task.
// The parseInt() function transforms the value from a string to a number for comparison
// (taking care of leading zeros, and removing spaces and underscores from the string).
let matched = parseInt(hours) === hourCheck;
matched &&= parseInt(minutes) === minuteCheck;
matched &&= parseInt(day) === dayCheck;
matched &&= parseInt(monthNumber) === monthCheck;
matched &&= parseInt(year) === yearCheck;
if (matched && notified === 'no') {
// If the numbers all do match, run the createNotification() function to create a system notification
// but only if the permission is set
if (Notification.permission === 'granted') {
createNotification(taskTitle);
}
}
// Move on to the next cursor item
cursor.continue();
};
};
// Ask for permission when the 'Enable notifications' button is clicked
function askNotificationPermission() {
// Function to actually ask the permissions
function handlePermission(permission) {
// Whatever the user answers, we make sure Chrome stores the information
if (!Reflect.has(Notification, 'permission')) {
Notification.permission = permission;
}
// Set the button to shown or hidden, depending on what the user answers
if (Notification.permission === 'denied' || Notification.permission === 'default') {
notificationBtn.style.display = 'block';
} else {
notificationBtn.style.display = 'none';
}
};
// Check if the browser supports notifications
if (!Reflect.has(window, 'Notification')) {
console.log('This browser does not support notifications.');
} else {
if (checkNotificationPromise()) {
Notification.requestPermission().then(handlePermission);
} else {
Notification.requestPermission(handlePermission);
}
}
};
// Check whether browser supports the promise version of requestPermission()
// Safari only supports the old callback-based version
function checkNotificationPromise() {
try {
Notification.requestPermission().then();
} catch(e) {
return false;
}
return true;
};
// Wire up notification permission functionality to 'Enable notifications' button
notificationBtn.addEventListener('click', askNotificationPermission);
function createListItem(contents) {
const listItem = document.createElement('li');
listItem.textContent = contents;
return listItem;
};
// Create a notification with the given title
function createNotification(title) {
// Create and show the notification
const img = '/to-do-notifications/img/icon-128.png';
const text = `HEY! Your task "${title}" is now overdue.`;
const notification = new Notification('To do list', { body: text, icon: img });
// We need to update the value of notified to 'yes' in this particular data object, so the
// notification won't be set off on it again
// First open up a transaction
const objectStore = db.transaction(['toDoList'], 'readwrite').objectStore('toDoList');
// Get the to-do list object that has this title as its title
const objectStoreTitleRequest = objectStore.get(title);
objectStoreTitleRequest.onsuccess = () => {
// Grab the data object returned as the result
const data = objectStoreTitleRequest.result;
// Update the notified value in the object to 'yes'
data.notified = 'yes';
// Create another request that inserts the item back into the database
const updateTitleRequest = objectStore.put(data);
// When this new request succeeds, run the displayData() function again to update the display
updateTitleRequest.onsuccess = () => {
displayData();
};
};
};
// Using a setInterval to run the checkDeadlines() function every second
setInterval(checkDeadlines, 1000);
}
// Helper function returning the day of the month followed by an ordinal (st, nd, or rd)
function ordinal(day) {
const n = day.toString();
const last = n.slice(-1);
if (last === '1' && n !== '11') return `${n}st`;
if (last === '2' && n !== '12') return `${n}nd`;
if (last === '3' && n !== '13') return `${n}rd`;
return `${n}th`;
};
</script>
<title>To-do list with Notifications</title>
<!-- Icon originated from design by Sabine Wollender: http://thenounproject.com/wosamo/ -->
<link rel="icon" type="image/png" href="https://mdn.github.io/dom-examples/to-do-notifications/img/icon-128.png">
<style>
/* Basic set up + sizing for containers */
html,
body {
margin: 0;
}
html {
width: 100%;
height: 100%;
font-size: 10px;
font-family: Georgia, "Times New Roman", Times, serif;
background: #111;
}
body {
width: 50rem;
position: relative;
background: #d88;
margin: 0 auto;
border-left: 2px solid #d33;
border-right: 2px solid #d33;
}
h1,
h2 {
text-align: center;
background: #d88;
font-family: Arial, Helvetica, sans-serif;
}
h1 {
font-size: 6rem;
margin: 0;
background: #d66;
}
h2 {
font-size: 2.4rem;
}
/* Bottom toolbar styling */
#toolbar {
position: relative;
height: 6rem;
width: 100%;
background: #d66;
border-top: 2px solid #d33;
border-bottom: 2px solid #d33;
}
#enable,
input[type="submit"] {
line-height: 1.8;
font-size: 1.3rem;
border-radius: 5px;
border: 1px solid black;
color: black;
text-shadow: 1px 1px 1px black;
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: inset 0px 5px 3px rgba(255, 255, 255, 0.2),
inset 0px -5px 3px rgba(0, 0, 0, 0.2);
}
#enable {
position: absolute;
bottom: 0.3rem;
right: 0.3rem;
}
#notifications {
margin: 0;
position: relative;
padding: 0.3rem;
background: #ddd;
position: absolute;
top: 0rem;
left: 0rem;
height: 5.4rem;
width: 50%;
overflow: auto;
line-height: 1.2;
}
#notifications li {
margin-left: 1.5rem;
}
/* New item form styling */
.form-box {
background: #d66;
width: 85%;
padding: 1rem;
margin: 2rem auto;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7);
}
form div {
margin-bottom: 1rem;
}
form .full-width {
margin: 1rem auto 2rem;
width: 100%;
}
form .half-width {
width: 50%;
float: left;
}
form .third-width {
width: 33%;
float: left;
}
form div label {
width: 10rem;
float: left;
padding-right: 1rem;
font-size: 1.6rem;
line-height: 1.6;
}
form .full-width input {
width: 30rem;
}
form .half-width input {
width: 8.75rem;
}
form .third-width select {
width: 13.5rem;
}
form div input[type="submit"] {
clear: both;
width: 20rem;
display: block;
height: 3rem;
margin: 0 auto;
position: relative;
top: 0.5rem;
}
/* || tasks box */
.task-box {
width: 85%;
padding: 1rem;
margin: 2rem auto;
font-size: 1.8rem;
}
.task-box ul {
margin: 0;
padding: 0;
}
.task-box li {
list-style-type: none;
padding: 1rem;
border-bottom: 2px solid #d33;
}
.task-box li:last-child {
border-bottom: none;
}
.task-box li:last-child {
margin-bottom: 0rem;
}
.task-box button {
margin-left: 2rem;
font-size: 1.6rem;
border: 1px solid #eee;
border-radius: 5px;
box-shadow: inset 0 -2px 5px rgba(0, 0, 0, 0.5) 1px 1px 1px black;
}
/* setting cursor for interactive controls */
button,
input[type="submit"],
select {
cursor: pointer;
}
/* media query for small screens */
@media (max-width: 32rem) {
body {
width: 100%;
border-left: none;
border-right: none;
}
form div {
clear: both;
}
form .full-width {
margin: 1rem auto;
}
form .half-width {
width: 100%;
float: none;
}
form .third-width {
width: 100%;
float: none;
}
form div label {
width: 36%;
padding-left: 1rem;
}
form input,
form select,
form label {
line-height: 2.5rem;
font-size: 2rem;
}
form .full-width input {
width: 50%;
}
form .half-width input {
width: 50%;
}
form .third-width select {
width: 50%;
}
#enable {
right: 1rem;
}
}
</style>
</head>
<body>
<h1>To-do list</h1>
<div class="task-box">
<ul id="task-list"><li>rrrr — 12:00, June 09th 2023.<button data-task="rrrr">X</button></li></ul>
</div>
<div class="form-box">
<h2>Add new to-do item.</h2>
<form id="task-form" action="https://mdn.github.io/dom-examples/to-do-notifications/index.html">
<div class="full-width"><label for="title">Task title:</label><input type="text" id="title" required=""></div>
<div class="half-width"><label for="deadline-hours">Hours (hh):</label><input type="number" id="deadline-hours" required=""></div>
<div class="half-width"><label for="deadline-minutes">Mins (mm):</label><input type="number" id="deadline-minutes" required=""></div>
<div class="third-width"><label for="deadline-day">Day:</label>
<select id="deadline-day" required="">
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="08">08</option>
<option value="09">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="18">18</option>
<option value="19">19</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
<option value="28">28</option>
<option value="29">29</option>
<option value="30">30</option>
<option value="31">31</option>
</select></div>
<div class="third-width"><label for="deadline-month">Month:</label>
<select id="deadline-month" required="">
<option value="January">January</option>
<option value="February">February</option>
<option value="March">March</option>
<option value="April">April</option>
<option value="May">May</option>
<option value="June">June</option>
<option value="July">July</option>
<option value="August">August</option>
<option value="September">September</option>
<option value="October">October</option>
<option value="November">November</option>
<option value="December">December</option>
</select></div>
<div class="third-width"><label for="deadline-year">Year:</label>
<select id="deadline-year" required="">
<option value="2025">2025</option>
<option value="2024">2024</option>
<option value="2023">2023</option>
<option value="2022">2022</option>
<option value="2021">2021</option>
<option value="2020">2020</option>
<option value="2019">2019</option>
<option value="2018">2018</option>
</select></div>
<div><input type="submit" id="submit" value="Add Task"></div>
<div></div>
</form>
</div>
<div id="toolbar">
<ul id="notifications">
<li>App initialised.</li><li>Object store created.</li><li>Database initialised.</li><li>Entries all displayed.</li><li>Request successful.</li><li>Transaction completed: database modification finished.</li><li>Entries all displayed.</li></ul>
<button id="enable" style="display: block;">
Enable notifications
</button>
</div>
</body></html>