1. Analyze the entire Todolist feature

The illustration

2. The single arrow represents the process within the attached starting point. The second figure is a continuation of the single arrow in the first figure. The upper left corner of Figure 1 shows the HTML structure broken down by the content of the page

Functional analysis

  • Add a todo item
  • Delete the todo item
  • Modify the todo item
  • Complete the todo item
  • Delete completed TOdo items with one click
  • Displays todo items by category
  • Displays the total number of remaining TODO entries

2. Code analysis

2.1 Body section of Html

The header section (sectionclass="todoapp">
            <header class="header">
                <h1>todos</h1>
                <input class="new-todo" autofocus autocomplete="off" placeholder="To do" v-model="newTodo" @keyup.enter="addTodo">
            </header>
Copy the code
The main part of the <! <section class="main" v-show="todos.length" v-cloak> <! -- V-model implements two-way data binding --> <! - allDone: When I click on the checkbox, <input class='toggle-all' type="checkbox" V-model ='allDone'> <ul class="todo-list"> <! -- Class: If todo.completed is true, execute completed; Editing if todo==editedTodo, meaning you are editing --> <! <li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{completed:todo.completed,editing:todo==editedTodo}"> <div class="view"> <! <input type="checkbox" class="toggle" v-model="todo.completed"> Dblclick ="editTodo(todo)">{{todo.title}}</label> <! <button class="destroy" @click="removeTodo(todo)"></button> </div> <! -- This is a hidden input box that only occurs when editing and is bidirectionally bound to todo.title --> <! -- @blur is when an element loses focus, in this case todo.title loses focus --> <! -- Keyboard function: While editing, press Esc key to cancel changes --> <! -- BUG: When keyup is triggered, blur is also triggered, so the two cannot be bound to the same function. Blur events are triggered by keyup --> <! -- BUG: Todo still has CSS checkbox effect after Enter, considering the user interaction experience should be uncheckbox effect after Enter --> <! BUG: - When the input box is empty, delete todo=>editedTodo is empty and return directly in doneEdit, <input type="text" class="edit" v-model="todo.title" v-todo-focus="todo == editedTodo" @blur="doneEdit(todo)" @keyup.enter="$event.target.blur" @keyup.esc="cancelEdit(todo)"> </li> </ul> </section>Copy the code
<span class="todo-count"> <span class="todo-count"> <span class="todo-count"> -- Pluralize, like Remaining, Is one of app instance attributes - > < strong > {{remaining}} < / strong > {{remaining | pluralize}} left < / span > < ul class = "filters" > < li > < a href="#/all" :class="{selected:visibility=='all'}">All</a></li> <li><a href="#/active" :class="{selected:visibility=='active'}">Active</a></li> <li><a href="#/completed" :class="{selected:visibility=='completed'}">Completed</a></li> </ul> <button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">Clear Completed</button> </footer> </section>Copy the code

2.2 the Vue. Js content

Window. The onload = function () {... }Copy the code

Put all BOM and DOM operations in it. Onload ensures that the code in it is done after the BODY page of the HTML is loaded, so the script can be anywhere on the page.

var STORAGE_KEY = 'todos - vuejs - 2.0'
                var todoStorage = {
                    fetch:function(){
                        var todos = JSON.parse(localStorage.getItem(STORAGE_KEY)||'[]')
                        todos.forEach(function(todo,index){
                            todo.id = index
                        })
                        todoStorage.uid = todos.length
                        return todos
                    },
                    save: function (todos) {
                        localStorage.setItem(STORAGE_KEY,JSON.stringify(todos))
                    }
                }
Copy the code

This is the BOM operation that interacts with the browser’s localSotrage to store the entire toDOS data in Local. Fetch: Fetch todos stored in local and list the contents of each toDO save: Store the entire toDOS in local. This operation is repeated with each toDO change

var filters = { all: function(todos){ return todos }, active:function(todos){ return todos.filter(function(todo){ return ! todo.completed }) }, Completed :function(todos){return todo.filter (function(todo){return todo.completed})}}//.filter() : Callback is a native JS method that calls the callback function for each element in the array, and creates a new array with all elements whose value causes callback to return true.Copy the code

Filters are global variables in which todos are classified. The properties inside will be represented and filtered using [visibility], which is independent of the instance because it involves a BOM operation.

                / / instance
                var app = new Vue({
                    // Initial state of the instance
                    // The initial visibility is all
                    // Todos is also a heat value fetched from storage
                    data: {todos:todoStorage.fetch(),
                        newTodo: ' '.editedTodo: null.visibility: 'all'
                    },
Copy the code
                    // Listen for attributes
                    watch: {todos: {handler:function(todos){
                                todoStorage.save(todos)
                            },
                            deep:true}},// Handler method: the operation performed when data changes
                    Imediate: True Does not perform the action by default
                    //deep: true Deep listening, can listen for changes in the internal properties of the object
Copy the code
                  // A return value is required to evaluate a property. This applies to property values that need to be changed in real time or checkbox values that need to be passed in
                    computed: {filteredTodos:function(){// Execute the filters method on todos
                            return filters[this.visibility](this.todos)
                        },
                        remaining:function(){// Remaining todo
                            return filters.active(this.todos).length
                        },
                        allDone: {get:function(){// Kill all unfinished todos
                                return this.remaining === 0
                            },
                            set:function(value){// Set the completed property of each todo to the passed value, which is true
                                this.todos.forEach(function(todo){
                                    todo.completed=value
                                })
                            }
                            }
                        },
Copy the code
                    / / contains Vue instance available filters in the hash table {{xx | pluralize}}
                    filters: {pluralize:function(n){
                            return n === 1 ? 'item':'items'}},Copy the code
                     This action is performed after the method event is triggered, such as @click@blur @keyupInput box
                    methods: {addTodo: function(){
                            var value = this.newTodo && this.newTodo.trim()
                            if(! value){// End the function call, return to the point of call, return nothing
                                return
                            }
                            this.todos.push({
                                id:todoStorage.uid++,
                                title:value,
                                completed:false
                            })
                            this.newTodo = ' ';
                        },
                        removeTodo: function(todo){
                            // Delete the corresponding todo from todos
                            this.todos.splice(this.todos.indexOf(todo),1)},editTodo: function(todo){
                            this.beforeEditCache=todo.title
                            this.editedTodo = todo
                        },
                        doneEdit: function(todo){
                            if(! todo.editedTodo){return
                            }
                            this.editedTodo = null;
                            todo.title = todo.title.trim();
                            if(! todo.title){// If the content is empty, delete todo
                                this.removeTodo(todo)
                            }
                        },
                        cancelEdit:function(todo){
                            this.editedTodo=null
                            todo.title=this.beforeEditCache
                        },
                        removeCompleted:function(){
                            // reassign the contents of todos
                            this.todos=filters.active(this.todos)
                        }
                    },
Copy the code
                    // Custom components -- Hook functions: hash tables containing instructions available in Vue instances
                    //el: the element bound by the directive that can be used to manipulate the DOM directly.
                    // binding: an object containing the following attributes:
                    // name: indicates the command name, excluding the v- prefix. Todo - focus here
                    // value: the binding value of the directive, where v-todo-focus="todo == editedTodo", value is the true or false of the expression
                    // This component checks whether todo is equal to editedTodo, i.e. focus immediately after the double click, and it is called only once,
                    Called when the directive is first bound to an element. You can use this hook function to define an initialization action that is performed once at binding time.
                    directives: {'todo-focus':function(el,binding){
                            if(binding.value){
                                el.focus()
                            }
                        }
                    }
                })
Copy the code

So that’s the content of the example App.

                //window.location.hash retrieves the hash value of the URL (#/ XXXX), which changes with the href in the footer
                function onHashChange() {
                    var visibility = window.location.hash.replace(/ # \ /? /.' ')
                    // Visibility is retrieved from the hash above the filters
                    if(filters[visibility]){
                        app.visibility=visibility
                        // The above =visibility is the var created in this method
                    }else{
                        window.location.hash=' '
                        app.visibility='all'}}// Once the hash of the address bar changes, do onHashChange
                window.addEventListener('hashchange',onHashChange)
                onHashChange()
Copy the code

This is a BOM operation that interacts with the value of the URL.

                // After the instance is mounted, the element can be accessed with vm.$el.
                // If el is instantiated, the instance will start compiling immediately; otherwise, you need to explicitly call vm.$mount() to start compiling manually.
                app.$mount('.todoapp')}Copy the code

This is where the script ends.

2.3 CSS content

/* Element selector */
html,
body {
	margin: 0;
	padding: 0;
}

button {
	position: relative;
	margin-left: 0;
	padding: 0;
	border: 0;
	background: none;
	font-size: 20px;
	vertical-align: baseline;
	font-family: inherit;
	font-weight: inherit;
	color: rgba(175.47.47.0.15);
	-webkit-appearance: none;
	appearance: none;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}
/* -webkit-font-smoothing: Modify the serrated font */
/ * - moz - osx - the font - smoothing: inherit | grayscale = "Gecko kernel browser firefox, for ios system made a rendering rules * /
/* In IELT, the checkbox is checked unchecked due to CSS "-webkit-appearance: None;" -webkit-appearance: checkbox * /
/* All major browsers do not support the appearance attribute. Firefox supports the alternative -moz-appearance attribute. Safari and Chrome support the alternative -webkit-appearance attribute. * /
body {
	font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
	line-height: 1.4em;
	background: #f5f5f5;
	color: #4d4d4d;
	min-width: 230px;
	max-width: 550px;
	margin: 0 auto;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	font-weight: 300;
}
/* Max and min-width can limit the maximum and minimum size of elements, and there is a limit to the size of the page, which is helpful for responsive pages */
/* margin: 0 auto= "margin: 0 auto=

/* False element selector */
/* focus the entire input CSS design */
:focus {
	outline: 0;
}
/* The outline property is closely related to focus state and keyboard access, and is functionally similar to border */

/* Class selector */
.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: #e6e6e6;
}

.todoapp input::-moz-placeholder {
	font-style: italic;
	font-weight: 300;
	color: #e6e6e6;
}

.todoapp input::input-placeholder {
	font-style: italic;
	font-weight: 300;
	color: #e6e6e6;
}

.todoapp h1 {
	position: absolute;
	top: -155px;
	width: 100%;
	font-size: 100px;
	font-weight: 100;
	text-align: center;
	color: rgba(175.47.47.0.15);
	-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;
	border: 0;
	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;
}
/* Property selector */
label[for='toggle-all'] {
	display: none;
}

.toggle-all {
	position: absolute;
	top: -55px;
	left: -12px;
	width: 60px;
	height: 34px;
	text-align: center;
	border: none; /* Mobile Safari */
}

.toggle-all:before {
	content: '❯';
	font-size: 22px;
	color: #e6e6e6;
	padding: 10px 27px 10px 27px;
}

.toggle-all:checked: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;
}

/* The border at the bottom of the entire li will disappear */
.todo-list li.editing {
	border-bottom:none;
	padding: 0;
}
/* Displays the input box */ when the edit process is performed
.todo-list li.editing .edit {
	display: block;
	width: 506px;
	padding: 12px 16px;
	margin: 0 0 0 43px;
}
/* When editing happens, the entire div is not displayed. Instead, display the following input box */
.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: 0; /* Mobile Safari */
	-webkit-appearance: none;
	appearance: none;
}

.toggle:before {
	background-image: url('./public/check-circle-regular.svg');
  	background-size: 100%;
	display: inline-block;
	margin-right: -7px;
	margin-top:12px;
	vertical-align: 3px;
	height: 16px;
	width: 16px;
	content: "";
}

.toggle:checked:before {
	background-image: url('./public/check-circle-solid.svg');
	background-size: 100%;
	display: inline-block;
	margin-right: -7px;
	margin-top:12px;
	vertical-align: -3px;
	height: 16px;
	width: 16px;
	content: "";
}



.todo-list li label {
	word-break: break-all;
	padding: 15px 60px 15px 15px;
	margin-left: 45px;
	display: block;
	line-height: 1.2;
	transition: color 0.4s;
}

.todo-list li.completed label {
	color: #d9d9d9;
	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: The '*';
}

.todo-list li:hover .destroy {
	display: block;
}

.todo-list li .edit {
	display: none;
}

.todo-list li.editing:last-child {
	margin-bottom: -1px;
}

.footer {
	color: #777;
	padding: 10px 15px;
	height: 20px;
	text-align: center;
	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: #bfbfbf;
	font-size: 10px;
	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;
}


@media screen and (-webkit-min-device-pixel-ratio:0) {
	.toggle-all,
	.todo-list li .toggle {
		background: none;
	}

	.todo-list li .toggle {
		height: 40px;
	}

	.toggle-all {
		-webkit-transform: rotate(90deg);
		transform: rotate(90deg);
		-webkit-appearance: none;
		appearance: none;
	}
}

@media (max-width: 430px) {
	.footer {
		height: 50px;
	}

	.filters {
		bottom: 10px; }}Copy the code