Skip to content

Instantly share code, notes, and snippets.

@hanjae-jea
Last active April 29, 2019 15:11
Show Gist options
  • Save hanjae-jea/453c9f8c9cd5f31d0e559fa008d22a1c to your computer and use it in GitHub Desktop.
Save hanjae-jea/453c9f8c9cd5f31d0e559fa008d22a1c to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie-edge">
<title>Document</title>
<style>
main .gnb{
}
main .folder{
float: left;
width: 200px;
border-right: 1px solid #000;
}
main .task{
margin-left:210px;
}
</style>
</head>
<body>
<main>
<nav class="gnb">
<button>load</button>
<button>save</button>
</nav>
<nav class="folder">
<input type="text">
<ul></ul>
</nav>
<section class="task">
<header>
<h2></h2>
<input type="text">
</header>
<ul></ul>
</section>
</main>
<script src="index.js">
</script>
</body>
</html>
const err = v =>{
throw v;
}
const Task = class{
static load(json){
const task = new Task(json.title, json.isComplete);
return task;
}
toJSON(){
return this.getInfo();
}
static get(title){
return new Task(title);
}
constructor(title, isComplete = false){
this.title = title;
this.isComplete = false;
}
toggle(){this.isComplete = !this.isComplete;}
getInfo(){return {title:this.title, isComplete:this.isComplete};}
};
const Folder = class extends Set{
static load(json){
const folder = new Folder(json.title);
json.tasks.forEach(task=>{
this.addTask(Task.load(task));
});
return folder;
}
toJSON(){
return {title: this.title, tasks: this.getTasks()};
}
static get(title){
return new Folder(title);
}
constructor(title){
super();
this.title = title;
}
moveTask(task, srcFolder){
if( super.has(task) || !srcFolder.has(task) ) return err('...');
srcFolder.removeTask(task);
this.addTask(task);
}
addTask(task){
if( !(task instanceof Task) ) err('invalid task');
super.add(task);
}
removeTask(task){
if( !(task instanceof Task) ) err('invalid task');
super.delete(task);
}
getTitle(){return this.title;}
getTasks(){return [...super.values()];}
add(){err('...');}
delete(){err('...');}
clear(){err('...');}
values(){err('...');}
};
const App = class extends Set{
static load(json){
const app = new App();
json.forEach(folder=>{
app.addFolder(Folder.load(f));
});
return app;
}
toJSON(){
return this.getFolders();
}
constructor(renderer){
super();
}
addFolder(folder){
if( !(folder instanceof Folder) ) err('invalid folder');
super.add(folder);
}
removeFolder(Folder){
if( !(folder instanceof Folder) ) err('invalid folder');
super.delete(folder);
}
getFolders(){return [...super.values()];}
add(){err('...');}
delete(){err('...');}
clear(){err('...');}
values(){err('...');}
};
const Renderer = class{
constructor(app){this.app = app;}
render(){this._render();}
_render(){throw 1;}
};
const el = tag => document.createElement(tag);
const DomRenderer = class extends Renderer{
constructor(parent, app){
super(app);
this.taskEl = [];
const [folder, task] = Array.from(parent.querySelectorAll('ul'));
const [load, save] = Array.from(parent.querySelectorAll('button'));
load.onclick=e=>{
const v = localStorage['todo'];
if(v){
this.app = App.load(JSON.parse(v));
this.render();
}
};
save.onclick=e=>{
localStorage['todo'] = JSON.stringify(this.app);
};
this.folder = folder;
this.task = task;
this.currentFolder = null;
parent.querySelector('nav>input').addEventListener('keyup', e=>{
if(e.keyCode != 13) return;
const v = e.target.value;
const folder = Folder.get(v);
this.app.addFolder(folder);
e.target.value = '';
this.render();
});
parent.querySelector('header>input').addEventListener('keyup', e=>{
if(e.keyCode != 13 || !this.currentFolder) return;
const v = e.target.value;
const task = Task.get(v);
this.currentFolder.addTask(task);
e.target.value = '';
this.render();
});
}
_render(){
const folders = this.app.getFolders();
let moveTask, tasks;
if(!folders.length) return;
if(!this.currentFolder) this.currentFolder = folders[0];
let oldEl = this.folder.firstElementChild, lastEl=oldEl;
folders.forEach(f=>{
let li;
if( oldEl ){
li = oldEl;
oldEl = oldEl.nextElementSibling;
}
else {
li = el('li');
this.folder.appendChild(li);
oldEl = null;
}
lastEl = li;
li.innerHTML = f.getTitle();
li.style.color = this.currentFolder == f ? '#000' : '#777';
li.onclick=()=>{
this.currentFolder = f;
this.render();
};
li.ondrop=e=>{
e.preventDefault();
f.moveTask(moveTask, this.currentFolder);
this.render();
};
li.ondragover=e=>{
e.preventDefault();
};
});
if(lastEl){
while(oldEl = lastEl.nextElementSibling){
this.folder.removeChild(oldEl);
}
}
if(!this.currentFolder) return;
tasks = this.currentFolder.getTasks();
if( !tasks.length ){
while( oldEl = this.task.firstElementChild ){
this.task.removeChild(oldEl);
this.taskEl.push(oldEl);
}
}
oldEl = this.task.firstElementChild, lastEl = oldEl;
tasks.forEach(t=>{
let li;
if( oldEl ){
li = oldEl;
oldEl = oldEl.nextElementSibling;
}else{
li = this.taskEl.length ? this.taskEl.pop() : el('li');
this.task.appendChild(li);
oldEl = null;
}
lastEl = li;
const {title, isComplete} = t.getInfo();
li.setAttribute('draggable', true);
li.innerHTML = (isComplete?'completed ':'process ') + title;
li.onclick=()=>{
t.toggle();
this.render();
};
li.ondragstart=()=>{
moveTask = t;
};
});
if( lastEl ){
while( oldEl = lastEl.nextElementSibling ){
this.task.removeChild(oldEl);
this.taskEl.push(oldEl);
}
}
}
}
const todo = new DomRenderer(document.querySelector('main'), new App());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment