Today's notes are todos to-do items
I believe you can't wait to realize some functions after learning the Vue foundation, so I'll use Vue basic grammar and some js knowledge to combine them
Maybe the notes are not very good I won't share the source code. If you want to see the source code, you'd better read it yourself and type it out
Create your own sub components and style css under the directory
todos - create projects and components
The contents are as follows
The content of base.css is as follows
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; } }
The index.css style is as follows
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; } :focus { outline: 0; } .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: 300; color: rgba(0, 0, 0, 0.4); } .todoapp input::-moz-placeholder { font-style: italic; font-weight: 300; color: rgba(0, 0, 0, 0.4); } .todoapp input::input-placeholder { font-style: italic; font-weight: 300; 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; 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 { width: 60px; height: 34px; font-size: 0; position: absolute; top: 12px; left: -13px; -webkit-transform: rotate(90deg); transform: rotate(90deg); z-index: 9999 } .toggle-all + label:before { content: '❯'; font-size: 22px; color: #e6e6e6; padding: 10px 27px 10px 27px; } .toggle-all:checked + label:before { color: #737373; } .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%23ededed%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//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%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%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: #4d4d4d; } .todo-list li.completed label { color: #cdcdcd; 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: #cc9a9a; margin-bottom: 11px; transition: color 0.2s ease-out; } .todo-list li .destroy:hover { color: #af5b5e; } .todo-list li .destroy:after { content: '×'; } .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: rgba(175, 47, 47, 0.1); } .filters li a.selected { border-color: rgba(175, 47, 47, 0.2); } .clear-completed, html .clear-completed:active { float: right; position: relative; line-height: 20px; 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; } }
Self copy, paste and put
Then we introduce these two CSS style sheets into App.vue
import "./styles/base.css" import "./styles/index.css"
TodoHeader.vue TodoMain.vue TodoFooter.vue three sub components will Distinguish the whole App.vue
components/TodoHeader.vue - copy labels and class names
<template> <header class="header"> <h1>todos</h1> <input id="toggle-all" class="toggle-all" type="checkbox" > <label for="toggle-all"></label> <input class="new-todo" placeholder="Enter task name-Enter to confirm" autofocus /> </header> </template> <script> export default { } </script>
components/TodoMain.vue - copy labels and class names
<template> <ul class="todo-list"> <!-- completed: Completed class name --> <li class="completed" > <div class="view"> <input class="toggle" type="checkbox" /> <label>Task name</label> <button class="destroy"></button> </div> </li> </ul> </template> <script> export default { } </script>
components/TodoFooter.vue - copy labels and class names
<template> <footer class="footer"> <span class="todo-count">surplus<strong>Quantity value</strong></span> <ul class="filters"> <li> <a class="selected" href="javascript:;" >whole</a> </li> <li> <a href="javascript:;">hang in the air</a> </li> <li> <a href="javascript:;" >Completed</a> </li> </ul> <button class="clear-completed" >Cleanup completed</button> </footer> </template> <script> export default { } </script>
Introduced and used in App.vue
<template> <section class="todoapp"> <!-- Except for the hump, You can also use-Conversion link --> <TodoHeader></TodoHeader> <TodoMain></TodoMain> <TodoFooter></TodoFooter> </section> </template> <script> // 1.0 Style Introduction import "./styles/base.css" import "./styles/index.css" import TodoHeader from "./components/TodoHeader"; import TodoMain from "./components/TodoMain"; import TodoFooter from "./components/TodoFooter"; export default { components: { TodoHeader, TodoMain, TodoFooter, }, }; </script>
-
Component creation
-
Component introduction
-
Component registration
-
Component use
It must be unrealistic for us to complete such a large project at one time
So I divided them into functions and implemented them one by one. First, I prepared some data to be laid in Todomain and App.vue
todos - laying to-do tasks
-
Requirement 1: display the to-do tasks on the TodoMain.vue component of the page
-
Requirement 2: Associate selected status and set related styles
① : App.vue – prepare the array to be passed into TodoMain.vue
② : v-for loop display data
③ The: v-model binding check box is selected
④ : sets the finish dash style according to the selected status
Put this data into App data
<TodoMain :arr="showArr"></TodoMain>
list: [ { id: 100, name: "having dinner", isDone: true }, { id: 201, name: "sleep", isDone: false }, { id: 103, name: "Beat beans", isDone: true }, ]
TodoMain.vue
1. Circulating laying obj in arr
2. When the radio box is in the trigger point, let the value be true
3. Dynamic class judges whether there is a completed class name through isDone
4. Give the button a click event, and then write the trigger function
<template> <ul class="todo-list"> <!-- 2.2 Circular task-Association selected status-Laying data --> <!-- completed: Completed class name --> <li :class="{completed: obj.isDone}" v-for="(obj, index) in arr" :key='obj.id'> <div class="view"> <input class="toggle" type="checkbox" v-model="obj.isDone"/> <label>{{ obj.name }}</label> <!-- 4.0 Register click events --> <button @click="delFn(index)" class="destroy"></button> </div> </li> </ul> </template> <script> export default { props: ["list"] }; </script> <style> </style>
This paragraph relates to
-
Parent to child Technology
-
v-for loop
-
v-model binding
-
Dynamic class usage
The data laying is finished. Next, try to add tasks
todos - add task
Demand: enter a task and press enter to add a to-do task
Objective: enter the task name to be completed in the top input box, and click enter to complete the new function
analysis:
① : TodoHeader.vue – input box – keyboard event – enter key
② : pass the child to the parent, and add the to-do task - App.vue - to the array list
③ : if the original array is changed, all the places used will be updated
④ : the input box is empty, prompting the user to enter content
TodoHeader.vue
1. Listen to the event of clicking enter
2. Bidirectional binding task
3. Judge that the task is not empty and clear the space before and after it
4. Clear the input box after passing the value to the parent App
<template> <header class="header"> <h1>todos</h1> <input id="toggle-all" class="toggle-all" type="checkbox" v-model="isAll"> <label for="toggle-all"></label> <!-- 3.0 Keyboard events-Enter key 3.1 Input box - v-model Get value --> <input class="new-todo" placeholder="Enter task name-Enter to confirm" autofocus @keydown.enter="downFn" v-model="task" /> </header> </template> <script> // 3. Objective - new task export default { data(){ return { task: "" } }, methods: { downFn(){ if (this.task.trim().length === 0) { alert("Task name cannot be empty"); return; } // 3.2 (important) - the name of the current task should be added to the list array // Son to father Technology this.$emit("create", this.task) this.task = "" } } } </script>
App.vue
1. Receive the create trigger function
2. Judge whether the list is empty. If it is empty, the newly added list will start from 100. If it is not empty, judge to start recording from the last id+1
3. Add the received value to the new object and store it in the list
<TodoHeader @create="createFn"></TodoHeader> methods: { createFn(taskName){ // Add task // 3.3 push to array let id = this.list.length == 0 ? 100 : this.list[this.list.length - 1].id + 1 this.list.push({ id: id, name: taskName, isDone: false }) }, }
todos delete task
The laying data and new data are all completed. The next task is to delete
Implement point x and delete task function
① Distinguished x tag – click event – incoming id
② : pass the child to the parent, return the id to – App.vue – delete a corresponding object in the array list
③ : if the original array is changed, all the places used will be updated
App.vue - incoming custom event waiting to receive sequence number to be deleted
<TodoMain :arr="showArr" @del="deleteFn"></TodoMain> methods: { deleteFn(theId){ // Delete task let index = this.list.findIndex(obj => obj.id === theId) this.list.splice(index, 1) }, },
TodoMain.vue - pass the id back to delete (delete the data wherever you want)
<!-- 4.0 Register click events --> <button class="destroy" @click="delFn(obj.id)"></button> methods: { delFn(id){ // 4.1 son to father this.$emit('del', id) } }
What technical points are involved?
-
Click the event and send it to the index
-
Son to father
-
Array delete an element, v-for update
todos bottom statistics
Count the number of current tasks
① : in App.vue – array list – passed to TodoFooter.vue
② : display / define calculation properties directly on the label for display
③ : as long as the original array is changed, all places where this array is used will be updated
TodoFooter.vue - receive list statistics and display directly
<template> <footer class="footer"> <span class="todo-count">surplus<strong>{{ count }}</strong></span> <ul class="filters"> <li> <a class="selected" href="javascript:;">whole</a> </li> <li> <a href="javascript:;">hang in the air</a> </li> <li> <a href="javascript:;">Completed</a> </li> </ul> <button class="clear-completed">Cleanup completed</button> </footer> </template> <script> export default { // 5.0 props definition props: ['farr'], // 5.1 calculation attribute - task quantity computed: { count(){ return this.farr.length } }, } </script> <style> </style>
App.vue incoming data
<TodoFooter :farr="showArr"></TodoFooter>
-
Father to son
-
Calculation properties
-
Array update – affected everywhere
todos data switching
target
Click the bottom to switch data
explain
-
Requirement 1: click the switch at the bottom - who has a border
-
Requirement 2: switch different data display correspondingly
analysis:
① : TodoFooter.vue – define isSel – the values are all, yes, no, one of them
② selected: multiple classes determine who should have the class name
③ : Click to modify the value of isSel
④ : pass the child to the parent, and transfer the type isSel to App.vue
⑤ : define the calculation attribute showArr to determine which data to display in the list to TodoMain.vue and TodoFooter.vue
App.vue
<TodoFooter :farr="showArr" @changeType="typeFn"></TodoFooter> <script> export default{ data(){ return { // ... other omitted getSel: "all" // Show all by default } }, methods: { // ... other omitted typeFn(str){ // 'all', 'yes',' no '/ / modify type this.getSel = str }, }, // 6.5 define showArr array - filtered by list matching conditions computed: { showArr(){ if (this.getSel === 'yes') { // Show completed return this.list.filter(obj => obj.isDone === true) } else if (this.getSel === 'no') { // Show incomplete return this.list.filter(obj => obj.isDone === false) } else { return this.list // show all } } }, } </script>
TodoFooter.vue
<template> <footer class="footer"> <span class="todo-count">surplus<strong>{{ count }}</strong></span> <ul class="filters" @click="fn"> <li> <!-- 6.1 Determine who should have a highlighted style: dynamic class 6.2 User clicks to switch isSel Values saved in --> <a :class="{selected: isSel === 'all'}" href="javascript:;" @click="isSel='all'">whole</a> </li> <li> <a :class="{selected: isSel === 'no'}" href="javascript:;" @click="isSel='no'">hang in the air</a> </li> <li> <a :class="{selected: isSel === 'yes'}" href="javascript:;" @click="isSel='yes'">Completed</a> </li> </ul> <!-- 7. target: Cleanup completed --> <!-- 7.0 Click event --> <button class="clear-completed" >Cleanup completed</button> </footer> </template> <script> // 5. Objective: quantity statistics export default { // 5.0 props definition props: ['farr'], // 5.1 calculation attribute - task quantity computed: { count(){ return this.farr.length } }, // 6. Goal: who lights up // 6.0 variable isSel data(){ return { isSel: 'all' // All: 'all', completed 'yes', incomplete' no ' } }, methods: { fn(){ // Toggle filter criteria // 6.3 child - > parent type string passed to App.vue this.$emit("changeType", this.isSel) } } } </script>
What technical points are involved?
-
Dynamic class – cooperation judgment
-
Son to father Technology
-
Calculation attribute + array filtering method
todos emptying completed
Click the button in the lower right corner - clear the completed tasks
explain
-
Requirements: click the link tab in the lower right corner to clear the completed tasks
analysis:
① : empty tab – click event
② : child to parent – App.vue – an emptying method
③ : filter the incomplete overlay list array (regardless of recovery)
App.vue - first pass in a custom event - because you have to receive the click event in TodoFooter.vue
<TodoFooter :farr="showArr" @changeType="typeFn" @clear="clearFun"></TodoFooter> <script> methods: { // ... omit other clearFun(){ // Cleanup completed this.list = this.list.filter(obj => obj.isDone == false) } } </script>
TodoFooter.vue
<!-- 7. target: Cleanup completed --> <!-- 7.0 Click event --> <button class="clear-completed" @click="clearFn">Cleanup completed</button> <script> methods: { clearFn(){ // Empty completed tasks // 7.1 trigger clearFun method corresponding to event in App.vue this.$emit('clear') } } </script>
-
Son to father Technology
-
Array filtering method
todos data cache
We don't have a server for the time being. We can't store these data In this case, the data disappears as soon as it is refreshed
Here we store the data into the browser cache As long as the browser cache data is not cleared, it will always exist
Similar token values can be stored in this way
After adding / modifying status / deleting, synchronize the data to the local storage of the browser immediately
explain
-
Requirements: no matter what changes – ensure that the data is still after refresh
analysis:
① : App.vue - listening list array change - depth
② : overwrite save locally – note that only JSON strings can be saved locally
③ : refresh the page – the list should take value locally by default – empty array without data should be considered
App.vue
<script> export default { data(){ return { // 8.1 default value from local list: JSON.parse(localStorage.getItem('todoList')) || [], // 6.4 first transfer receiving type string getSel: "all" // Show all by default } }, // 8. Target: data cache watch: { list: { deep: true, handler(){ // 8.0 as long as the list changes - overwrite save to localStorage localStorage.setItem('todoList', JSON.stringify(this.list)) } } } }; </script>
-
Deep listening
-
Data cache
-
serialization and deserialization
Then it's the last point
Select all function
todos select all function
Click the v sign in the upper left corner to set one key to complete, and then click again to cancel the selection of all
explain
-
Requirement 1: click Select all – the small selection box is affected
-
Requirement 2: select all the small boxes (manually) – select all and select automatically
analysis:
① : TodoHeader.vue – calculate properties - isAll
② : App.vue – pass in the array list – affect the small checkbox in the set of isAll
③ : count the last status of the small selection box in the get of isAll, affect isAll – affect the status of all selection
④ : consider the empty array without data - select all and should not be checked
Tip: it is to traverse all objects and modify the value of their completion status attribute
TodoHeader.vue
<!-- 9. target: Select all status 9.0 v-model Associated select all status Page change(Tick true, Unchecked false) -> v-model -> isAll variable --> <input id="toggle-all" class="toggle-all" type="checkbox" v-model="isAll"> <script> export default { // ... other omitted // 9.1 defining calculation attributes computed: { isAll: { set(checked){ // Only true / false // 9.3 isDone attribute that affects the binding of each small checkbox in the array this.arr.forEach(obj => obj.isDone = checked) }, get(){ // 9.4 statistical status of small selection box - > select all box // 9.5 if there is no data, directly return false - do not make all checked return this.arr.length !== 0 && this.arr.every(obj => obj.isDone === true) } } }, } </script>
App.vue
<TodoHeader :arr="list" @create="createFn"></TodoHeader>
What technical points are involved?
-
Calculation attribute complete writing method
-
The every method returns true if it does not traverse
Then the rendering is completed, and there is no waste of traffic at the front
I will write a separate article and put the full version of the code. Let's study it by ourselves
So tired