js事件委托、时间操作、本地化存储@2018-06-23

TODO应用


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><!DOCTYPE html>
        <html>
        <head>
            <meta charset="utf-8">
            <title>上课用品 8</title>
            <style>
                /* 完成的样式 */
                .done {
                    color: gray;
                    text-decoration: line-through;
                }
            </style>
        </head>
<body>
<div class="todo-main">
    <!-- todo 输入框 -->
    <div class="todo-form">
        <input id='id-input-todo' type="text">
        <button id='id-button-add' type="button">Add</button>
    </div>
    <!-- todo 列表 -->
    <div id="id-div-container">
        <!-- <div class='todo-cell'>
            <button class='todo-done'>完成</button>
            <button class='todo-delete'>删除</button>
            <span contenteditable='true'>上课</span>
        </div> -->
    </div>
</div>
<script>

    // 通过一个 Todo 应用, 学习下面这个概念
    // 1, 什么是事件委托
    // 2, 为什么需要事件委托
    // 3, 如何实现事件委托
    //
    // 时间操作
    // content editable (标签的可编辑属性)
    // localStorage (本地存储) 和 JSON 格式



    // 自己定义一个 log 函数
    var log = function() {
        console.log.apply(console, arguments)
    }

    var todoList = []

    // 给 add button 绑定添加 todo 事件
    var addButton = document.querySelector('#id-button-add')
    addButton.addEventListener('click', function(){
        // 获得 input.value
        var todoInput = document.querySelector('#id-input-todo')
        var task = todoInput.value
        // 生成 todo 对象
        var todo = {
            'task': task,
            'time': currentTime()
        }
        todoList.push(todo)
        saveTodos()
        insertTodo(todo)
    })

    var insertTodo = function(todo) {
        // 添加到 container 中
        var todoContainer = document.querySelector('#id-div-container')
        var t = templateTodo(todo)
        // 这个方法用来添加元素更加方便, 不需要 createElement
        todoContainer.insertAdjacentHTML('beforeend', t);
    }

    var templateTodo = function(todo) {
        var t = `
        <div class='todo-cell'>
            <button class='todo-done'>完成</button>
            <button class='todo-delete'>删除</button>
            <span contenteditable='true'>${todo.task}</span>
            <span>${todo.time}</span>
        </div>
    `
        return t
    }

    // 事件委托相关概念
    // ===
    //
    // 问题在于, todo 都是运行的时候才添加的元素
    // 对于这样的元素, 我们没办法事先绑定事件
    // 我们可以把 click 事件绑定在事先存在的父元素上
    // 然后在运行的时候检查被点击的对象(通过 event.target 属性)
    // 是否是我们需要的对象, 这个概念就是事件委托

    var todoContainer = document.querySelector('#id-div-container')

    todoContainer.addEventListener('keydown', function(event){
        log('container keydown', event, event.target)
        var target = event.target
        if(event.key === 'Enter') {
            log('按了回车')
            // 失去焦点
            target.blur()
            // 阻止默认行为的发生, 也就是不插入回车
            event.preventDefault()
            // 更新 todo
            var index = indexOfElement(target)
            log('update index',  index)
            // 把元素在 todoList 中更新
            todoList[index].task = target.innerHTML
            // todoList.splice(index, 1)
            saveTodos()

        }
    })

    // 通过 event.target 的 class 来检查点击的是什么
    todoContainer.addEventListener('click', function(event){
        log('container click', event, event.target)
        var target = event.target
        if(target.classList.contains('todo-done')) {
            log('done')
            // 给 todo div 开关一个状态 class
            var todoDiv = target.parentElement
            toggleClass(todoDiv, 'done')
        } else if (target.classList.contains('todo-delete')) {
            log('delete')
            var todoDiv = target.parentElement
            var index = indexOfElement(target)
            log('delete index',  index)
            todoDiv.remove()
            // 把元素从 todoList 中 remove 掉
            // delete todoList[index]
            todoList.splice(index, 1)
            saveTodos()
        }
    })

    // 保存 todoList
    var saveTodos = function() {
        var s = JSON.stringify(todoList)
        localStorage.todoList = s
    }

    var loadTodos = function() {
        var s = localStorage.todoList
        return JSON.parse(s)
    }

    // 返回自己在父元素中的下标
    var indexOfElement = function(element) {
        var parent = element.parentElement
        for (var i = 0; i < parent.children.length; i++) {
            var e = parent.children[i]
            if (e === element) {
                return i
            }
        }
    }

    // 这个函数用来开关一个元素的某个 class
    var toggleClass = function(element, className) {
        if (element.classList.contains(className)) {
            element.classList.remove(className)
        } else {
            element.classList.add(className)
        }
    }


    // localStorage 可以用来存储字符串数据, 在浏览器关闭后依然存在
    // 存储方法如下
    // localStorage.name = 'gua'
    // 关闭浏览器, 再次打开, 仍然能获取到这个值
    log('关闭浏览器后', localStorage.name)
    var todos = ['吃饭', '喝水', '上课']
    //
    // 利用 localStorage 就可以存储 todo
    // 但是 todo 存在 array 中
    // 而 localStorage 只能存储 string 数据
    // 所以没办法直接存储
    //
    // 可行的办法如下
    // 存储的时候把 array 转换为字符串
    // 读取的时候把字符串转成 array
    // 这个过程通常被称之为 序列化 和 反序列化
    //
    // 在 js 中, 序列化使用 JSON 格式

    var s = JSON.stringify([1, 2, 3, 4])
    log('序列化后的字符串', typeof s, s)
    var a = JSON.parse(s)
    log('反序列化后的数组', typeof a, a)

    // 使用 JSON 序列化后, 就可以把 todo 存入浏览器的 localStorage 了

    // {
    //     task: '',
    //     time: '',
    // }

    var currentTime = function() {
        var d = new Date()
        var month = d.getMonth() + 1
        var date = d.getDate()
        var hours = d.getHours()
        var minutes = d.getMinutes()
        var seconds = d.getSeconds()
        var timeString = `${month}/${date} ${hours}:${minutes}:${seconds}`
        return timeString
    }
    // 时间标准库
    // ===
    // 常用用法如下
    /*
    var d = new Date()
    d.getFullYear()
    年份, 2016
    d.getMonth()
    月份, 0-11
    d.getDate()
    日期, 1-31
    d.getHours()
    小时, 0-23
    d.getMinutes()
    分钟, 0-59
    d.getSeconds()
    秒数, 0-59
    d.getMilliseconds()
    毫秒, 0-999
    d.getDay()
    星期几, 0-6
    */

    // 程序加载后, 加载 todoList 并且添加到页面中
    todoList = loadTodos()
    for (var i = 0; i < todoList.length; i++) {
        var todo = todoList[i]
        insertTodo(todo)
    }
</script>
</body>
</html>

效果:
这里写图片描述


改版Todo应用程序


<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>上课用品 8</title>
        <style>
            /* 完成的样式 */
            .done {
                color: gray;
                text-decoration: line-through;
            }
        </style>
    </head>
    <body>
        <div class="todo-main">
            <!-- todo 输入框 -->
            <div class="todo-form">
                <input id='id-input-todo' type="text">
                <button id='id-button-add' type="button">Add</button>
            </div>
            <!-- todo 列表 -->
            <div id="id-div-container">
                <!-- <div class='todo-cell'>
                    <button class='todo-done'>完成</button>
                    <button class='todo-delete'>删除</button>
                    <span contenteditable='true'>上课</span>
                </div> -->
            </div>
        </div>
        <script src='todo.js'></script>
    </body>
</html>

todo.js

// 自己定义一个 log 函数
var log = function() {
    console.log.apply(console, arguments)
}

// 给 add button 绑定添加 todo 事件
var bindEventAdd = function() {
    log('bind add button')
    var addButton = document.querySelector('#id-button-add')
    log('add button', addButton)
    addButton.addEventListener('click', function(){
        log('click add')
        // 获得 input.value
        var todoInput = document.querySelector('#id-input-todo')
        var task = todoInput.value
        // 生成 todo 对象
        var todo = {
            'task': task,
            'time': currentTime()
        }
        todoList.push(todo)
        saveTodos()
        insertTodo(todo)
    })
}

var bindEventEnter = function() {
    var todoContainer = document.querySelector('#id-div-container')
    todoContainer.addEventListener('keydown', function(event){
        log('container keydown', event, event.target)
        var target = event.target
        if(event.key === 'Enter') {
            log('按了回车')
            // 失去焦点
            target.blur()
            // 阻止默认行为的发生, 也就是不插入回车
            event.preventDefault()
            // 更新 todo
            var index = indexOfElement(target.parentElement)
            log('update index',  index)
            // 把元素在 todoList 中更新
            todoList[index].task = target.innerHTML
            // todoList.splice(index, 1)
            saveTodos()

        }
    })
}

var bindEventButton = function() {
    // 通过 event.target 的 class 来检查点击的是什么
    var todoContainer = document.querySelector('#id-div-container')
    todoContainer.addEventListener('click', function(event){
        log('container click', event, event.target)
        var target = event.target
        if(target.classList.contains('todo-done')) {
            log('done')
            // 给 todo div 开关一个状态 class
            var todoDiv = target.parentElement
            toggleClass(todoDiv, 'done')
        } else if (target.classList.contains('todo-delete')) {
            log('delete')
            var todoDiv = target.parentElement
            var index = indexOfElement(target.parentElement)
            log('delete index',  index)
            todoDiv.remove()
            // 把元素从 todoList 中 remove 掉
            // delete todoList[index]
            todoList.splice(index, 1)
            saveTodos()
        } else if (target.classList.contains('todo-edit')) {
            log('edit')
            var cell = target.parentElement
            var span = cell.children[3]
            log('span is ', span)
            span.setAttribute('contenteditable', 'true')
            // span.contentEditable = true
            span.focus()
        }
    })
}

var GuaEventType = {
    blur: 'blur',
    click: 'click',
}
// EventType.blur
// EventType.click

var bindEventBlur = function() {
    log('bind event blur function')
    var todoContainer = document.querySelector('#id-div-container')
    todoContainer.addEventListener(GuaEventType.blur, function(event){
        log('container blur', event, event.target)
        var target = event.target
        if (target.classList.contains('todo-label')) {
            log('update and save')
            // 让 span 不可编辑
            target.setAttribute('contenteditable', 'false')
            // 更新 todo
            var index = indexOfElement(target.parentElement)
            log('update index',  index)
            // 把元素在 todoList 中更新
            todoList[index].task = target.innerHTML
            // todoList.splice(index, 1)
            saveTodos()
        }
    }, true)
}

var bindEvents = function() {
    // 添加 todo
    bindEventAdd()
    // 文本框输入 todo 按回车保存
    bindEventEnter()
    // 完成按钮和删除按钮
    bindEventButton()
    // 文本框失去焦点后保存 todo
    bindEventBlur()
}


var insertTodo = function(todo) {
    // 添加到 container 中
    var todoContainer = document.querySelector('#id-div-container')
    var t = templateTodo(todo)
    // 这个方法用来添加元素更加方便, 不需要 createElement
    todoContainer.insertAdjacentHTML('beforeend', t);
}

var templateTodo = function(todo) {
    var t = `
        <div class='todo-cell'>
            <button class='todo-done'>完成</button>
            <button class='todo-delete'>删除</button>
            <button class='todo-edit'>编辑</button>
            <span class='todo-label' contenteditable='false'>${todo.task}</span>
            <span>${todo.time}</span>
        </div>
    `
    return t
}

// 保存 todoList
var saveTodos = function() {
    var s = JSON.stringify(todoList)
    localStorage.todoList = s
}

var loadTodos = function() {
    var s = localStorage.todoList
    return JSON.parse(s)
}

// 返回自己在父元素中的下标
var indexOfElement = function(element) {
    var parent = element.parentElement
    for (var i = 0; i < parent.children.length; i++) {
        var e = parent.children[i]
        if (e === element) {
            return i
        }
    }
}

// 这个函数用来开关一个元素的某个 class
var toggleClass = function(element, className) {
    if (element.classList.contains(className)) {
        element.classList.remove(className)
    } else {
        element.classList.add(className)
    }
}

var currentTime = function() {
    var d = new Date()
    var month = d.getMonth() + 1
    var date = d.getDate()
    var hours = d.getHours()
    var minutes = d.getMinutes()
    var seconds = d.getSeconds()
    var timeString = `${month}/${date} ${hours}:${minutes}:${seconds}`
    return timeString
}


var initTodos = function() {
    todoList = loadTodos()
    for (var i = 0; i < todoList.length; i++) {
        var todo = todoList[i]
        insertTodo(todo)
    }
}


var todoList = []

var __main = function() {
    // 绑定事件
    bindEvents()

    // 程序加载后, 加载 todoList 并且添加到页面中
    initTodos()
}

__main()

参考:
事件委托
https://github.com/yonyouyc/blog/issues/25
https://segmentfault.com/a/1190000002613617
http://www.cnblogs.com/owenChen/archive/2013/02/18/2915521.html

http://blog.xieliqun.com/2016/08/12/event-delegate/#%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98

http://blog.xieliqun.com/2016/08/12/event-delegate/#%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98

时间操作
https://segmentfault.com/a/1190000000481753
https://www.cnblogs.com/tugenhua0707/p/3776808.html

本地化存储
https://segmentfault.com/a/1190000002701423

https://xifengxx.github.io/2016/07/10/HTML5%E5%AD%98%E5%82%A8%E5%8F%8A%E7%A6%BB%E7%BA%BF%E5%BA%94%E7%94%A8%E5%AE%9E%E6%88%98/

https://segmentfault.com/a/1190000003965297
https://segmentfault.com/a/1190000007506189#articleHeader5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值