Tutorial: todomvc and director routing

Time:2021-9-30

summary

mvcIt usually cooperates with model, view and controller. Vue itself has been biased towards the view render layer, so this example useslocalStorageAs data persistence memory,directorThe routing library acts as a controller and is responsible for the rendering and distribution scheduling of page nodes, so as to realize a simple todomvc application.

Knowledge points

  • vuejsAddition, deletion, modification and query of single pageTodoMVC
  • vueCustom instruction, observation function, calculation attribute, method use
  • localStorageObject combinationJSONSerialization and deserialization as persistence layer
  • directorIt does not rely on the lightweight front-end routing library and can route on the server / client / command line

route

  • Regular matching routing, parameter listening
  • directorHash based routing methods, such as/a/cMatching corresponding pathindex.php#/a/c
Router({
    '/(active|completed|all)/?': function (filter) {
        //Indirectly immerse views through Vue instances
        app.visibility = filter
    }
}).init()

Data persistence

var STORAGE_KEY =  'pardon110'
var todoStorage = {
    fetch() {
        var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
        todos.forEach((todo, index) => todo.id = index);
        todoStorage.uid = todos.length
        return todos
    },
    save(todos) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
    }
}

design sketch

Tutorial: todomvc and director routing

Source code

  • app.d.js
var STORAGE_KEY = 'pardon110'
//Memory instance object
var todoStorage = {
    fetch() {
        var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
        todos.forEach((todo, index) => todo.id = index);
        todoStorage.uid = todos.length
        return todos
    },
    save(todos) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
    }
}
//Visibility filtering
var filters = {
    all: todos => todos,
    active: todos => todos.filter(todo => !todo.completed),
    completed: todos => todos.filter(todo => todo.completed)
}

var app = new Vue({
    //Initialize application status
    data: {
        todos: todoStorage.fetch(),
        newTodo: '',
        editedTodo: null,
        visibility: 'all'
    },
    //Observe persistent storage
    watch: {
        todos: {
            //Note that you should not use the arrow function to define the watcher function 
            //The arrow function is bound to the context of the parent scope, and its this will not point to the Vue instance as expected
            handler: todoStorage.save,
            //The callback is called when the property of any object being listened on changes, no matter how deep it is nested
            deep: true
        }
    },
    //Calculation properties
    computed: {
        filteredTodos() {
            return filters[this.visibility](this.todos)
        },
        remaining() {
            return filters.active(this.todos).length
        },
        // getter/setter
        allDone: {
            get() {
                return this.remaining === 0
            },
            set(value) {
                this.todos.forEach(todo => todo.completed = value)
            }
        }
    },

    //View filter, type for pipe|
    filters: {
        //Complex conversion
        pluralize: function (n) {
            return n === 1 ? 'item' : 'items'
        }
    },

    //Realize the data addition, deletion and modification logic, and pay attention not to directly operate the DOM node
    methods: {
        addTodo() {
            var value = this.newTodo && this.newTodo.trim()
            if (!value) {
                return
            }
            this.todos.push({
                id: todoStorage.uid++,
                title: value,
                completed: false
            })
            this.newTodo = ''
        },

        removeTodo(todo) {
            this.todos.splice(this.todos.indexOf(todo), 1)
        },

        //Edit data
        editTodo(todo) {
            //Cache content before editing
            this.beforeEditCache = todo.title
            //Keep edited content
            this.editedTodo = todo
        },
        doneEdit(todo) {
            if (!this.editedTodo) {
                return
            }
            this.editedTodo = null
            todo.title = todo.title.trim()
            if (!todo.title) {
                this.removeTodo(todo)
            }
        },
        cancelEdit(todo) {
            this.editedTodo = null
            todo.title = this.beforeEditCache
        },

        removeCompleted() {
            this.todos = filters.active(this.todos)
        }
    },

    //Customize Vue instructions to achieve aggregation effect before entering fields
    directives: {
        'todo-focus': function (el, binding) {
            if (binding.value) {
                el.focus()
            }
        }
    }
})

//Register the route using the director library
Router({
    '/(active|completed|all)/?': function (filter) {
        //Change Vue each corresponding data and render the view
        app.visibility = filter
    }
}).init()

//Mount DOM node on Vue instance
app.$mount('.todoapp')
  • index.htmlfile
<!DOCTYPE html>
<html data-framework="vue">
<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>Vue.js • TodoMVC</title>
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/index.css">
    <!--  Front end routing Library (supporting spa application) - >
    <script src="https://cdn.bootcss.com/Director/1.2.8/director.js"></script>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <section class="todoapp">
        <header class="header">
            <h1>todos</h1>
            <!--  Bidirectional binding add -- >
            < input V-model = "newtodo" placeholder = "write something?" autofocus autocomplete = "off" @ Keyup. Enter = "addtodo"
                class="new-todo">
        </header>

        <section class="main" v-show="todos.length" v-cloak>
            <!--  Select all / unselect all switch -- >
            <input type="checkbox" class="toggle-all" v-model="allDone">
            <!--  Display list -- >
            <ul class="todo-list">
                <li v-for="todo in filteredTodos" :key="todo.id"
                    :class="{ completed: todo.completed, editing: todo == editedTodo }">
                    <div class="view">
                        <!--  Single selected and unselected -- >
                        <input type="checkbox" class="toggle" v-model="todo.completed">
                         <!--  Double click the entry to edit todo -- >
                        <label @dblclick="editTodo(todo)">{{ todo.title }}</label>
                         <!--  Click the delete button -- >
                        <button class="destroy" @click="removeTodo(todo)"></button>
                    </div>
                    <input type="text" class="edit" v-model="todo.title" v-todo-focus="todo == editedTodo"
                        @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)" @keyup.esc="cancelEdit(todo)">
                </li>
            </ul>
        </section>

        <footer class="footer" v-show="todos.length" v-cloak>
            <span class="todo-count">
                <strong> {{ remaining }}</strong>
                <!--  Filter pipe -- >
                {{ remaining | pluralize }} left
            </span>

            <!-- Use the director library for routing, automatic distribution scheduling, trigger Vue instance callback, and indirectly render the view -- >
            <ul class="filters">
                <li>
                    < a href = "# / all": class = "{selected: visibility = ='All '}" > all</a>
                    < a href = "# / active": class = "{selected: visibility = ='active '}" > active</a>
                    < a href = "# / completed": class = "{selected: visibility = ='completed '}" > recycle bin</a>
                </li>
            </ul>
            <button class="clear-completed" @click="removeCompleted" v-show="todos.length >  remaining">
                empty
            </button>
        </footer>
    </section>
    <footer class="info">
        <p>Double click to edit a todo application</p>
        <p>Written by <a href="http://evanyou.me">Evan You</a></p>
        <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="app.d.js"></script>
</body>
</html>

This work adoptsCC agreement, reprint must indicate the author and the link to this article