index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" type="image/png" href="images/favicon-16x16.png">
<title>Todo</title>
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/pure-min.css">
<link rel="stylesheet" href="styles/style.css"/>
<script type="module">
import { Todo } from "./modules/todo.js";
const _todo = new Todo();
</script>
</head>
<body>
<main>
<section title="Today" class="todoContainer">
<header>
<h1>Today</h1>
<img class="icon" title="Add Item" width="36" height="36" src="images/plus.png"/>
</header><div class="msg"></div>
<ul></ul>
</section>
<section title="Tomorrow" class="todoContainer">
<header>
<h1>Tomorrow</h1>
<img class="icon" title="Add Item" width="36" height="36" src="images/plus.png"/>
</header>
<div class="msg"></div>
<ul></ul>
</section>
<section title="Future" class="todoContainer">
<header>
<h1>Future</h1>
<img class="icon" title="Add Item" width="36" height="36" src="images/plus.png"/>
</header>
<div class="msg"></div>
<ul>
<li>
<span>Test this out it is even longer...</span>
<img class="icon" width="36" height="36" src="images/minus.png"/>
<img class="icon" width="36" height="36" src="images/mom.png"/>
</li>
</ul>
</section>
</main>
</body>
</html>
todo.js
class Todo {
constructor() {
this.NO_ELEMENT_FOUND = true;
this.NO_CHILDREN = true;
/* A list of the main containers we have in the application */
this.targets = ['Today','Tomorrow','Future'];
window.addEventListener("DOMContentLoaded", (event) => {
// Setup the onclick callbacks for each Todo list 'Add' button.
this.targets.forEach((target) => {
const iconElem = document.querySelector(`section[title='${target}'] img[title='Add Item']`);
iconElem.addEventListener("click", event => {
this.addTodoItem(target);
});
});
});
}
/* Each target Todo list has a message div that you can display something into.
* Use this method to do only that.
*
* @target The Todo section DOM element to load
* @message The message you want to display
*/
updateMessageOn(target, message = "") {
const ele = document.querySelector(`section[title='${target}'] > header + div.msg`);
if ( !ele ) {
console.error(`${MESSAGES.en_US.NO_ELEMENT_FOUND} on ${target} for updateMessageOn(target)`);
}
ele.innerHTML = message;
}
/* Gets the main section you want ('Today', "Tomorrow" or "Future")
*
* @returns NULL if the target cannot be found in the DOM
*/
getTodoContainerElement(target = "") {
const todoSectionEle = document.querySelector(`section[title='${target}']`);
if ( !todoSectionEle ) {
console.error(`${MESSAGES.en_US.NO_ELEMENT_FOUND} on ${target} for getTodoContainerElement(target)`);
return null;
}
return todoSectionEle;
}
/* Get the unordered list element within your target Todo.
*
* @target The Todo section DOM element to load
* @return A unordered list element, or NULL if not found
*/
getTodoListFor(target) {
const listEle = document.querySelector(`section[title='${target}'] ul`);
if ( !listEle ) {
console.error(`${MESSAGES.en_US.NO_ELEMENT_FOUND} on ${target} for getTodoListFor(target)`);
return null;
}
return listEle;
}
/* Adds a new Todo item, if there isn't pending new one's going on right now.
*
* @target The Todo section DOM element to load
*/
addTodoItem(target) {
if ( this.doesTargetTodoHaveUnsavedItem(target) ) {
this.updateMessageOn(target, "Finish the previous one you created first...");
window.setTimeout( () => {
this.updateMessageOn(target);
}, 1400);
return;
}
const newHtml =
`<li unsaved="true" id="${target}_${Date.now()}>
<textarea placeholder="Just tab out to save" class="todoItem" onchange="_todo.save(${target}/></textarea>
<img class="icon" width="20" height="20" src="images/save_36x36.png"/>
</li>`;
this.getTodoListFor(target).innerHTML += newHtml;
}
/* Utility method to look for any unsaved list items in a target Todo list.
*
* @target The Todo section DOM element to load
* @return false - if there are no unsaved list items; true here are unsaved items...
*/
doesTargetTodoHaveUnsavedItem(target) {
let hasSavedItem = false;
/** See [url]https://bit.ly/3hsgFbo[/url] for HTMLCollection. It is not an Array by default.. */
let coll = Array.from(this.getTodoListFor(target).children);
coll.forEach((child) => {
if ( child.getAttribute("unsaved")) {
hasSavedItem = true;
}
});
return hasSavedItem;
}
/* Checks the containers (Today, Tomorrow & Future). */
/* They should always exist. If not returns FALSE. */
/* They could be empty. That is expected. Returns FALSE */
/* Only returns TRUE when element exists and has kids. */
isListEmpty(target = "Today") {
const targetList =
document.querySelector(`[title ^= '${target}']`);
if ( !targetElement ) {
console.error(`Expected parent node (today, tomorrow, future). It is not in the DOM.`);
return this.NO_ELEMENT_FOUND;
}
const children = targetList.children;
if ( !children || children.length === 0 ) {
return this.NO_CHILDREN;
}
return true;
}
addActions(target) {
if ( !target) {
console.error("Cannot add actions on NULL target");
return;
}
}
}
const MESSAGES = {
en_US : {
NO_ELEMENT_FOUND: "The expected DOM element was not found.",
NO_CHILDREN: ""
}
}
export { Todo };