Skip to content

Commit

Permalink
[0300-js-dom-todo] add sources to global public
Browse files Browse the repository at this point in the history
  • Loading branch information
version-1 committed Apr 23, 2024
1 parent 6e754ac commit fe39311
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 1 deletion.
56 changes: 56 additions & 0 deletions public/0300-js-dom-todo/index.html
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>Todo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="stylesheets/style.css">
<link rel="icon" type="image/x-icon" href="assets/favicon.ico">
<script src="https://kit.fontawesome.com/51423ebba7.js" crossorigin="anonymous"></script>
</head>
<body class="body">
<main class="main">
<div class="main__container">
<div class="main__header">
<h1 class="main__header-title">Todo</h1>
</div>
<div class="main__body">
<form class="form js-form">
<div class="form__input">
<label class="form__input-label">タスク</label>
<input name="name" type="text" class="form__input-field" placeholder="タスク名を入力">
</div>
<div class="form__input">
<label class="form__input-label">期限日</label>
<input name="deadline" type="date" class="form__input-field" placeholder="期限日を入力">
</div>
<div class="form__input-footer">
<button class="button button--primary">追加</button>
</div>
</form>
<div class="list">
<div class="list__setting">
<label class="list__setting-label">
<input type="checkbox" class="list__setting-input js-show-completed" />
完了タスクを表示
</label>
</div>
<div class="list__header">
<div class="list__header-item">&nbsp;</div>
<div class="list__header-item list__header-item--name">タスク</div>
<div class="list__header-item">期限日</div>
<div class="list__header-item">&nbsp;</div>
</div>
<div class="list__container js-list-container">
</div>
</div>
</div>
</div>
</main>

<script src="js/lib.js"></script>
<script src="js/function.js"></script>
</body>
</html>
178 changes: 178 additions & 0 deletions public/0300-js-dom-todo/js/function.js
@@ -0,0 +1,178 @@

const state = {
showCompleted: false,
tasks: [
{
name: 'Task 1',
deadline: new AppDate().getDateInXMonth(1),
},
{
name: 'Task 2',
deadline: new AppDate().getDateInXMonth(2),
},
{
name: 'Task 3',
deadline: new AppDate().getDateInXMonth(3),
},
]
}

function renderTasks(container) {
const { tasks, showCompleted } = state

container.innerHTML = ''
tasks.sort((a, b) => a.deadline.getTime() - b.deadline.getTime())
const rerender = () => renderTasks(container)

tasks.forEach((task, index) => {
if (!showCompleted && task.completed) {
return
}

renderTask(container, task, {
onEditName: (col) => {
const value = col.textContent
col.innerHTML = ''

const input = document.createElement('input')
input.setAttribute('type', 'text')
input.setAttribute('class', 'form__input-field')
input.value = value

col.innerHTML = ''
col.appendChild(input)

input.addEventListener('blur', (e) => {
e.stopPropagation()
if (e.target.value) {
const name = e.target.value
task.name = name
}
rerender()
})

input.focus()
},
onEditDeadline: (col) => {
col.innerHTML = ''

const input = document.createElement('input')
input.setAttribute('type', 'date')
input.setAttribute('class', 'form__input-field')
input.value = col.textContent

col.innerHTML = ''
col.appendChild(input)

input.addEventListener('blur', (e) => {
e.stopPropagation()
if (e.target.value) {
task.deadline = AppDate.parse(e.target.value)
}

rerender()
})

input.focus()
input.showPicker()
},
onComplete: (row, completed) => {
row.classList.toggle('list__item--completed')
if (!showCompleted && completed) {
row.classList.add('list__item--completed-dismissing')
}
task.completed = completed

setTimeout(() => {
rerender()
}, 1000)
},
onDelete: () => {
tasks.splice(index, 1)
rerender()
}
})
})
}

const buildColumns = () => {
return {
checkbox: div('list__item-col list__item-col--checkbox'),
name: div('list__item-col list__item-col--name'),
deadline: div('list__item-col list__item-col--deadline'),
actions: div('list__item-col list__item-col--actions')
}
}

function renderTask(target, task, { onEditName, onEditDeadline, onComplete, onDelete }) {
const li = document.createElement('li')
const taskContainer = div('list__item')

const columns = buildColumns()

const checkboxEle = checkbox(task.completed, (checked) => {
onComplete(taskContainer, checked)
})

columns.checkbox.appendChild(checkboxEle)
columns.name.textContent = task.name
columns.name.addEventListener('click', () => {
onEditName(columns.name)
})
columns.deadline.textContent = task.deadline.toString()
columns.deadline.addEventListener('click', () => {
onEditDeadline(columns.deadline)
})

columns.actions.appendChild(icon('icon icon--trash fa-solid fa-trash', () => {
if (window.confirm('このタスクを削除しますか?')) {
onDelete()
}
}))

Object.values(columns).forEach((column) => taskContainer.appendChild(column))
li.appendChild(taskContainer)

target.appendChild(li)
}

function onSubmitTask(container) {
const form = document.querySelector('.js-form')
const data = new FormData(form)
const name = data.get('name')
if (!name) {
window.alert('タスク名を入力してください。')
return
}

const deadline = AppDate.parse(data.get('deadline'))
if (!deadline) {
window.alert('期限日を入力してください。')
return
}

state.tasks.push({
name,
deadline
})

renderTasks(container)
form.reset()
}

function main() {
const todoContainer = document.querySelector('.js-list-container')

document.querySelector('.js-form').addEventListener('submit', (e) => {
e.preventDefault()
onSubmitTask(todoContainer)
})

document.querySelector('.js-show-completed').addEventListener('change', (e) => {
state.showCompleted = e.target.checked
renderTasks(todoContainer)
})
renderTasks(todoContainer)
}

main()
98 changes: 98 additions & 0 deletions public/0300-js-dom-todo/js/lib.js
@@ -0,0 +1,98 @@

// date class
class AppDate {
static parse(dateString) {
if (!dateString) {
return
}

const [year, month, day] = dateString.split('-').map((str) => parseInt(str, 10))
return new AppDate(new Date(year, month - 1, day))
}

constructor(date = new Date()) {
this.date = date
}

get cloneDate() {
return new Date(this.date.getTime())
}

toString() {
const month = (this.date.getMonth() + 1).toString().padStart(2, '0')
const day = this.date.getDate().toString().padStart(2, '0')

return `${this.date.getFullYear()}-${month}-${day}`
}

getDateInXMonth(n) {
const date = (this.date.getMonth() + n) % 12
const res = new Date(this.cloneDate.setMonth(date))

return new AppDate(res)
}

getTime() {
return this.date.getTime()
}

isAfter(date) {
return this.date.getTime() > date.getTime()
}
}

// components

function div(klass) {
const div = document.createElement('div')
div.setAttribute('class', klass)

return div
}

function icon(klass, onClick) {
const i = document.createElement('i')
i.setAttribute('class', klass)
i.addEventListener('click', onClick)

return i
}

function button(text, klass, onClick) {
const button = document.createElement('button')
button.setAttribute('class', `button ${klass}`)
button.textContent = text
button.addEventListener('click', onClick)

return button
}

function checkbox(checked, onClick) {
const label = document.createElement('label')
label.setAttribute('class', 'checkbox')
if (checked) {
label.classList.add('checkbox--checked')
}

const checkbox = document.createElement('input')
checkbox.setAttribute('type', 'checkbox')
checkbox.setAttribute('class', 'checkbox__input')
checkbox.checked = checked

label.addEventListener('click', () => {
checkbox.checked = !checkbox.checked
if (checkbox.checked) {
label.classList.add('checkbox--checked')
} else {
label.classList.remove('checkbox--checked')
}

onClick(checkbox.checked)
})
label.appendChild(checkbox)
label.appendChild(icon('icon icon--check fa-solid fa-check', onClick))

return label
}


1 change: 1 addition & 0 deletions public/0300-js-dom-todo/stylesheets/style.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion public/index.html
Expand Up @@ -15,7 +15,7 @@ <h2>フロントエンド課題一覧</h2>
<ol>
<li><a href="0100-html-css/index.html">HTML/CSS(BEM, Sass)課題</a></li>
<li><a href="">JavaScript 課題</a></li>
<li><a href="">React 入門課題</a></li>
<li><a href="0300-js-dom-todo/index.html">React 入門課題</a></li>
<li><a href="">React + Context API + Next.js 課題</a></li>
<li><a href="">TypeScript 課題</a></li>
<li><a href="">React + HTTP 課題</a></li>
Expand Down

0 comments on commit fe39311

Please sign in to comment.