Remove es6 todomvc (#283)
Since the es6-webpack workload doesn't transpile down to any earlier versions of EcmaScript, testing two todomvc workloads with similar logic is redundant.
This commit is contained in:
parent
f02d7e4b64
commit
36712deb57
23 changed files with 1 additions and 3233 deletions
|
|
@ -86,36 +86,6 @@ Suites.push({
|
|||
],
|
||||
});
|
||||
|
||||
Suites.push({
|
||||
name: "TodoMVC-JavaScript-ES6",
|
||||
url: "todomvc/vanilla-examples/javascript-es6/dist/index.html",
|
||||
tags: ["todomvc"],
|
||||
async prepare(page) {
|
||||
const element = await page.waitForElement(".new-todo");
|
||||
element.focus();
|
||||
},
|
||||
tests: [
|
||||
new BenchmarkTestStep(`Adding${numberOfItemsToAdd}Items`, (page) => {
|
||||
const newTodo = page.querySelector(".new-todo");
|
||||
for (let i = 0; i < numberOfItemsToAdd; i++) {
|
||||
newTodo.setValue(getTodoText("ru", i));
|
||||
newTodo.dispatchEvent("change");
|
||||
newTodo.enter("keypress");
|
||||
}
|
||||
}),
|
||||
new BenchmarkTestStep("CompletingAllItems", (page) => {
|
||||
const checkboxes = page.querySelectorAll(".toggle");
|
||||
for (let i = 0; i < numberOfItemsToAdd; i++)
|
||||
checkboxes[i].click();
|
||||
}),
|
||||
new BenchmarkTestStep("DeletingAllItems", (page) => {
|
||||
const deleteButtons = page.querySelectorAll(".destroy");
|
||||
for (let i = numberOfItemsToAdd - 1; i >= 0; i--)
|
||||
deleteButtons[i].click();
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Suites.push({
|
||||
name: "TodoMVC-JavaScript-ES6-Webpack",
|
||||
url: "todomvc/vanilla-examples/javascript-es6-webpack/dist/index.html",
|
||||
|
|
@ -128,7 +98,7 @@ Suites.push({
|
|||
new BenchmarkTestStep(`Adding${numberOfItemsToAdd}Items`, (page) => {
|
||||
const newTodo = page.querySelector(".new-todo");
|
||||
for (let i = 0; i < numberOfItemsToAdd; i++) {
|
||||
newTodo.setValue(getTodoText(defaultLanguage, i));
|
||||
newTodo.setValue(getTodoText("ru", i));
|
||||
newTodo.dispatchEvent("change");
|
||||
newTodo.enter("keypress");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
# Speedometer 3.0: TodoMVC: JavaScript Es6
|
||||
|
||||
## Description
|
||||
|
||||
This application uses JavaScript with ES6 language features to implement a todo application.
|
||||
|
||||
JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions. While it is most well-known as the scripting language for Web pages, many non-browser environments also use it..
|
||||
|
||||
[JavaScript - developer.mozilla.org](http://developer.mozilla.org/en-US/docs/JavaScript)
|
||||
|
||||
## Implementation details
|
||||
|
||||
This implementation uses an explicit MVC pattern, with a clear file structure to reflect the architecture. The storage solution uses an in-memory data object that implements a simple array to hold the todos.
|
||||
|
||||
## Built steps
|
||||
|
||||
A simple build script copies all necessary files to a `dist` folder.
|
||||
It does not rely on compilers or transpilers and serves raw html, css and js files to the user.
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
The only requirement is an installation of Node, to be able to install dependencies and run scripts to serve a local server.
|
||||
|
||||
```
|
||||
* Node (min version: 18.13.0)
|
||||
* NPM (min version: 8.19.3)
|
||||
```
|
||||
|
||||
## Local preview
|
||||
|
||||
```
|
||||
terminal:
|
||||
1. npm install
|
||||
2. npm run dev
|
||||
|
||||
browser:
|
||||
1. http://localhost:7001/
|
||||
```
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/* eslint no-undef: 0 */
|
||||
"use strict";
|
||||
|
||||
let todo;
|
||||
const setView = () => todo.controller.setView(document.location.hash);
|
||||
|
||||
class Todo {
|
||||
/**
|
||||
* Init new Todo List
|
||||
* @param {string} The name of your list
|
||||
*/
|
||||
constructor(name) {
|
||||
this.storage = new Store(name);
|
||||
this.model = new Model(this.storage);
|
||||
|
||||
this.template = new Template();
|
||||
this.view = new View(this.template);
|
||||
|
||||
this.controller = new Controller(this.model, this.view);
|
||||
}
|
||||
}
|
||||
|
||||
$on(window, "load", () => {
|
||||
todo = new Todo("javascript-es6");
|
||||
setView();
|
||||
});
|
||||
|
||||
$on(window, "hashchange", setView);
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
hr {
|
||||
margin: 20px 0;
|
||||
border: 0;
|
||||
border-top: 1px dashed #c5c5c5;
|
||||
border-bottom: 1px dashed #f7f7f7;
|
||||
}
|
||||
|
||||
.learn a {
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: #b83f45;
|
||||
}
|
||||
|
||||
.learn a:hover {
|
||||
text-decoration: underline;
|
||||
color: #787e7e;
|
||||
}
|
||||
|
||||
.learn h3,
|
||||
.learn h4,
|
||||
.learn h5 {
|
||||
margin: 10px 0;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.learn h3 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.learn h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.learn h5 {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.learn ul {
|
||||
padding: 0;
|
||||
margin: 0 0 30px 25px;
|
||||
}
|
||||
|
||||
.learn li {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.learn p {
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
line-height: 1.3;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#issue-count {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quote {
|
||||
border: none;
|
||||
margin: 20px 0 60px 0;
|
||||
}
|
||||
|
||||
.quote p {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.quote p:before {
|
||||
content: '“';
|
||||
font-size: 50px;
|
||||
opacity: .15;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.quote p:after {
|
||||
content: '”';
|
||||
font-size: 50px;
|
||||
opacity: .15;
|
||||
position: absolute;
|
||||
bottom: -42px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.quote footer {
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.quote footer img {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.quote footer a {
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.speech-bubble {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, .04);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.speech-bubble:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 30px;
|
||||
border: 13px solid transparent;
|
||||
border-top-color: rgba(0, 0, 0, .04);
|
||||
}
|
||||
|
||||
.learn-bar > .learn {
|
||||
position: absolute;
|
||||
width: 272px;
|
||||
top: 8px;
|
||||
left: -300px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: rgba(255, 255, 255, .6);
|
||||
transition-property: left;
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
|
||||
@media (min-width: 899px) {
|
||||
.learn-bar {
|
||||
width: auto;
|
||||
padding-left: 300px;
|
||||
}
|
||||
|
||||
.learn-bar > .learn {
|
||||
left: 8px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
class Controller {
|
||||
/**
|
||||
* Take a model & view, then act as controller between them
|
||||
* @param {object} model The model instance
|
||||
* @param {object} view The view instance
|
||||
*/
|
||||
constructor(model, view) {
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
|
||||
this.view.bind("newTodo", (title) => this.addItem(title));
|
||||
this.view.bind("itemEdit", (item) => this.editItem(item.id));
|
||||
this.view.bind("itemEditDone", (item) => this.editItemSave(item.id, item.title));
|
||||
this.view.bind("itemEditCancel", (item) => this.editItemCancel(item.id));
|
||||
this.view.bind("itemRemove", (item) => this.removeItem(item.id));
|
||||
this.view.bind("itemToggle", (item) => this.toggleComplete(item.id, item.completed));
|
||||
this.view.bind("removeCompleted", () => this.removeCompletedItems());
|
||||
this.view.bind("toggleAll", (status) => this.toggleAll(status.completed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load & Initialize the view
|
||||
* @param {string} '' | 'active' | 'completed'
|
||||
*/
|
||||
setView(hash) {
|
||||
let route = hash.split("/")[1];
|
||||
let page = route || "";
|
||||
this._updateFilter(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fires on load. Gets all items & displays them
|
||||
*/
|
||||
showAll() {
|
||||
this.model.read((data) => this.view.render("showEntries", data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all active tasks
|
||||
*/
|
||||
showActive() {
|
||||
this.model.read({ completed: false }, (data) => this.view.render("showEntries", data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all completed tasks
|
||||
*/
|
||||
showCompleted() {
|
||||
this.model.read({ completed: true }, (data) => this.view.render("showEntries", data));
|
||||
}
|
||||
|
||||
/**
|
||||
* An event to fire whenever you want to add an item. Simply pass in the event
|
||||
* object and it'll handle the DOM insertion and saving of the new item.
|
||||
*/
|
||||
addItem(title) {
|
||||
if (title.trim() === "")
|
||||
return;
|
||||
|
||||
this.model.create(title, () => {
|
||||
this.view.render("clearNewTodo");
|
||||
this._filter(true);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Triggers the item editing mode.
|
||||
*/
|
||||
editItem(id) {
|
||||
this.model.read(id, (data) => {
|
||||
let title = data[0].title;
|
||||
this.view.render("editItem", { id, title });
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Finishes the item editing mode successfully.
|
||||
*/
|
||||
editItemSave(id, title) {
|
||||
title = title.trim();
|
||||
|
||||
if (title.length !== 0) {
|
||||
this.model.update(id, { title }, () => {
|
||||
this.view.render("editItemDone", { id, title });
|
||||
});
|
||||
} else {
|
||||
this.removeItem(id);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cancels the item editing mode.
|
||||
*/
|
||||
editItemCancel(id) {
|
||||
this.model.read(id, (data) => {
|
||||
let title = data[0].title;
|
||||
this.view.render("editItemDone", { id, title });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the DOM element with given ID,
|
||||
* Then remove it from DOM & Storage
|
||||
*/
|
||||
removeItem(id) {
|
||||
this.model.remove(id, () => this.view.render("removeItem", id));
|
||||
this._filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove all completed items from the DOM and storage.
|
||||
*/
|
||||
removeCompletedItems() {
|
||||
this.model.read({ completed: true }, (data) => {
|
||||
for (let item of data)
|
||||
this.removeItem(item.id);
|
||||
});
|
||||
|
||||
this._filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Give it an ID of a model and a checkbox and it will update the item
|
||||
* in storage based on the checkbox's state.
|
||||
*
|
||||
* @param {number} id The ID of the element to complete or uncomplete
|
||||
* @param {object} checkbox The checkbox to check the state of complete
|
||||
* or not
|
||||
* @param {boolean|undefined} silent Prevent re-filtering the todo items
|
||||
*/
|
||||
toggleComplete(id, completed, silent) {
|
||||
this.model.update(id, { completed }, () => {
|
||||
this.view.render("elementComplete", { id, completed });
|
||||
});
|
||||
|
||||
if (!silent)
|
||||
this._filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will toggle ALL checkboxes' on/off state and completeness of models.
|
||||
* Just pass in the event object.
|
||||
*/
|
||||
toggleAll(completed) {
|
||||
this.model.read({ completed: !completed }, (data) => {
|
||||
for (let item of data)
|
||||
this.toggleComplete(item.id, completed, true);
|
||||
});
|
||||
|
||||
this._filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the pieces of the page which change depending on the remaining
|
||||
* number of todos.
|
||||
*/
|
||||
_updateCount() {
|
||||
this.model.getCount((todos) => {
|
||||
const completed = todos.completed;
|
||||
const visible = completed > 0;
|
||||
const checked = completed === todos.total;
|
||||
|
||||
this.view.render("updateElementCount", todos.active);
|
||||
this.view.render("clearCompletedButton", { completed, visible });
|
||||
|
||||
this.view.render("toggleAll", { checked });
|
||||
this.view.render("contentBlockVisibility", { visible: todos.total > 0 });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-filters the todo items, based on the active route.
|
||||
* @param {boolean|undefined} force forces a re-painting of todo items.
|
||||
*/
|
||||
_filter(force) {
|
||||
let active = this._activeRoute;
|
||||
const activeRoute = active.charAt(0).toUpperCase() + active.substr(1);
|
||||
|
||||
// Update the elements on the page, which change with each completed todo
|
||||
this._updateCount();
|
||||
|
||||
// If the last active route isn't "All", or we're switching routes, we
|
||||
// re-create the todo item elements, calling:
|
||||
// this.show[All|Active|Completed]()
|
||||
if (force || this._lastActiveRoute !== "All" || this._lastActiveRoute !== activeRoute)
|
||||
this[`show${activeRoute}`]();
|
||||
|
||||
this._lastActiveRoute = activeRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply updates the filter nav's selected states
|
||||
*/
|
||||
_updateFilter(currentPage) {
|
||||
// Store a reference to the active route, allowing us to re-filter todo
|
||||
// items as they are marked complete or incomplete.
|
||||
this._activeRoute = currentPage;
|
||||
|
||||
if (currentPage === "")
|
||||
this._activeRoute = "All";
|
||||
|
||||
this._filter();
|
||||
|
||||
this.view.render("setFilter", currentPage);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
// Allow for looping on nodes by chaining:
|
||||
// qsa('.foo').forEach(function () {})
|
||||
NodeList.prototype.forEach = Array.prototype.forEach;
|
||||
|
||||
// Get element(s) by CSS selector:
|
||||
function qs(selector, scope) {
|
||||
return (scope || document).querySelector(selector);
|
||||
}
|
||||
|
||||
function qsa(selector, scope) {
|
||||
return (scope || document).querySelectorAll(selector);
|
||||
}
|
||||
|
||||
// addEventListener wrapper:
|
||||
function $on(target, type, callback, useCapture) {
|
||||
target.addEventListener(type, callback, !!useCapture);
|
||||
}
|
||||
|
||||
// Attach a handler to event for all elements that match the selector,
|
||||
// now or in the future, based on a root element
|
||||
function $delegate(target, selector, type, handler) {
|
||||
let dispatchEvent = (event) => {
|
||||
const targetElement = event.target;
|
||||
const potentialElements = qsa(selector, target);
|
||||
const hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0;
|
||||
|
||||
if (hasMatch)
|
||||
handler.call(targetElement, event);
|
||||
};
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Events/blur
|
||||
const useCapture = type === "blur" || type === "focus";
|
||||
|
||||
$on(target, type, dispatchEvent, useCapture);
|
||||
}
|
||||
|
||||
// Find the element's parent with the given tag name:
|
||||
// $parent(qs('a'), 'div')
|
||||
function $parent(element, tagName) {
|
||||
if (!element.parentNode)
|
||||
return undefined;
|
||||
|
||||
if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase())
|
||||
return element.parentNode;
|
||||
|
||||
return $parent(element.parentNode, tagName);
|
||||
}
|
||||
|
|
@ -1,393 +0,0 @@
|
|||
@charset "utf-8";
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4em;
|
||||
background: #f5f5f5;
|
||||
color: #111111;
|
||||
min-width: 230px;
|
||||
max-width: 550px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todoapp {
|
||||
background: #fff;
|
||||
margin: 130px 0 40px 0;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
||||
0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.todoapp input::-webkit-input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.todoapp input::-moz-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.todoapp input::input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.todoapp h1 {
|
||||
position: absolute;
|
||||
top: -140px;
|
||||
width: 100%;
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
text-align: center;
|
||||
color: #b83f45;
|
||||
-webkit-text-rendering: optimizeLegibility;
|
||||
-moz-text-rendering: optimizeLegibility;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.new-todo,
|
||||
.edit {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: 1.4em;
|
||||
color: inherit;
|
||||
padding: 6px;
|
||||
border: 1px solid #999;
|
||||
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.new-todo {
|
||||
padding: 16px 16px 16px 60px;
|
||||
height: 65px;
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.003);
|
||||
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
border: none; /* Mobile Safari */
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
bottom: 100%;
|
||||
}
|
||||
|
||||
.toggle-all + label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 45px;
|
||||
height: 65px;
|
||||
font-size: 0;
|
||||
position: absolute;
|
||||
top: -65px;
|
||||
left: -0;
|
||||
}
|
||||
|
||||
.toggle-all + label:before {
|
||||
content: '❯';
|
||||
display: inline-block;
|
||||
font-size: 22px;
|
||||
color: #949494;
|
||||
padding: 10px 27px 10px 27px;
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.toggle-all:checked + label:before {
|
||||
color: #484848;
|
||||
}
|
||||
|
||||
.todo-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.todo-list li {
|
||||
position: relative;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.todo-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing {
|
||||
border-bottom: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.todo-list li.editing .edit {
|
||||
display: block;
|
||||
width: calc(100% - 43px);
|
||||
padding: 12px 16px;
|
||||
margin: 0 0 0 43px;
|
||||
}
|
||||
|
||||
.todo-list li.editing .view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
border: none; /* Mobile Safari */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.todo-list li .toggle + label {
|
||||
/*
|
||||
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
|
||||
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
|
||||
*/
|
||||
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center left;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:checked + label {
|
||||
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E');
|
||||
}
|
||||
|
||||
.todo-list li label {
|
||||
word-break: break-all;
|
||||
padding: 15px 15px 15px 60px;
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
transition: color 0.4s;
|
||||
font-weight: 400;
|
||||
color: #484848;
|
||||
}
|
||||
|
||||
.todo-list li.completed label {
|
||||
color: #949494;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.todo-list li .destroy {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: auto 0;
|
||||
font-size: 30px;
|
||||
color: #949494;
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:hover,
|
||||
.todo-list li .destroy:focus {
|
||||
color: #C18585;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:after {
|
||||
content: '×';
|
||||
display: block;
|
||||
height: 100%;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.todo-list li:hover .destroy {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.todo-list li .edit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing:last-child {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 10px 15px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.footer:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
|
||||
0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2),
|
||||
0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.todo-count {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.todo-count strong {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.filters li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.filters li a {
|
||||
color: inherit;
|
||||
margin: 3px;
|
||||
padding: 3px 7px;
|
||||
text-decoration: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.filters li a:hover {
|
||||
border-color: #DB7676;
|
||||
}
|
||||
|
||||
.filters li a.selected {
|
||||
border-color: #CE4646;
|
||||
}
|
||||
|
||||
.clear-completed,
|
||||
html .clear-completed:active {
|
||||
float: right;
|
||||
position: relative;
|
||||
line-height: 19px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clear-completed:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin: 65px auto 0;
|
||||
color: #4d4d4d;
|
||||
font-size: 11px;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info p {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.info a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/*
|
||||
Hack to remove background from Mobile Safari.
|
||||
Can't use it globally since it destroys checkboxes in Firefox
|
||||
*/
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
.toggle-all,
|
||||
.todo-list li .toggle {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.footer {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
:focus,
|
||||
.toggle:focus + label,
|
||||
.toggle-all:focus + label {
|
||||
box-shadow: 0 0 2px 2px #CF7D7D;
|
||||
outline: 0;
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" data-framework="javascript" data-version="es6" data-features="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="A TodoMVC workload app for Speedometer!" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>TodoMVC: JavaScript Es6</title>
|
||||
<link rel="stylesheet" href="base.css" />
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>todos</h1>
|
||||
<input class="new-todo" placeholder="What needs to be done?" autofocus />
|
||||
</header>
|
||||
<main class="main">
|
||||
<div class="toggle-all-container">
|
||||
<input class="toggle-all" type="checkbox">
|
||||
<label class="toggle-all-label" for="toggle-all">Mark all as complete</label>
|
||||
</div>
|
||||
<ul class="todo-list"></ul>
|
||||
</main>
|
||||
<footer class="footer">
|
||||
<span class="todo-count"></span>
|
||||
<ul class="filters">
|
||||
<li>
|
||||
<a href="#/" class="selected">All</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/active">Active</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/completed">Completed</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="clear-completed">Clear completed</button>
|
||||
</footer>
|
||||
</section>
|
||||
<footer class="info">
|
||||
<p>Click on input field to write your todo.</p>
|
||||
<p>At least two characters are needed to be a valid entry.</p>
|
||||
<p>Press 'enter' to add the todo.</p>
|
||||
<p>Double-click to edit a todo</p>
|
||||
</footer>
|
||||
<script src="helpers.js"></script>
|
||||
<script src="store.js"></script>
|
||||
<script src="model.js"></script>
|
||||
<script src="template.js"></script>
|
||||
<script src="view.js"></script>
|
||||
<script src="controller.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Creates a new Model instance and hooks up the storage.
|
||||
* @constructor
|
||||
* @param {object} storage A reference to the client side storage class
|
||||
*/
|
||||
class Model {
|
||||
constructor(storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new todo model
|
||||
*
|
||||
* @param {string} [title] The title of the task
|
||||
* @param {function} [callback] The callback to fire after the model is created
|
||||
*/
|
||||
create(title, callback) {
|
||||
title = title || "";
|
||||
|
||||
let newItem = {
|
||||
title: title.trim(),
|
||||
completed: false,
|
||||
};
|
||||
|
||||
this.storage.save(newItem, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns a model in storage. If no query is given it'll simply
|
||||
* return everything. If you pass in a string or number it'll look that up as
|
||||
* the ID of the model to find. Lastly, you can pass it an object to match
|
||||
* against.
|
||||
*
|
||||
* @param {string|number|object} [query] A query to match models against
|
||||
* @param {function} [callback] The callback to fire after the model is found
|
||||
*
|
||||
* @example
|
||||
* model.read(1, func) // Will find the model with an ID of 1
|
||||
* model.read('1') // Same as above
|
||||
* //Below will find a model with foo equalling bar and hello equalling world.
|
||||
* model.read({ foo: 'bar', hello: 'world' })
|
||||
*/
|
||||
read(query, callback) {
|
||||
const queryType = typeof query;
|
||||
|
||||
if (queryType === "function") {
|
||||
callback = query;
|
||||
this.storage.findAll(callback);
|
||||
} else if (queryType === "string" || queryType === "number") {
|
||||
query = parseInt(query, 10);
|
||||
this.storage.find({ id: query }, callback);
|
||||
} else {
|
||||
this.storage.find(query, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a model by giving it an ID, data to update, and a callback to fire when
|
||||
* the update is complete.
|
||||
*
|
||||
* @param {number} id The id of the model to update
|
||||
* @param {object} data The properties to update and their new value
|
||||
* @param {function} callback The callback to fire when the update is complete.
|
||||
*/
|
||||
update(id, data, callback) {
|
||||
this.storage.save(data, callback, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a model from storage
|
||||
*
|
||||
* @param {number} id The ID of the model to remove
|
||||
* @param {function} callback The callback to fire when the removal is complete.
|
||||
*/
|
||||
remove(id, callback) {
|
||||
this.storage.remove(id, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: Will remove ALL data from storage.
|
||||
*
|
||||
* @param {function} callback The callback to fire when the storage is wiped.
|
||||
*/
|
||||
removeAll(callback) {
|
||||
this.storage.drop(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a count of all todos
|
||||
*/
|
||||
getCount(callback) {
|
||||
let todos = {
|
||||
active: 0,
|
||||
completed: 0,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
this.storage.findAll((data) => {
|
||||
for (let todo of data) {
|
||||
if (todo.completed)
|
||||
todos.completed++;
|
||||
else
|
||||
todos.active++;
|
||||
|
||||
todos.total++;
|
||||
}
|
||||
|
||||
if (callback)
|
||||
callback(todos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
let uniqueID = 1;
|
||||
|
||||
/**
|
||||
* Creates a new client side storage object and will create an empty
|
||||
* collection if no collection already exists.
|
||||
*
|
||||
* @param {string} name The name of our DB we want to use
|
||||
* @param {function} callback Our fake DB uses callbacks because in
|
||||
* real life you probably would be making AJAX calls
|
||||
*/
|
||||
class Store {
|
||||
constructor(name, callback) {
|
||||
this._dbName = name;
|
||||
this.memoryStorage = this.memoryStorage || {};
|
||||
|
||||
if (!this.memoryStorage[name]) {
|
||||
let data = {
|
||||
todos: [],
|
||||
};
|
||||
|
||||
this.memoryStorage[name] = JSON.stringify(data);
|
||||
}
|
||||
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[name]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds items based on a query given as a JS object
|
||||
*
|
||||
* @param {object} query The query to match against (i.e. {foo: 'bar'})
|
||||
* @param {function} callback The callback to fire when the query has
|
||||
* completed running
|
||||
*
|
||||
* @example
|
||||
* db.find({foo: 'bar', hello: 'world'}, function (data) {
|
||||
* // data will return any items that have foo: bar and
|
||||
* // hello: world in their properties
|
||||
* })
|
||||
*/
|
||||
find(query, callback) {
|
||||
if (!callback)
|
||||
return;
|
||||
|
||||
let todos = JSON.parse(this.memoryStorage[this._dbName]).todos;
|
||||
|
||||
callback.call(
|
||||
this,
|
||||
todos.filter((todo) => {
|
||||
for (let q in query) {
|
||||
if (query[q] !== todo[q])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will retrieve all data from the collection
|
||||
*
|
||||
* @param {function} callback The callback to fire upon retrieving data
|
||||
*/
|
||||
findAll(callback) {
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[this._dbName]).todos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will save the given data to the DB. If no item exists it will create a new
|
||||
* item, otherwise it'll simply update an existing item's properties
|
||||
*
|
||||
* @param {object} updateData The data to save back into the DB
|
||||
* @param {function} callback The callback to fire after saving
|
||||
* @param {number} id An optional param to enter an ID of an item to update
|
||||
*/
|
||||
save(updateData, callback, id) {
|
||||
const data = JSON.parse(this.memoryStorage[this._dbName]);
|
||||
let todos = data.todos;
|
||||
const len = todos.length;
|
||||
|
||||
// If an ID was actually given, find the item and update each property
|
||||
if (id) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (todos[i].id === id) {
|
||||
for (let key in updateData)
|
||||
todos[i][key] = updateData[key];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.memoryStorage[this._dbName] = JSON.stringify(data);
|
||||
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[this._dbName]).todos);
|
||||
} else {
|
||||
// Generate an ID
|
||||
updateData.id = uniqueID++;
|
||||
|
||||
todos.push(updateData);
|
||||
this.memoryStorage[this._dbName] = JSON.stringify(data);
|
||||
|
||||
if (callback)
|
||||
callback.call(this, [updateData]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove an item from the Store based on its ID
|
||||
*
|
||||
* @param {number} id The ID of the item you want to remove
|
||||
* @param {function} callback The callback to fire after saving
|
||||
*/
|
||||
remove(id, callback) {
|
||||
const data = JSON.parse(this.memoryStorage[this._dbName]);
|
||||
let todos = data.todos;
|
||||
|
||||
for (let i = 0; i < todos.length; i++) {
|
||||
if (todos[i].id === id) {
|
||||
todos.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.memoryStorage[this._dbName] = JSON.stringify(data);
|
||||
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[this._dbName]).todos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will drop all storage and start fresh
|
||||
*
|
||||
* @param {function} callback The callback to fire after dropping the data
|
||||
*/
|
||||
drop(callback) {
|
||||
this.memoryStorage[this._dbName] = JSON.stringify({ todos: [] });
|
||||
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[this._dbName]).todos);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
const htmlEscapes = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`",
|
||||
};
|
||||
|
||||
const reUnescapedHtml = /[&<>"'`]/g;
|
||||
const reHasUnescapedHtml = new RegExp(reUnescapedHtml.source);
|
||||
|
||||
let escape = (str) => str && reHasUnescapedHtml.test(str) ? str.replace(reUnescapedHtml, escapeHtmlChar) : str;
|
||||
let escapeHtmlChar = (chr) => htmlEscapes[chr];
|
||||
|
||||
class Template {
|
||||
constructor() {
|
||||
this.defaultTemplate = `
|
||||
<li data-id="{{id}}" class="targeted li-{{list-index}} {{completed}}">
|
||||
<div class="targeted view-{{view-index}}">
|
||||
<input class="toggle" type="checkbox" {{checked}}>
|
||||
<label>{{title}}</label>
|
||||
<button class="destroy"></button>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an <li> HTML string and returns it for placement in your app.
|
||||
*
|
||||
* NOTE: In real life you should be using a templating engine such as Mustache
|
||||
* or Handlebars, however, this is a vanilla JS example.
|
||||
*
|
||||
* @param {object} data The object containing keys you want to find in the
|
||||
* template to replace.
|
||||
* @returns {string} HTML String of an <li> element
|
||||
*
|
||||
* @example
|
||||
* view.show({
|
||||
* id: 1,
|
||||
* title: "Hello World",
|
||||
* completed: 0,
|
||||
* })
|
||||
*/
|
||||
show(data) {
|
||||
let i = 0;
|
||||
let view = "";
|
||||
const len = data.length;
|
||||
|
||||
for (i; i < len; i++) {
|
||||
let completed = "";
|
||||
let checked = "";
|
||||
let template = this.defaultTemplate;
|
||||
|
||||
if (data[i].completed) {
|
||||
completed = "completed";
|
||||
checked = "checked";
|
||||
}
|
||||
|
||||
template = template.replace("{{id}}", data[i].id);
|
||||
template = template.replace("{{title}}", escape(data[i].title));
|
||||
template = template.replace("{{completed}}", completed);
|
||||
template = template.replace("{{checked}}", checked);
|
||||
template = template.replace("{{list-index}}", i);
|
||||
template = template.replace("{{view-index}}", i);
|
||||
|
||||
view += template;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a counter of how many to dos are left to complete
|
||||
*
|
||||
* @param {number} activeTodos The number of active todos.
|
||||
* @returns {string} String containing the count
|
||||
*/
|
||||
itemCounter(activeTodos) {
|
||||
let plural = activeTodos === 1 ? "" : "s";
|
||||
return `<strong>${activeTodos}</strong> item${plural} left`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the text within the "Clear completed" button
|
||||
*
|
||||
* @param {[type]} completedTodos The number of completed todos.
|
||||
* @returns {string} String containing the count
|
||||
*/
|
||||
clearCompletedButton(completedTodos) {
|
||||
return completedTodos > 0 ? "Clear completed" : "";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
// import {qs, qsa, $on, $parent, $delegate} from './helpers';
|
||||
|
||||
let _itemId = (element) => parseInt($parent(element, "li").dataset.id, 10);
|
||||
|
||||
let _setFilter = (currentPage) => {
|
||||
qs(".filters .selected").className = "";
|
||||
qs(`.filters [href="#/${currentPage}"]`).className = "selected";
|
||||
};
|
||||
|
||||
let _elementComplete = (id, completed) => {
|
||||
let listItem = qs(`[data-id="${id}"]`);
|
||||
|
||||
if (!listItem)
|
||||
return;
|
||||
|
||||
listItem.className = completed ? "completed" : "";
|
||||
|
||||
// In case it was toggled from an event and not by clicking the checkbox
|
||||
qs("input", listItem).checked = completed;
|
||||
};
|
||||
|
||||
let _editItem = (id, title) => {
|
||||
let listItem = qs(`[data-id="${id}"]`);
|
||||
|
||||
if (!listItem)
|
||||
return;
|
||||
|
||||
listItem.className += " editing";
|
||||
|
||||
let input = document.createElement("input");
|
||||
input.className = "edit";
|
||||
|
||||
listItem.appendChild(input);
|
||||
input.focus();
|
||||
input.value = title;
|
||||
};
|
||||
|
||||
/**
|
||||
* View that abstracts away the browser's DOM completely.
|
||||
* It has two simple entry points:
|
||||
*
|
||||
* - bind(eventName, handler)
|
||||
* Takes a todo application event and registers the handler
|
||||
* - render(command, parameterObject)
|
||||
* Renders the given command with the options
|
||||
*/
|
||||
/* eslint no-undef: 0 */
|
||||
/* eslint no-unused-vars: 0 */
|
||||
/* eslint no-return-assign: 0 */
|
||||
class View {
|
||||
constructor(template) {
|
||||
this.template = template;
|
||||
|
||||
this.ENTER_KEY = 13;
|
||||
this.ESCAPE_KEY = 27;
|
||||
|
||||
this.$todoList = qs(".todo-list");
|
||||
this.$todoItemCounter = qs(".todo-count");
|
||||
this.$clearCompleted = qs(".clear-completed");
|
||||
this.$main = qs(".main");
|
||||
this.$footer = qs(".footer");
|
||||
this.$toggleAll = qs(".toggle-all-label");
|
||||
this.$toggleAllInput = qs(".toggle-all");
|
||||
this.$newTodo = qs(".new-todo");
|
||||
|
||||
this.viewCommands = {
|
||||
showEntries: (parameter) => this.$todoList.innerHTML = this.template.show(parameter),
|
||||
removeItem: (parameter) => this._removeItem(parameter),
|
||||
updateElementCount: (parameter) => this.$todoItemCounter.innerHTML = this.template.itemCounter(parameter),
|
||||
clearCompletedButton: (parameter) => this._clearCompletedButton(parameter.completed, parameter.visible),
|
||||
contentBlockVisibility: (parameter) => this.$main.style.display = this.$footer.style.display = parameter.visible ? "block" : "none",
|
||||
toggleAll: (parameter) => this.$toggleAll.checked = parameter.checked,
|
||||
setFilter: (parameter) => _setFilter(parameter),
|
||||
clearNewTodo: (parameter) => this.$newTodo.value = "",
|
||||
elementComplete: (parameter) => _elementComplete(parameter.id, parameter.completed),
|
||||
editItem: (parameter) => _editItem(parameter.id, parameter.title),
|
||||
editItemDone: (parameter) => this._editItemDone(parameter.id, parameter.title),
|
||||
};
|
||||
}
|
||||
|
||||
_removeItem(id) {
|
||||
let elem = qs(`[data-id="${id}"]`);
|
||||
|
||||
if (elem)
|
||||
this.$todoList.removeChild(elem);
|
||||
}
|
||||
|
||||
_clearCompletedButton(completedCount, visible) {
|
||||
this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount);
|
||||
this.$clearCompleted.style.display = visible ? "block" : "none";
|
||||
}
|
||||
|
||||
_editItemDone(id, title) {
|
||||
let listItem = qs(`[data-id="${id}"]`);
|
||||
|
||||
if (!listItem)
|
||||
return;
|
||||
|
||||
let input = qs("input.edit", listItem);
|
||||
listItem.removeChild(input);
|
||||
|
||||
listItem.className = listItem.className.replace(" editing", "");
|
||||
|
||||
qsa("label", listItem).forEach((label) => label.textContent = title);
|
||||
}
|
||||
|
||||
render(viewCmd, parameter) {
|
||||
this.viewCommands[viewCmd](parameter);
|
||||
}
|
||||
|
||||
_bindItemEditDone(handler) {
|
||||
let self = this;
|
||||
|
||||
$delegate(self.$todoList, "li .edit", "blur", function () {
|
||||
if (!this.dataset.iscanceled) {
|
||||
handler({
|
||||
id: _itemId(this),
|
||||
title: this.value,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the cursor from the input when you hit enter just like if it were a real form
|
||||
$delegate(self.$todoList, "li .edit", "keypress", function (event) {
|
||||
if (event.keyCode === self.ENTER_KEY)
|
||||
this.blur();
|
||||
});
|
||||
}
|
||||
|
||||
_bindItemEditCancel(handler) {
|
||||
let self = this;
|
||||
|
||||
$delegate(self.$todoList, "li .edit", "keyup", function (event) {
|
||||
if (event.keyCode === self.ESCAPE_KEY) {
|
||||
let id = _itemId(this);
|
||||
this.dataset.iscanceled = true;
|
||||
this.blur();
|
||||
|
||||
handler({ id });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bind(event, handler) {
|
||||
var self = this;
|
||||
if (event === "newTodo") {
|
||||
$on(this.$newTodo, "change", () => handler(this.$newTodo.value));
|
||||
} else if (event === "removeCompleted") {
|
||||
$on(this.$clearCompleted, "click", handler);
|
||||
} else if (event === "toggleAll") {
|
||||
$on(this.$toggleAll, "click", function () {
|
||||
self.$toggleAllInput.click();
|
||||
handler({ completed: self.$toggleAllInput.checked });
|
||||
});
|
||||
} else if (event === "itemEdit") {
|
||||
$delegate(this.$todoList, "li label", "dblclick", function () {
|
||||
handler({ id: _itemId(this) });
|
||||
});
|
||||
} else if (event === "itemRemove") {
|
||||
$delegate(this.$todoList, ".destroy", "click", function () {
|
||||
handler({ id: _itemId(this) });
|
||||
});
|
||||
} else if (event === "itemToggle") {
|
||||
$delegate(this.$todoList, ".toggle", "click", function () {
|
||||
handler({
|
||||
id: _itemId(this),
|
||||
completed: this.checked,
|
||||
});
|
||||
});
|
||||
} else if (event === "itemEditDone") {
|
||||
this._bindItemEditDone(handler);
|
||||
} else if (event === "itemEditCancel") {
|
||||
this._bindItemEditCancel(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" data-framework="javascript" data-version="es6" data-features="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="A TodoMVC workload app for Speedometer!" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>TodoMVC: JavaScript Es6</title>
|
||||
<link rel="stylesheet" href="node_modules/todomvc-common/base.css" />
|
||||
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>todos</h1>
|
||||
<input class="new-todo" placeholder="What needs to be done?" autofocus />
|
||||
</header>
|
||||
<main class="main">
|
||||
<div class="toggle-all-container">
|
||||
<input class="toggle-all" type="checkbox" />
|
||||
<label class="toggle-all-label" for="toggle-all">Mark all as complete</label>
|
||||
</div>
|
||||
<ul class="todo-list"></ul>
|
||||
</main>
|
||||
<footer class="footer">
|
||||
<span class="todo-count"></span>
|
||||
<ul class="filters">
|
||||
<li>
|
||||
<a href="#/" class="selected">All</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/active">Active</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/completed">Completed</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="clear-completed">Clear completed</button>
|
||||
</footer>
|
||||
</section>
|
||||
<footer class="info">
|
||||
<p>Click on input field to write your todo.</p>
|
||||
<p>At least two characters are needed to be a valid entry.</p>
|
||||
<p>Press 'enter' to add the todo.</p>
|
||||
<p>Double-click to edit a todo</p>
|
||||
</footer>
|
||||
<script src="src/helpers.js"></script>
|
||||
<script src="src/store.js"></script>
|
||||
<script src="src/model.js"></script>
|
||||
<script src="src/template.js"></script>
|
||||
<script src="src/view.js"></script>
|
||||
<script src="src/controller.js"></script>
|
||||
<script src="src/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,789 +0,0 @@
|
|||
{
|
||||
"name": "javascript-es6",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "javascript-es6",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"todomvc-app-css": "^2.4.2",
|
||||
"todomvc-common": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.13.0",
|
||||
"npm": ">=8.19.3"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"whatwg-encoding": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
"requires-port": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-server": {
|
||||
"version": "14.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
|
||||
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"basic-auth": "^2.0.1",
|
||||
"chalk": "^4.1.2",
|
||||
"corser": "^2.0.1",
|
||||
"he": "^1.2.0",
|
||||
"html-encoding-sniffer": "^3.0.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"mime": "^1.6.0",
|
||||
"minimist": "^1.2.6",
|
||||
"opener": "^1.5.1",
|
||||
"portfinder": "^1.0.28",
|
||||
"secure-compare": "3.0.1",
|
||||
"union": "~0.5.0",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"http-server": "bin/http-server"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"opener": "bin/opener-bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder": {
|
||||
"version": "1.0.32",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
|
||||
"integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"async": "^2.6.4",
|
||||
"debug": "^3.2.7",
|
||||
"mkdirp": "^0.5.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/secure-compare": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
|
||||
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/todomvc-app-css": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.4.2.tgz",
|
||||
"integrity": "sha512-ViAkQ7ed89rmhFIGRsT36njN+97z8+s3XsJnB8E2IKOq+/SLD/6PtSvmTtiwUcVk39qPcjAc/OyeDys4LoJUVg=="
|
||||
},
|
||||
"node_modules/todomvc-common": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.5.tgz",
|
||||
"integrity": "sha512-D8kEJmxVMQIWwztEdH+WeiAfXRbbSCpgXq4NkYi+gduJ2tr8CNq7sYLfJvjpQ10KD9QxJwig57rvMbV2QAESwQ=="
|
||||
},
|
||||
"node_modules/union": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"qs": "^6.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
|
||||
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true
|
||||
},
|
||||
"html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"whatwg-encoding": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"http-proxy": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"http-server": {
|
||||
"version": "14.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
|
||||
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"basic-auth": "^2.0.1",
|
||||
"chalk": "^4.1.2",
|
||||
"corser": "^2.0.1",
|
||||
"he": "^1.2.0",
|
||||
"html-encoding-sniffer": "^3.0.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"mime": "^1.6.0",
|
||||
"minimist": "^1.2.6",
|
||||
"opener": "^1.5.1",
|
||||
"portfinder": "^1.0.28",
|
||||
"secure-compare": "3.0.1",
|
||||
"union": "~0.5.0",
|
||||
"url-join": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||
"dev": true
|
||||
},
|
||||
"opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"dev": true
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "1.0.32",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
|
||||
"integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "^2.6.4",
|
||||
"debug": "^3.2.7",
|
||||
"mkdirp": "^0.5.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"secure-compare": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
|
||||
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
|
||||
"dev": true
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"todomvc-app-css": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.4.2.tgz",
|
||||
"integrity": "sha512-ViAkQ7ed89rmhFIGRsT36njN+97z8+s3XsJnB8E2IKOq+/SLD/6PtSvmTtiwUcVk39qPcjAc/OyeDys4LoJUVg=="
|
||||
},
|
||||
"todomvc-common": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.5.tgz",
|
||||
"integrity": "sha512-D8kEJmxVMQIWwztEdH+WeiAfXRbbSCpgXq4NkYi+gduJ2tr8CNq7sYLfJvjpQ10KD9QxJwig57rvMbV2QAESwQ=="
|
||||
},
|
||||
"union": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"qs": "^6.4.0"
|
||||
}
|
||||
},
|
||||
"url-join": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
|
||||
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"iconv-lite": "0.6.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"name": "javascript-es6",
|
||||
"version": "1.0.0",
|
||||
"description": "TodoMVC app written with JavaScript es6 features.",
|
||||
"engines": {
|
||||
"node": ">=18.13.0",
|
||||
"npm": ">=8.19.3"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "http-server ./ -p 7001 -c-1 --cors",
|
||||
"build": "node scripts/build.js",
|
||||
"serve": "http-server ./dist -p 7002 -c-1 --cors"
|
||||
},
|
||||
"dependencies": {
|
||||
"todomvc-app-css": "^2.4.2",
|
||||
"todomvc-common": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"http-server": "^14.1.1"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
const fs = require("fs").promises;
|
||||
|
||||
const rootDirectory = "./";
|
||||
const sourceDirectory = "./src";
|
||||
const targetDirectory = "./dist";
|
||||
|
||||
const htmlFile = "index.html";
|
||||
|
||||
const filesToMove = ["node_modules/todomvc-common/base.css", "node_modules/todomvc-app-css/index.css"];
|
||||
|
||||
const copy = async (src, dest) => {
|
||||
await fs.copyFile(src, dest);
|
||||
};
|
||||
|
||||
const build = async () => {
|
||||
// remove dist directory if it exists
|
||||
await fs.rm(targetDirectory, { recursive: true, force: true });
|
||||
|
||||
// re-create the directory.
|
||||
await fs.mkdir(targetDirectory);
|
||||
|
||||
// copy src folder
|
||||
await fs.cp(sourceDirectory, targetDirectory, { recursive: true }, (err) => {
|
||||
if (err)
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
// copy html file
|
||||
await fs.copyFile(`${rootDirectory}${htmlFile}`, `${targetDirectory}/${htmlFile}`);
|
||||
|
||||
// copy files to move
|
||||
for (let i = 0; i < filesToMove.length; i++) {
|
||||
const fileName = filesToMove[i].split("/").pop();
|
||||
await copy(filesToMove[i], `${targetDirectory}/${fileName}`);
|
||||
}
|
||||
|
||||
// read html file
|
||||
let html = await fs.readFile(`${targetDirectory}/${htmlFile}`, "utf8");
|
||||
|
||||
// remove base paths from files to move
|
||||
for (let i = 0; i < filesToMove.length; i++) {
|
||||
const basePath = `${filesToMove[i].split("/").slice(0, -1).join("/")}/`;
|
||||
html = html.replace(basePath, "");
|
||||
}
|
||||
|
||||
// remove basePath from source directory
|
||||
const basePath = `${sourceDirectory.split("/")[1]}/`;
|
||||
const re = new RegExp(basePath, "g");
|
||||
html = html.replace(re, "");
|
||||
|
||||
// write html files
|
||||
await fs.writeFile(`${targetDirectory}/${htmlFile}`, html);
|
||||
|
||||
console.log("done!!");
|
||||
};
|
||||
|
||||
build();
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/* eslint no-undef: 0 */
|
||||
"use strict";
|
||||
|
||||
let todo;
|
||||
const setView = () => todo.controller.setView(document.location.hash);
|
||||
|
||||
class Todo {
|
||||
/**
|
||||
* Init new Todo List
|
||||
* @param {string} The name of your list
|
||||
*/
|
||||
constructor(name) {
|
||||
this.storage = new Store(name);
|
||||
this.model = new Model(this.storage);
|
||||
|
||||
this.template = new Template();
|
||||
this.view = new View(this.template);
|
||||
|
||||
this.controller = new Controller(this.model, this.view);
|
||||
}
|
||||
}
|
||||
|
||||
$on(window, "load", () => {
|
||||
todo = new Todo("javascript-es6");
|
||||
setView();
|
||||
});
|
||||
|
||||
$on(window, "hashchange", setView);
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
class Controller {
|
||||
/**
|
||||
* Take a model & view, then act as controller between them
|
||||
* @param {object} model The model instance
|
||||
* @param {object} view The view instance
|
||||
*/
|
||||
constructor(model, view) {
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
|
||||
this.view.bind("newTodo", (title) => this.addItem(title));
|
||||
this.view.bind("itemEdit", (item) => this.editItem(item.id));
|
||||
this.view.bind("itemEditDone", (item) => this.editItemSave(item.id, item.title));
|
||||
this.view.bind("itemEditCancel", (item) => this.editItemCancel(item.id));
|
||||
this.view.bind("itemRemove", (item) => this.removeItem(item.id));
|
||||
this.view.bind("itemToggle", (item) => this.toggleComplete(item.id, item.completed));
|
||||
this.view.bind("removeCompleted", () => this.removeCompletedItems());
|
||||
this.view.bind("toggleAll", (status) => this.toggleAll(status.completed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load & Initialize the view
|
||||
* @param {string} '' | 'active' | 'completed'
|
||||
*/
|
||||
setView(hash) {
|
||||
let route = hash.split("/")[1];
|
||||
let page = route || "";
|
||||
this._updateFilter(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fires on load. Gets all items & displays them
|
||||
*/
|
||||
showAll() {
|
||||
this.model.read((data) => this.view.render("showEntries", data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all active tasks
|
||||
*/
|
||||
showActive() {
|
||||
this.model.read({ completed: false }, (data) => this.view.render("showEntries", data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders all completed tasks
|
||||
*/
|
||||
showCompleted() {
|
||||
this.model.read({ completed: true }, (data) => this.view.render("showEntries", data));
|
||||
}
|
||||
|
||||
/**
|
||||
* An event to fire whenever you want to add an item. Simply pass in the event
|
||||
* object and it'll handle the DOM insertion and saving of the new item.
|
||||
*/
|
||||
addItem(title) {
|
||||
if (title.trim() === "")
|
||||
return;
|
||||
|
||||
this.model.create(title, () => {
|
||||
this.view.render("clearNewTodo");
|
||||
this._filter(true);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Triggers the item editing mode.
|
||||
*/
|
||||
editItem(id) {
|
||||
this.model.read(id, (data) => {
|
||||
let title = data[0].title;
|
||||
this.view.render("editItem", { id, title });
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Finishes the item editing mode successfully.
|
||||
*/
|
||||
editItemSave(id, title) {
|
||||
title = title.trim();
|
||||
|
||||
if (title.length !== 0) {
|
||||
this.model.update(id, { title }, () => {
|
||||
this.view.render("editItemDone", { id, title });
|
||||
});
|
||||
} else {
|
||||
this.removeItem(id);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cancels the item editing mode.
|
||||
*/
|
||||
editItemCancel(id) {
|
||||
this.model.read(id, (data) => {
|
||||
let title = data[0].title;
|
||||
this.view.render("editItemDone", { id, title });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the DOM element with given ID,
|
||||
* Then remove it from DOM & Storage
|
||||
*/
|
||||
removeItem(id) {
|
||||
this.model.remove(id, () => this.view.render("removeItem", id));
|
||||
this._filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove all completed items from the DOM and storage.
|
||||
*/
|
||||
removeCompletedItems() {
|
||||
this.model.read({ completed: true }, (data) => {
|
||||
for (let item of data)
|
||||
this.removeItem(item.id);
|
||||
});
|
||||
|
||||
this._filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Give it an ID of a model and a checkbox and it will update the item
|
||||
* in storage based on the checkbox's state.
|
||||
*
|
||||
* @param {number} id The ID of the element to complete or uncomplete
|
||||
* @param {object} checkbox The checkbox to check the state of complete
|
||||
* or not
|
||||
* @param {boolean|undefined} silent Prevent re-filtering the todo items
|
||||
*/
|
||||
toggleComplete(id, completed, silent) {
|
||||
this.model.update(id, { completed }, () => {
|
||||
this.view.render("elementComplete", { id, completed });
|
||||
});
|
||||
|
||||
if (!silent)
|
||||
this._filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will toggle ALL checkboxes' on/off state and completeness of models.
|
||||
* Just pass in the event object.
|
||||
*/
|
||||
toggleAll(completed) {
|
||||
this.model.read({ completed: !completed }, (data) => {
|
||||
for (let item of data)
|
||||
this.toggleComplete(item.id, completed, true);
|
||||
});
|
||||
|
||||
this._filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the pieces of the page which change depending on the remaining
|
||||
* number of todos.
|
||||
*/
|
||||
_updateCount() {
|
||||
this.model.getCount((todos) => {
|
||||
const completed = todos.completed;
|
||||
const visible = completed > 0;
|
||||
const checked = completed === todos.total;
|
||||
|
||||
this.view.render("updateElementCount", todos.active);
|
||||
this.view.render("clearCompletedButton", { completed, visible });
|
||||
|
||||
this.view.render("toggleAll", { checked });
|
||||
this.view.render("contentBlockVisibility", { visible: todos.total > 0 });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-filters the todo items, based on the active route.
|
||||
* @param {boolean|undefined} force forces a re-painting of todo items.
|
||||
*/
|
||||
_filter(force) {
|
||||
let active = this._activeRoute;
|
||||
const activeRoute = active.charAt(0).toUpperCase() + active.substr(1);
|
||||
|
||||
// Update the elements on the page, which change with each completed todo
|
||||
this._updateCount();
|
||||
|
||||
// If the last active route isn't "All", or we're switching routes, we
|
||||
// re-create the todo item elements, calling:
|
||||
// this.show[All|Active|Completed]()
|
||||
if (force || this._lastActiveRoute !== "All" || this._lastActiveRoute !== activeRoute)
|
||||
this[`show${activeRoute}`]();
|
||||
|
||||
this._lastActiveRoute = activeRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply updates the filter nav's selected states
|
||||
*/
|
||||
_updateFilter(currentPage) {
|
||||
// Store a reference to the active route, allowing us to re-filter todo
|
||||
// items as they are marked complete or incomplete.
|
||||
this._activeRoute = currentPage;
|
||||
|
||||
if (currentPage === "")
|
||||
this._activeRoute = "All";
|
||||
|
||||
this._filter();
|
||||
|
||||
this.view.render("setFilter", currentPage);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
// Allow for looping on nodes by chaining:
|
||||
// qsa('.foo').forEach(function () {})
|
||||
NodeList.prototype.forEach = Array.prototype.forEach;
|
||||
|
||||
// Get element(s) by CSS selector:
|
||||
function qs(selector, scope) {
|
||||
return (scope || document).querySelector(selector);
|
||||
}
|
||||
|
||||
function qsa(selector, scope) {
|
||||
return (scope || document).querySelectorAll(selector);
|
||||
}
|
||||
|
||||
// addEventListener wrapper:
|
||||
function $on(target, type, callback, useCapture) {
|
||||
target.addEventListener(type, callback, !!useCapture);
|
||||
}
|
||||
|
||||
// Attach a handler to event for all elements that match the selector,
|
||||
// now or in the future, based on a root element
|
||||
function $delegate(target, selector, type, handler) {
|
||||
let dispatchEvent = (event) => {
|
||||
const targetElement = event.target;
|
||||
const potentialElements = qsa(selector, target);
|
||||
const hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0;
|
||||
|
||||
if (hasMatch)
|
||||
handler.call(targetElement, event);
|
||||
};
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Events/blur
|
||||
const useCapture = type === "blur" || type === "focus";
|
||||
|
||||
$on(target, type, dispatchEvent, useCapture);
|
||||
}
|
||||
|
||||
// Find the element's parent with the given tag name:
|
||||
// $parent(qs('a'), 'div')
|
||||
function $parent(element, tagName) {
|
||||
if (!element.parentNode)
|
||||
return undefined;
|
||||
|
||||
if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase())
|
||||
return element.parentNode;
|
||||
|
||||
return $parent(element.parentNode, tagName);
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Creates a new Model instance and hooks up the storage.
|
||||
* @constructor
|
||||
* @param {object} storage A reference to the client side storage class
|
||||
*/
|
||||
class Model {
|
||||
constructor(storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new todo model
|
||||
*
|
||||
* @param {string} [title] The title of the task
|
||||
* @param {function} [callback] The callback to fire after the model is created
|
||||
*/
|
||||
create(title, callback) {
|
||||
title = title || "";
|
||||
|
||||
let newItem = {
|
||||
title: title.trim(),
|
||||
completed: false,
|
||||
};
|
||||
|
||||
this.storage.save(newItem, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns a model in storage. If no query is given it'll simply
|
||||
* return everything. If you pass in a string or number it'll look that up as
|
||||
* the ID of the model to find. Lastly, you can pass it an object to match
|
||||
* against.
|
||||
*
|
||||
* @param {string|number|object} [query] A query to match models against
|
||||
* @param {function} [callback] The callback to fire after the model is found
|
||||
*
|
||||
* @example
|
||||
* model.read(1, func) // Will find the model with an ID of 1
|
||||
* model.read('1') // Same as above
|
||||
* //Below will find a model with foo equalling bar and hello equalling world.
|
||||
* model.read({ foo: 'bar', hello: 'world' })
|
||||
*/
|
||||
read(query, callback) {
|
||||
const queryType = typeof query;
|
||||
|
||||
if (queryType === "function") {
|
||||
callback = query;
|
||||
this.storage.findAll(callback);
|
||||
} else if (queryType === "string" || queryType === "number") {
|
||||
query = parseInt(query, 10);
|
||||
this.storage.find({ id: query }, callback);
|
||||
} else {
|
||||
this.storage.find(query, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a model by giving it an ID, data to update, and a callback to fire when
|
||||
* the update is complete.
|
||||
*
|
||||
* @param {number} id The id of the model to update
|
||||
* @param {object} data The properties to update and their new value
|
||||
* @param {function} callback The callback to fire when the update is complete.
|
||||
*/
|
||||
update(id, data, callback) {
|
||||
this.storage.save(data, callback, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a model from storage
|
||||
*
|
||||
* @param {number} id The ID of the model to remove
|
||||
* @param {function} callback The callback to fire when the removal is complete.
|
||||
*/
|
||||
remove(id, callback) {
|
||||
this.storage.remove(id, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: Will remove ALL data from storage.
|
||||
*
|
||||
* @param {function} callback The callback to fire when the storage is wiped.
|
||||
*/
|
||||
removeAll(callback) {
|
||||
this.storage.drop(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a count of all todos
|
||||
*/
|
||||
getCount(callback) {
|
||||
let todos = {
|
||||
active: 0,
|
||||
completed: 0,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
this.storage.findAll((data) => {
|
||||
for (let todo of data) {
|
||||
if (todo.completed)
|
||||
todos.completed++;
|
||||
else
|
||||
todos.active++;
|
||||
|
||||
todos.total++;
|
||||
}
|
||||
|
||||
if (callback)
|
||||
callback(todos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
let uniqueID = 1;
|
||||
|
||||
/**
|
||||
* Creates a new client side storage object and will create an empty
|
||||
* collection if no collection already exists.
|
||||
*
|
||||
* @param {string} name The name of our DB we want to use
|
||||
* @param {function} callback Our fake DB uses callbacks because in
|
||||
* real life you probably would be making AJAX calls
|
||||
*/
|
||||
class Store {
|
||||
constructor(name, callback) {
|
||||
this._dbName = name;
|
||||
this.memoryStorage = this.memoryStorage || {};
|
||||
|
||||
if (!this.memoryStorage[name]) {
|
||||
let data = {
|
||||
todos: [],
|
||||
};
|
||||
|
||||
this.memoryStorage[name] = JSON.stringify(data);
|
||||
}
|
||||
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[name]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds items based on a query given as a JS object
|
||||
*
|
||||
* @param {object} query The query to match against (i.e. {foo: 'bar'})
|
||||
* @param {function} callback The callback to fire when the query has
|
||||
* completed running
|
||||
*
|
||||
* @example
|
||||
* db.find({foo: 'bar', hello: 'world'}, function (data) {
|
||||
* // data will return any items that have foo: bar and
|
||||
* // hello: world in their properties
|
||||
* })
|
||||
*/
|
||||
find(query, callback) {
|
||||
if (!callback)
|
||||
return;
|
||||
|
||||
let todos = JSON.parse(this.memoryStorage[this._dbName]).todos;
|
||||
|
||||
callback.call(
|
||||
this,
|
||||
todos.filter((todo) => {
|
||||
for (let q in query) {
|
||||
if (query[q] !== todo[q])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will retrieve all data from the collection
|
||||
*
|
||||
* @param {function} callback The callback to fire upon retrieving data
|
||||
*/
|
||||
findAll(callback) {
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[this._dbName]).todos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will save the given data to the DB. If no item exists it will create a new
|
||||
* item, otherwise it'll simply update an existing item's properties
|
||||
*
|
||||
* @param {object} updateData The data to save back into the DB
|
||||
* @param {function} callback The callback to fire after saving
|
||||
* @param {number} id An optional param to enter an ID of an item to update
|
||||
*/
|
||||
save(updateData, callback, id) {
|
||||
const data = JSON.parse(this.memoryStorage[this._dbName]);
|
||||
let todos = data.todos;
|
||||
const len = todos.length;
|
||||
|
||||
// If an ID was actually given, find the item and update each property
|
||||
if (id) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (todos[i].id === id) {
|
||||
for (let key in updateData)
|
||||
todos[i][key] = updateData[key];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.memoryStorage[this._dbName] = JSON.stringify(data);
|
||||
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[this._dbName]).todos);
|
||||
} else {
|
||||
// Generate an ID
|
||||
updateData.id = uniqueID++;
|
||||
|
||||
todos.push(updateData);
|
||||
this.memoryStorage[this._dbName] = JSON.stringify(data);
|
||||
|
||||
if (callback)
|
||||
callback.call(this, [updateData]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will remove an item from the Store based on its ID
|
||||
*
|
||||
* @param {number} id The ID of the item you want to remove
|
||||
* @param {function} callback The callback to fire after saving
|
||||
*/
|
||||
remove(id, callback) {
|
||||
const data = JSON.parse(this.memoryStorage[this._dbName]);
|
||||
let todos = data.todos;
|
||||
|
||||
for (let i = 0; i < todos.length; i++) {
|
||||
if (todos[i].id === id) {
|
||||
todos.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.memoryStorage[this._dbName] = JSON.stringify(data);
|
||||
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[this._dbName]).todos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will drop all storage and start fresh
|
||||
*
|
||||
* @param {function} callback The callback to fire after dropping the data
|
||||
*/
|
||||
drop(callback) {
|
||||
this.memoryStorage[this._dbName] = JSON.stringify({ todos: [] });
|
||||
|
||||
if (callback)
|
||||
callback.call(this, JSON.parse(this.memoryStorage[this._dbName]).todos);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/* eslint no-unused-vars: 0 */
|
||||
"use strict";
|
||||
|
||||
const htmlEscapes = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`",
|
||||
};
|
||||
|
||||
const reUnescapedHtml = /[&<>"'`]/g;
|
||||
const reHasUnescapedHtml = new RegExp(reUnescapedHtml.source);
|
||||
|
||||
let escape = (str) => str && reHasUnescapedHtml.test(str) ? str.replace(reUnescapedHtml, escapeHtmlChar) : str;
|
||||
let escapeHtmlChar = (chr) => htmlEscapes[chr];
|
||||
|
||||
class Template {
|
||||
constructor() {
|
||||
this.defaultTemplate = `
|
||||
<li data-id="{{id}}" class="targeted li-{{list-index}} {{completed}}">
|
||||
<div class="targeted view-{{view-index}}">
|
||||
<input class="toggle" type="checkbox" {{checked}}>
|
||||
<label>{{title}}</label>
|
||||
<button class="destroy"></button>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an <li> HTML string and returns it for placement in your app.
|
||||
*
|
||||
* NOTE: In real life you should be using a templating engine such as Mustache
|
||||
* or Handlebars, however, this is a vanilla JS example.
|
||||
*
|
||||
* @param {object} data The object containing keys you want to find in the
|
||||
* template to replace.
|
||||
* @returns {string} HTML String of an <li> element
|
||||
*
|
||||
* @example
|
||||
* view.show({
|
||||
* id: 1,
|
||||
* title: "Hello World",
|
||||
* completed: 0,
|
||||
* })
|
||||
*/
|
||||
show(data) {
|
||||
let i = 0;
|
||||
let view = "";
|
||||
const len = data.length;
|
||||
|
||||
for (i; i < len; i++) {
|
||||
let completed = "";
|
||||
let checked = "";
|
||||
let template = this.defaultTemplate;
|
||||
|
||||
if (data[i].completed) {
|
||||
completed = "completed";
|
||||
checked = "checked";
|
||||
}
|
||||
|
||||
template = template.replace("{{id}}", data[i].id);
|
||||
template = template.replace("{{title}}", escape(data[i].title));
|
||||
template = template.replace("{{completed}}", completed);
|
||||
template = template.replace("{{checked}}", checked);
|
||||
template = template.replace("{{list-index}}", i);
|
||||
template = template.replace("{{view-index}}", i);
|
||||
|
||||
view += template;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a counter of how many to dos are left to complete
|
||||
*
|
||||
* @param {number} activeTodos The number of active todos.
|
||||
* @returns {string} String containing the count
|
||||
*/
|
||||
itemCounter(activeTodos) {
|
||||
let plural = activeTodos === 1 ? "" : "s";
|
||||
return `<strong>${activeTodos}</strong> item${plural} left`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the text within the "Clear completed" button
|
||||
*
|
||||
* @param {[type]} completedTodos The number of completed todos.
|
||||
* @returns {string} String containing the count
|
||||
*/
|
||||
clearCompletedButton(completedTodos) {
|
||||
return completedTodos > 0 ? "Clear completed" : "";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
// import {qs, qsa, $on, $parent, $delegate} from './helpers';
|
||||
|
||||
let _itemId = (element) => parseInt($parent(element, "li").dataset.id, 10);
|
||||
|
||||
let _setFilter = (currentPage) => {
|
||||
qs(".filters .selected").className = "";
|
||||
qs(`.filters [href="#/${currentPage}"]`).className = "selected";
|
||||
};
|
||||
|
||||
let _elementComplete = (id, completed) => {
|
||||
let listItem = qs(`[data-id="${id}"]`);
|
||||
|
||||
if (!listItem)
|
||||
return;
|
||||
|
||||
listItem.className = completed ? "completed" : "";
|
||||
|
||||
// In case it was toggled from an event and not by clicking the checkbox
|
||||
qs("input", listItem).checked = completed;
|
||||
};
|
||||
|
||||
let _editItem = (id, title) => {
|
||||
let listItem = qs(`[data-id="${id}"]`);
|
||||
|
||||
if (!listItem)
|
||||
return;
|
||||
|
||||
listItem.className += " editing";
|
||||
|
||||
let input = document.createElement("input");
|
||||
input.className = "edit";
|
||||
|
||||
listItem.appendChild(input);
|
||||
input.focus();
|
||||
input.value = title;
|
||||
};
|
||||
|
||||
/**
|
||||
* View that abstracts away the browser's DOM completely.
|
||||
* It has two simple entry points:
|
||||
*
|
||||
* - bind(eventName, handler)
|
||||
* Takes a todo application event and registers the handler
|
||||
* - render(command, parameterObject)
|
||||
* Renders the given command with the options
|
||||
*/
|
||||
/* eslint no-undef: 0 */
|
||||
/* eslint no-unused-vars: 0 */
|
||||
/* eslint no-return-assign: 0 */
|
||||
class View {
|
||||
constructor(template) {
|
||||
this.template = template;
|
||||
|
||||
this.ENTER_KEY = 13;
|
||||
this.ESCAPE_KEY = 27;
|
||||
|
||||
this.$todoList = qs(".todo-list");
|
||||
this.$todoItemCounter = qs(".todo-count");
|
||||
this.$clearCompleted = qs(".clear-completed");
|
||||
this.$main = qs(".main");
|
||||
this.$footer = qs(".footer");
|
||||
this.$toggleAll = qs(".toggle-all-label");
|
||||
this.$toggleAllInput = qs(".toggle-all");
|
||||
this.$newTodo = qs(".new-todo");
|
||||
|
||||
this.viewCommands = {
|
||||
showEntries: (parameter) => this.$todoList.innerHTML = this.template.show(parameter),
|
||||
removeItem: (parameter) => this._removeItem(parameter),
|
||||
updateElementCount: (parameter) => this.$todoItemCounter.innerHTML = this.template.itemCounter(parameter),
|
||||
clearCompletedButton: (parameter) => this._clearCompletedButton(parameter.completed, parameter.visible),
|
||||
contentBlockVisibility: (parameter) => this.$main.style.display = this.$footer.style.display = parameter.visible ? "block" : "none",
|
||||
toggleAll: (parameter) => this.$toggleAll.checked = parameter.checked,
|
||||
setFilter: (parameter) => _setFilter(parameter),
|
||||
clearNewTodo: (parameter) => this.$newTodo.value = "",
|
||||
elementComplete: (parameter) => _elementComplete(parameter.id, parameter.completed),
|
||||
editItem: (parameter) => _editItem(parameter.id, parameter.title),
|
||||
editItemDone: (parameter) => this._editItemDone(parameter.id, parameter.title),
|
||||
};
|
||||
}
|
||||
|
||||
_removeItem(id) {
|
||||
let elem = qs(`[data-id="${id}"]`);
|
||||
|
||||
if (elem)
|
||||
this.$todoList.removeChild(elem);
|
||||
}
|
||||
|
||||
_clearCompletedButton(completedCount, visible) {
|
||||
this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount);
|
||||
this.$clearCompleted.style.display = visible ? "block" : "none";
|
||||
}
|
||||
|
||||
_editItemDone(id, title) {
|
||||
let listItem = qs(`[data-id="${id}"]`);
|
||||
|
||||
if (!listItem)
|
||||
return;
|
||||
|
||||
let input = qs("input.edit", listItem);
|
||||
listItem.removeChild(input);
|
||||
|
||||
listItem.className = listItem.className.replace(" editing", "");
|
||||
|
||||
qsa("label", listItem).forEach((label) => label.textContent = title);
|
||||
}
|
||||
|
||||
render(viewCmd, parameter) {
|
||||
this.viewCommands[viewCmd](parameter);
|
||||
}
|
||||
|
||||
_bindItemEditDone(handler) {
|
||||
let self = this;
|
||||
|
||||
$delegate(self.$todoList, "li .edit", "blur", function () {
|
||||
if (!this.dataset.iscanceled) {
|
||||
handler({
|
||||
id: _itemId(this),
|
||||
title: this.value,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the cursor from the input when you hit enter just like if it were a real form
|
||||
$delegate(self.$todoList, "li .edit", "keypress", function (event) {
|
||||
if (event.keyCode === self.ENTER_KEY)
|
||||
this.blur();
|
||||
});
|
||||
}
|
||||
|
||||
_bindItemEditCancel(handler) {
|
||||
let self = this;
|
||||
|
||||
$delegate(self.$todoList, "li .edit", "keyup", function (event) {
|
||||
if (event.keyCode === self.ESCAPE_KEY) {
|
||||
let id = _itemId(this);
|
||||
this.dataset.iscanceled = true;
|
||||
this.blur();
|
||||
|
||||
handler({ id });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bind(event, handler) {
|
||||
var self = this;
|
||||
if (event === "newTodo") {
|
||||
$on(this.$newTodo, "change", () => handler(this.$newTodo.value));
|
||||
} else if (event === "removeCompleted") {
|
||||
$on(this.$clearCompleted, "click", handler);
|
||||
} else if (event === "toggleAll") {
|
||||
$on(this.$toggleAll, "click", function () {
|
||||
self.$toggleAllInput.click();
|
||||
handler({ completed: self.$toggleAllInput.checked });
|
||||
});
|
||||
} else if (event === "itemEdit") {
|
||||
$delegate(this.$todoList, "li label", "dblclick", function () {
|
||||
handler({ id: _itemId(this) });
|
||||
});
|
||||
} else if (event === "itemRemove") {
|
||||
$delegate(this.$todoList, ".destroy", "click", function () {
|
||||
handler({ id: _itemId(this) });
|
||||
});
|
||||
} else if (event === "itemToggle") {
|
||||
$delegate(this.$todoList, ".toggle", "click", function () {
|
||||
handler({
|
||||
id: _itemId(this),
|
||||
completed: this.checked,
|
||||
});
|
||||
});
|
||||
} else if (event === "itemEditDone") {
|
||||
this._bindItemEditDone(handler);
|
||||
} else if (event === "itemEditCancel") {
|
||||
this._bindItemEditCancel(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue