-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
xxx
committed
Aug 26, 2020
0 parents
commit ed8be2a
Showing
8 changed files
with
401 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* linguist-language=JavaScript |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# Task Manager - JavaScript | ||
|
||
![oc](/img/octocat.jpeg) | ||
*`[3]`* | ||
<br><br> | ||
## Demo | ||
|
||
![demo](/img/demo.gif) | ||
<br><br> | ||
## Overview | ||
|
||
Task manager web app was specifically designed to test web broser' Local Storage which functions as a permanent storage for users tasks. Some of the features used on the project were: | ||
|
||
- **JavaScript**. | ||
- **HTML**. | ||
- **DOM**. | ||
- **querySelector**. | ||
- **Materialize**. | ||
- **Bootstrap**. | ||
- **LocalStorage**. | ||
<br><br> | ||
## HTML Web Storage *`[1]`* | ||
|
||
The Web Storage API provides mechanisms by which browsers can securely store key/value pairs. Storage objects are simple key-value stores, similar to objects, but they stay intact through page loads. The keys and the values are always strings (note that, as with objects, integer keys will be automatically converted to strings). | ||
|
||
> **Note**: It's recommended to use the Web Storage API ([setItem](setItem), [getItem](getItem), [removeItem](removeItem), key, length) to prevent the **pitfalls** associated with using plain objects as key-value stores. | ||
|
||
- **sessionStorage**: The read-only sessionStorage property accesses a session Storage object for the current origin. sessionStorage is similar to localStorage; the difference is that while data in localStorage doesn't expire, data in sessionStorage is cleared when the page session ends. | ||
|
||
- **localStorage**: The read-only localStorage property allows you to access a Storage object for the Document's origin; the stored data is saved across browser sessions. localStorage is similar to sessionStorage, except that while data stored in localStorage has no expiration time, data stored in sessionStorage gets cleared when the page session ends — that is, when the page is closed. (Data in a localStorage object created in a "private browsing" or "incognito" session is cleared when the last "private" tab is closed.) | ||
|
||
<br><br> | ||
|
||
| Web Storage API | Summary | Limit | | ||
|--------------------------------------------|--------------------------|--------------------------| | ||
| [Window.sessionStorage](Window.sessionStorage) | - Shared between all tabs and windows with the same origin. <br>- Survives browser restart. | **4KB** | | ||
| [Window.localStorage](Window.localStorage) | - Visible within a browser tab, including iframes from the same origin. <br>- Survives page refresh (but not tab close). | **5MB** | | ||
| | ||
|
||
|
||
> **Note**: In private browsing mode, most data storage is not supported. Local storage data and cookies are still stored, but they are ephemeral — the data is deleted when you close the last private browsing window. | ||
|
||
<br><br> | ||
## Security *`[2]`* | ||
|
||
|
||
- **It can only store string data.** This makes it pretty useless for storing data that's even slightly more complex than a simple string. And sure, you could serialize everything including data types into local storage, but that's an ugly hack. | ||
|
||
- **It is synchronous.** This means each local storage operation you run will be one-at-a-time. For complex applications this is a big no-no as it'll slow down your app's runtime. | ||
|
||
- **It can't be used by web workers.** This means that if you want to build an application that takes advantage of background processing for performance, chrome extensions, things like that: you can't use local storage at all since it isn't available to the web workers. | ||
|
||
- **It still limits the size of data you can store (~5MB across all major browsers).** This is a fairly low limit for people building apps that are data intensive or need to function offline. | ||
|
||
- **Any JavaScript code on your page can access local storage.** It has no data protection whatsoever. This is the big one for security reasons. | ||
|
||
- **Cross-site scripting attacks (XSS).** If an attacker can run JavaScript on your website, they can retrieve all the data you've stored in local storage and send it off to their own domain. This means anything sensitive you've got in local storage (like a user's session data) can be compromised. | ||
|
||
- **Don't Store JSON Web Tokens (JWTs) in Local Storage**. The biggest security offenders I see today are those of us who store JWTs (session data) in local storage. Many people don't realize that JWTs are essentially the same thing as a username/password. | ||
|
||
<br><br> | ||
## Alternatives to LocalStorage | ||
|
||
- Use server-side sessions for sensitive information | ||
- For non-sensitive information, choose IndexedDB | ||
|
||
If you need to store sensitive data, you should always use a server-side session. Sensitive data includes: | ||
|
||
``` | ||
- User IDs | ||
- Session IDs | ||
- JWTs | ||
- Personal information | ||
- Credit card information | ||
- API keys | ||
- And anything else you wouldn't want to publicly share on Facebook | ||
``` | ||
|
||
If you need to store sensitive data, here's how to do it: | ||
|
||
When a user logs into your website, create a session identifier for them and store it in a cryptographically signed cookie. If you're using a web framework. Make sure that whatever cookie library your web framework uses is setting the httpOnly cookie flag. This flag makes it impossible for a browser to read any cookies, which is required in order to safely use server-side sessions with cookies. Make sure that your cookie library also sets the SameSite=strict cookie flag (to prevent CSRF attacks), as well as the secure=true flag (to ensure cookies can only be set over an encrypted connection). Each time a user makes a request to your site, use their session ID (extracted from the cookie they send to you) to retrieve their account details from either a database or a cache (depending on how large your website is). Once you have the user's account info pulled up and verified, feel free to pull any associated sensitive data along with it | ||
|
||
### Non-String Data | ||
|
||
- If you need to store data in the browser that isn't sensitive and isn't purely string data, the best option for you is IndexedDB. It's an API that lets you work with a database-esque object store in the browser. | ||
|
||
### Offline Data | ||
|
||
- If you need your app to run offline, your best option is to use a combination of IndexedDB (above) along with the Cache API (which is a part of Service Workers). | ||
|
||
<br><br> | ||
## Screenshot | ||
|
||
![screenshot](/img/screenshot.png) | ||
|
||
<br><br> | ||
## References | ||
|
||
[1] <https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API> | ||
|
||
[2] <https://dev.to/rdegges/please-stop-using-local-storage-1i04> | ||
|
||
[3] <https://github.blog/2012-02-09-kids-are-the-future-teach-em-to-code/> | ||
|
||
<br><br> | ||
## License | ||
|
||
> Licensed under the [MIT](license) license. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<!-- Compiled and minified CSS --> | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> | ||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous"> | ||
<title>Task List</title> | ||
</head> | ||
<body> | ||
<div class="container"> | ||
<div class="row"> | ||
<div class="col s12"> | ||
<div id="main" class="card lime z-depth-3"> | ||
<div class="card-content"> | ||
<span class="card-title center-align"><h5><b>Task Manager</b></h5></span> | ||
<div class="row"> | ||
<form id="task-form"> | ||
<!-- form#task-form --> | ||
<div class="input-field co1 s12"> | ||
<input type="text" name="task" id="task"> | ||
<!-- taskInput#task --> | ||
<label for="task" class="pink-text text-darken-3">New Task</label> | ||
</div> | ||
<div class="input-field co1 s12"> | ||
<input type="date" name="start" id="start"> | ||
<label for="start" class="pink-text text-darken-3"> Expected date to complete the task</label> | ||
</div> | ||
<input type="submit" value="Add Task" class="btn black-text amber accent-4"> | ||
</form> | ||
</div> | ||
</div> | ||
<div class="card-action lime darken-1"> | ||
<a class="reload secondary-content" style="color: green;"> | ||
<i class="small fa fa-retweet"></i> | ||
</a> | ||
<h5 id="task-title" class="center-align"><b>Tasks</b></h5> | ||
|
||
<div class="input-field co1 s12"> | ||
<input type="text" name="filter" id="filter"> | ||
<!-- filter#filter --> | ||
<label for="filter" class="pink-text text-darken-3">Filter Task</label> | ||
</div> | ||
<ul class="collection z-depth-3"></ul> | ||
<!-- taskList.collection --> | ||
<a href="#" class="clear-tasks btn blue-grey darken-4">Clear Tasks</a> | ||
<!-- clearBtn.clear-tasks --> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<script src="https://code.jquery.com/jquery-3.2.1.js" integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE=" crossorigin="anonymous"></script> | ||
<!-- Compiled and minified JavaScript --> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> | ||
<script src="js/app.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
// Define UI Vars | ||
const form = document.querySelector('#task-form'); | ||
const taskList = document.querySelector('.collection'); | ||
const clearBtn = document.querySelector('.clear-tasks'); | ||
const filter = document.querySelector('#filter'); | ||
const taskInput = document.querySelector('#task'); | ||
const dateInput = document.querySelector('#start'); | ||
const reloadIcon = document.querySelector('.reload'); | ||
const jsonTask = { | ||
task: "", | ||
endDate: "" | ||
}; | ||
|
||
// Load all event listeners | ||
loadEventListeners(); | ||
|
||
// Load all event listener | ||
function loadEventListeners(){ | ||
// clear input | ||
taskInput.value = ''; | ||
filter.value = ''; | ||
dateInput.value = ''; | ||
// load local storage | ||
document.addEventListener('DOMContentLoaded', loadTaskLocalStorage); | ||
// Add task event | ||
form.addEventListener('submit', addTask); | ||
// Remove task event | ||
taskList.addEventListener('click', removeTask); | ||
// remove tasks button | ||
clearBtn.addEventListener('click', clearTasks); | ||
// filter task | ||
filter.addEventListener('keyup', filterTasks); | ||
// reload icon | ||
reloadIcon.addEventListener('click', reloadTask); | ||
} | ||
|
||
function reloadTask(e){ | ||
location.reload(); | ||
} | ||
|
||
// load local storage | ||
function loadTaskLocalStorage(){ | ||
let taskArrayItem; | ||
if(localStorage.getItem('savedTasks') !== null){ | ||
taskArrayItem = JSON.parse(localStorage.getItem('savedTasks')); | ||
taskArrayItem.forEach( | ||
function(taskStored){ | ||
createTaskElement(taskStored.task, taskStored.endDate); | ||
// console.log(taskStored); | ||
} | ||
); | ||
} | ||
} | ||
|
||
// Add task | ||
function addTask(e){ | ||
if(taskInput.value !== '' && dateInput.value !== '' && daysRemaining(Date.parse(dateInput.value), Date.now()) >= 0){ | ||
createTaskElement(taskInput.value, Date.parse(dateInput.value)); | ||
// save locally | ||
saveTaskLocalStorage(taskInput.value, Date.parse(dateInput.value)); | ||
e.preventDefault(); | ||
} | ||
taskInput.value = ''; | ||
dateInput.value = ''; | ||
} | ||
|
||
// create a tasks elements on the page | ||
function createTaskElement(taskInput, taskDate){ | ||
// Create li element | ||
const li = document.createElement('li'); | ||
|
||
const vBolt = document.createElement('b'); | ||
vBolt.innerText = 'Task: '; | ||
li.appendChild(vBolt); | ||
|
||
// add class | ||
li.className = 'collection-item orange darken-3 z-depth-5'; | ||
// create text node and append to li | ||
li.appendChild(document.createTextNode(taskInput)); | ||
|
||
// ******** X button ********** | ||
// create new link element | ||
const link = document.createElement('a'); | ||
// add class | ||
link.className = 'delete-item secondary-content'; | ||
// add icon html | ||
link.innerHTML = '<i class="fa fa-remove"></i>'; | ||
// **************************** | ||
|
||
// ******** Line ************* | ||
const vLine = document.createElement('hr'); | ||
vLine.style.color = 'orange'; | ||
// **************************** | ||
|
||
// ******** Date ************** | ||
// create new Date element | ||
const newDate = document.createElement('p'); | ||
newDate.className = 'newDate'; | ||
var dDate = new Date(taskDate); | ||
newDate.innerText = 'Task deadline: ' + dDate.getUTCDate() + '/' + (dDate.getUTCMonth() + 1) + '/' + dDate.getUTCFullYear(); | ||
// **************************** | ||
|
||
// ******** Date ************** | ||
// create new Date element | ||
const newReDate = document.createElement('p'); | ||
newReDate.className = 'remainingDays'; | ||
var daysR = daysRemaining(taskDate, Date.now()); | ||
newReDate.innerText = `Remaining days to complete this task: ${daysR}`; | ||
// **************************** | ||
|
||
// ******** Preloader ************** | ||
// create new Date element | ||
const newPreloader = document.createElement('div'); | ||
newPreloader.className = 'progress'; | ||
const newDeterminate = document.createElement('div'); | ||
newDeterminate.className = 'determinate grey'; | ||
var daysRPorcent = 100 - daysR; | ||
newDeterminate.style.width = daysRPorcent + '%'; | ||
newPreloader.appendChild(newDeterminate) | ||
// **************************** | ||
|
||
// append the link to li | ||
li.appendChild(link); | ||
li.appendChild(vLine); | ||
// append Date | ||
li.appendChild(newDate); | ||
li.appendChild(newReDate); | ||
li.appendChild(newPreloader); | ||
// append li to ul | ||
taskList.appendChild(li); | ||
} | ||
|
||
function daysRemaining(datePlanned, dateNow) { | ||
var minutes = 1000 * 60; | ||
var hours = minutes * 60; | ||
var days = hours * 24; | ||
var years = days * 365; | ||
var tdatePlanned = datePlanned; | ||
var tdateNow = dateNow; | ||
var ydatePlanned = Math.round(tdatePlanned / days); | ||
var ydateNow = Math.round(tdateNow / days); | ||
var yt = ydatePlanned - ydateNow; | ||
return yt; | ||
} | ||
|
||
|
||
function saveTaskLocalStorage(saveInput, taskDate){ | ||
// console.log(saveInput); | ||
let tempKey; | ||
if(localStorage.getItem('savedTasks') === null){ | ||
tempKey = []; | ||
} else { | ||
tempKey = JSON.parse(localStorage.getItem('savedTasks')); | ||
} | ||
jsonTask.task = saveInput; | ||
jsonTask.endDate = taskDate; | ||
tempKey.push(jsonTask); | ||
// console.log(tempKey); | ||
localStorage.setItem('savedTasks', JSON.stringify(tempKey)); | ||
} | ||
|
||
function removeTask(e){ | ||
if(e.target.parentElement.classList.contains('delete-item')){ | ||
e.target.parentElement.parentElement.remove(); | ||
// remove from LS | ||
// console.log(e.target.parentElement.parentElement.firstChild.nextSibling.textContent); | ||
removeTaskLocalStorage(e.target.parentElement.parentElement.firstChild.nextSibling.textContent); | ||
} | ||
} | ||
|
||
// remove item local storage | ||
function removeTaskLocalStorage(taskItem){ | ||
let taskArrayItem; | ||
if(localStorage.getItem('savedTasks') === null){ | ||
taskArrayItem = []; | ||
} else { | ||
taskArrayItem = JSON.parse(localStorage.getItem('savedTasks')); | ||
} | ||
taskArrayItem.forEach( | ||
function(taskStored, index){ | ||
if(taskStored.task === taskItem){ | ||
taskArrayItem.splice(index, 1); | ||
// console.log(taskArrayItem); | ||
} | ||
} | ||
); | ||
|
||
localStorage.setItem('savedTasks', JSON.stringify(taskArrayItem)); | ||
} | ||
|
||
function clearTasks(e){ | ||
// option 1 | ||
// taskList.innerHTML = ''; | ||
// option 2 - faster | ||
while(taskList.firstChild){ | ||
taskList.removeChild(taskList.firstChild); | ||
} | ||
localStorage.removeItem('savedTasks'); | ||
} | ||
|
||
function filterTasks(e){ | ||
const text = e.target.value.toLowerCase(); | ||
document.querySelectorAll('.collection-item').forEach( | ||
function(task){ | ||
const item = task.firstChild.nextSibling.textContent.toLowerCase(); | ||
// console.log(item); | ||
if(item.indexOf(text) != -1){ | ||
task.style.display = 'block'; | ||
} else { | ||
task.style.display = 'none'; | ||
} | ||
}); | ||
} |
Oops, something went wrong.