Skip to content

Commit

Permalink
以 mvc 的方式写一个 todolist
Browse files Browse the repository at this point in the history
  • Loading branch information
The-Punk-and-The-Monk committed Jul 6, 2020
1 parent c3fbabe commit 63166cc
Show file tree
Hide file tree
Showing 6 changed files with 454 additions and 0 deletions.
25 changes: 25 additions & 0 deletions js-mvc-todolist/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"type": "chrome",
"request": "launch",
"name": "Launch index.html",
// "url": "http://localhost:3000",
"file": "${workspaceFolder}/index.html",
"webRoot": "${workspaceFolder}/index.html"
},
{
"type": "chrome",
"request": "launch",
"name": "Launch test.html",
// "url": "http://localhost:3000",
"file": "${workspaceFolder}/test.html",
"webRoot": "${workspaceFolder}"
}
]
}
18 changes: 18 additions & 0 deletions js-mvc-todolist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>

<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>Todo App</title>

<link rel="stylesheet" href="style.css"/>
</head>

<body>
<div id="root"></div>
<script src="script.js"></script>
</body>

</html>
37 changes: 37 additions & 0 deletions js-mvc-todolist/messages.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
body {
margin: 10px auto;
width: 470px;
}

h3 {
margin: 0;
padding-bottom: .3em;
padding-right: 20px;
font-size: 1.1em;
}

p {
margin: 0;
padding: 0 0 .5em;
}

.pane {
background: #edf5e1;
padding: 10px 20px 10px;
border-top: solid 2px #c4df9b;
position: relative;
}

.remove-button {
position: absolute;
font-size: 110%;
top: 0;
color: darkred;
right: 10px;
display: block;
width: 24px;
height: 24px;
border: none;
background: transparent;
cursor: pointer;
}
205 changes: 205 additions & 0 deletions js-mvc-todolist/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/**
* 疑问:
* 1, 为什么点击 form 区域提交的是 submit 事件
* 2. 为 todolist 的整个区域添加 eventListener 是给他所有的子节点一起添加了吗? 如果不是,event.target 是怎么确定的?
*/

class Model {
constructor() {
this.todos = JSON.parse(localStorage.getItem('todos')) || []
}

update(){
localStorage.setItem('todos', JSON.stringify(this.todos))
this.onTodoListChanged(this.todos)

}


addTodo(todo) {
this.todos = [...this.todos, todo]
this.update()
}

editTodo(id, updatedText) {
this.todos = this.todos.map(todo => {
return todo.id === id ? {
id: todo.id,
text: updatedText,
complete: todo.complete
} : todo
})
this.update()
}

deleteTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id)
this.update()
}

toggleTodo(id) {
this.todos = this.todos.map(todo =>
todo.id === id ? {
id: todo.id,
text: todo.text,
complete: !todo.complete
} : todo)
this.update()
}

bindEvents(controller){
this.onTodoListChanged = controller.onTodoListChanged
}
}

class View {
constructor() {
this.app = this.getElemet('#root')

this.title = this.createElement('h1')
this.title.textContent = 'Todos'

this.form = this.createElement('form')

this.input = this.createElement('input')
this.input.type = "text"
this.input.placeholder = "Add Todo"
this.input.name = "todo"

this.submitButton = this.createElement('button')
this.submitButton.textContent = 'Submit'

this.todoList = this.createElement('ul', 'todo-list')

this.form.append(this.input, this.submitButton)
this.app.append(this.title, this.form, this.todoList)
}

getTodoText() {
return this.input.value
}

resetInput() {
this.input.value = ''
}

displayTodos(todos) {
while (this.todoList.firstChild) {
this.todoList.removeChild(this.todoList.firstChild)
}
if (todos.length === 0) {
const p = this.createElement('p')
p.textContent = 'Nothing to do! Add a task?'
this.todoList.append(p)
} else {
todos.forEach(todo => {
const li = this.createElement('li')
li.id = todo.id
const checkbox = this.createElement('input')
checkbox.type = 'checkbox'
checkbox.checked = todo.complete

const span = this.createElement('span')
span.contentEditable = true
span.classList.add('editable')

if (todo.complete) {
const strike = this.createElement('s')
strike.textContent = todo.text
span.append(strike)
} else {
span.textContent = todo.text
}

const deleteButton = this.createElement('button', 'delete')
deleteButton.textContent = 'Delete'
li.append(checkbox, span, deleteButton)
this.todoList.append(li)
})
}
}

createElement(tag, className) {
const element = document.createElement(tag)
if (className) element.classList.add(className);
return element
}

getElemet(selector) {
const element = document.querySelector(selector);

return element;
}

bindEvents(controller){
this.form.addEventListener('submit', controller.handleAddTodo) // 如果换成 click 会怎么样
this.todoList.addEventListener('click', controller.handleDeleteTodo)
this.todoList.addEventListener('change', controller.handleToggle)
this.todoList.addEventListener('focusout', controller.handleEditTodoComplete)
this.todoList.addEventListener('input', controller.handleEditTodo)
}
}

class Controller {
constructor(model, view) {
this.model = model;
this.view = view;

this.onTodoListChanged(this.model.todos)
this.view.bindEvents(this)
this.model.bindEvents(this)
this.temporaryEditValue
}

onTodoListChanged = todos => {
this.view.displayTodos(todos)
}

handleEditTodo = event => {
if(event.target.className === 'editable'){
this.temporaryEditValue = event.target.innerText
}
}

handleEditTodoComplete = event => {
if(this.temporaryEditValue){ // 为什么这里不用验证 event.target的类型
const id = parseInt(event.target.parentElement.id)
this.model.editTodo(id, this.temporaryEditValue)
this.temporaryEditValue = ''
}
}

handleAddTodo = event => {
event.preventDefault()

if(this.view.getTodoText()){ // 为什么这里不用验证 event.target 的类型
const todo = {
id:this.model.todos.length > 0 ? this.model.todos[this.model.todos.length - 1].id + 1:1,
text: this.view.getTodoText(),
complete: false,
}
this.model.addTodo(todo)
this.view.resetInput()
}

}

handleDeleteTodo = event => {
if(event.target.className === 'delete'){
const id = parseInt(event.target.parentElement.id)
this.model.deleteTodo(id)
}
}

handleToggle = event => {
if(event.target.type === 'checkbox'){
const id = parseInt(event.target.parentElement.id)
this.model.toggleTodo(id)
}
}
}

const model = new Model();

const view = new View();
const controller = new Controller(model, view);
90 changes: 90 additions & 0 deletions js-mvc-todolist/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
*,
*::before,
*::after {
box-sizing: border-box
}

html {
font-family: sans-serif;
font-size: 1rem;
color: #444;
}

#root {
max-width: 450px;
margin: 2rem auto;
padding: 0 1rem;
}

form {
display: flex;
margin-bottom: 2rem;
}

[type="text"],
button {
display: inline-block;
-webkit-appearance: none;
padding: .5rem 1rem;
font-size: 1rem;
border: 2px solid #ccc;
border-radius: 4px;
}

button {
cursor: pointer;
background: #007bff;
color: white;
border: 2px solid #007bff;
margin: 0 .5rem;
}

[type="text"] {
width: 100%;
}

[type="text"]:active,
[type="text"]:focus {
outline: 0;
border: 2px solid #007bff;
}

[type="checkbox"] {
margin-right: 1rem;
font-size: 2rem;
}

h1 {
color: #222;
}

ul {
padding: 0;
}

li {
display: flex;
align-items: center;
padding: 1rem;
margin-bottom: 1rem;
background: #f4f4f4;
border-radius: 4px;
}

li span {
display: inline-block;
padding: .5rem;
width: 250px;
border-radius: 4px;
border: 2px solid transparent;
}

li span:hover {
background: rgba(179, 215, 255, 0.52);
}

li span:focus {
outline: 0;
border: 2px solid #007bff;
background: rgba(179, 207, 255, 0.52)
}
Loading

0 comments on commit 63166cc

Please sign in to comment.