zoukankan      html  css  js  c++  java
  • python中实现原生的AJAX

    1. ajax:

    • 使用js来提交数据到服务器,服务器返回数据给js,然后js局部刷新显示在浏览器。js可以实现异步刷新浏览器界面。
    • ajax无法跨域访问 {即无法直接跳转至当前的模块外部,需要另写重定向函数及重定向路由}

    2. ajax改造todo:

    ajax()的执行流程:{下面3、4的顺序可以交换}

    1. 创建ajax对象:XMLHttpRequest()
    2. 连接服务器:open()
    3. 发送请求:send()
    4. 监听响应并接收返回值:onreadystatechange事件、readyState属性:请求状态、status属性:请求结果、responseText
    • 首先编写后端API,待会儿在前端JS里面写AJAX,通过AJAX向后端API发起HTTP请求,服务端对请求进行解析{即路由解析},返回HTTP响应被AJAX捕获并解析进行某些操作。
    • 前端AJAX的写法流程:
    • 第一,写个gua.js作为底层函数,这里面有3个逻辑:1,写好ajax()用于正在发送请求和接收响应,并调用监听函数{也即回调函数};2,定义好发送CURD的HTTP请求的API{即操作ajax()向服务器发起HTTP请求};3,写一些辅助函数
    • 第二,另定义一个event.js,这里面调用gua.js里面写好的CURD的API,并传入回调函数{其实是定义一个回调函数,然后以匿名函数的方式作为实参传入CURD的API中}
    # server.py # 接收HTTP请求,解析路由并执行相应的函数
    
    
    # todo.py
    from routes.session import session
    from utils import (
        log,
        redirect,
        template,
        http_response,
    )
    
    
    def main_index(request):
        return redirect('/todo/index')
    
    
    # 直接写函数名字不写 route 了
    def index(request):
        """
        主页的处理函数, 返回主页的响应
        """
        body = template('todo_index.html')
        return http_response(body)
    
    
    route_dict = {
        '/': main_index,
        '/todo/index': index,
    }
    
    
    <!-- todo_index.html -->
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>web10 todo ajax</title>
        </head>
        <body>
            <input id='id-input-todo'>
            <button id='id-button-add'>add</button>
            <div class="todo-list">
            </div>
            <!-- 这是我们处理静态文件的套路 -->
            <!-- gua.js 放了公共的函数 -->
            <!-- 按顺序引入 2 个 js 文件, 后面的 js 文件就能使用前面的文件中的函数了 -->
            <script src='/static?file=gua.js'></script>
            <script src='/static?file=todo.js'></script>
        </body>
    </html>
    
    
    /* gua.js */
    var log = function() {
        console.log.apply(console, arguments)
    }
    
    var e = function(sel) {
        return document.querySelector(sel)
    }
    
    /*
     ajax 函数
    */
    var ajax = function(method, path, data, responseCallback) {
        var r = new XMLHttpRequest()
        // 设置请求方法和请求地址
        r.open(method, path, true)
        // 设置发送的数据的格式为 application/json
        // 这个不是必须的
        r.setRequestHeader('Content-Type', 'application/json')
        // 注册响应函数 {当请求得到响应,就自动激发}
        r.onreadystatechange = function() {
            if(r.readyState === 4) {  //4表示服务器响应已完成
                // r.response 存的就是服务器发过来的放在 HTTP BODY 中的数据
                responseCallback(r.response) //把服务器响应内容给了回调函数做实参,故形参也是一个{接收实参}
            }
        }
        // 把数据转换为 json 格式字符串
        data = JSON.stringify(data)
        // 发送请求
        r.send(data)
    }
    
    // TODO API {向服务器发起请求的API}, 处理响应的回调函数写在todo.js里面
    // 获取所有 todo
    var apiTodoAll = function(callback) { //当本函数执行完,请求得到响应后,系统自动执行回调函数callback
        var path = '/api/todo/all'
        ajax('GET', path, '', callback)
    }
    
    // 增加一个 todo
    var apiTodoAdd = function(form, callback) {
        var path = '/api/todo/add'
        ajax('POST', path, form, callback)
    }
    
    // 删除一个 todo
    var apiTodoDelete = function(id, callback) {
        var path = '/api/todo/delete?id=' + id
        ajax('GET', path, '', callback)
        //    get(path, callback)
    }
    
    // 更新一个 todo
    var apiTodoUpdate = function(form, callback) {
        var path = '/api/todo/update'
        ajax('POST', path, form, callback)
        //    post(path, form, callback)
    }
    
    // load weibo all
    var apiWeiboAll = function(callback) {
        var path = '/api/weibo/all'
        ajax('GET', path, '', callback)
    }
    
    // 增加一个 todo
    var apiWeiboAdd = function(form, callback) {
        var path = '/api/weibo/add'
        ajax('POST', path, form, callback)
    }
    
    
    
    /* todo.js */
    var timeString = function(timestamp) {
        t = new Date(timestamp * 1000)
        t = t.toLocaleTimeString()
        return t
    }
    
    var todoTemplate = function(todo) {
        var title = todo.title
        var id = todo.id
        var ut = timeString(todo.ut)
        // data-xx 是自定义标签属性的语法
        // 通过这样的方式可以给任意标签添加任意属性
        // 假设 d 是 这个 div 的引用
        // 这样的自定义属性通过  d.dataset.xx 来获取
        // 在这个例子里面, 是 d.dataset.id
        var t = `
            <div class="todo-cell" id='todo-${id}' data-id="${id}">
                <button class="todo-edit">编辑</button>
                <button class="todo-delete">删除</button>
                <span class='todo-title'>${title}</span>
                <time class='todo-ut'>${ut}</time>
            </div>
        `
        return t
        /*
        上面的写法在 python 中是这样的
        t = """
        <div class="todo-cell">
            <button class="todo-delete">删除</button>
            <span>{}</span>
        </div>
        """.format(todo)
        */
    }
    
    var insertTodo = function(todo) {
        var todoCell = todoTemplate(todo)
        // 把todoCell 插入 todo-list
        var todoList = e('.todo-list') //找到已有的todolist,把新按钮所在的div挂在.todo-list下
        todoList.insertAdjacentHTML('beforeend', todoCell)
    }
    
    var insertEditForm = function(cell) {
        var form = `
            <div class='todo-edit-form'>
                <input class="todo-edit-input">
                <button class='todo-update'>更新</button>
            </div>
        `
        cell.insertAdjacentHTML('beforeend', form)
    }
    
    var loadTodos = function() {
        // 调用 ajax api 来载入数据
        apiTodoAll(function(r) { //r接收实参{这个实参来自服务器的响应,其定义见gua.js中对回调函数输入的实参}
            // console.log('load all', r)
            // 解析为 数组
            var todos = JSON.parse(r)
            // 循环添加到页面中
            for(var i = 0; i < todos.length; i++) {
                var todo = todos[i]
                insertTodo(todo)
            }
        })
    }
    
    var bindEventTodoAdd = function() { //编写事件监听器
        var b = e('#id-button-add')
        // 注意, 第二个参数可以直接给出定义函数
        b.addEventListener('click', function(){
            var input = e('#id-input-todo')
            var title = input.value
            log('click add', title)
            var form = {
                'title': title,
            }
            apiTodoAdd(form, function(r) { //定义回调函数,并作为参数传入gua.js中定义的API中
                // 收到返回的数据, 插入到页面中
                var todo = JSON.parse(r)
                insertTodo(todo)
            })
        })
    }
    
    var bindEventTodoDelete = function() { //和bindEventTodoAdd的逻辑刚刚相反
        var todoList = e('.todo-list') //当时add一个todo的div时,就是挂在.todo-list下,所以现在删也是要到.todo-list
        // 注意, 第二个参数可以直接给出定义函数
        todoList.addEventListener('click', function(event){ //事件监听委托给 爷爷节点 todoList
            var self = event.target //找到click动作的目标位置 {即要找到删除按钮},因为这个按钮的父亲是todo{一个div},todo的父亲是todo-list{也是一个div}
            if(self.classList.contains('todo-delete')){ //找到删除按钮了
                // 删除这个 todo
                var todoCell = self.parentElement // 其实是要删除“删除按钮”所在的那个todo{即一个div}
                var todo_id = todoCell.dataset.id //但是add一个todo的div时,曾经自定义了一个data-id属性,现在从这个属性中取到当时add时存的值
                apiTodoDelete(todo_id, function(r){ //又去gua.js中调用ajax(),发送删除的HTTP请求到服务器
                    log('删除成功', todo_id)
                    todoCell.remove() //把这个todo从本地浏览器的静态页上删除掉
                })
            }
        })
    }
    
    var bindEventTodoEdit = function() {
        var todoList = e('.todo-list')
        // 注意, 第二个参数可以直接给出定义函数
        todoList.addEventListener('click', function(event){ // 又是把监听事件委托给了爷爷todolist
            var self = event.target
            if(self.classList.contains('todo-edit')){ //找到了"编辑按钮"
                var todoCell = self.parentElement //找到 "编辑按钮"的父亲{某一个todo,本质是一个div}
                insertEditForm(todoCell) //把一个用于编辑的div插入到todoCell最末尾{让其紧跟着这个待编辑的todo}
            }
        })
    }
    
    
    var bindEventTodoUpdate = function() {
        var todoList = e('.todo-list') // 又是委托{这个静态页面中恒存在的div}来监听事件
        // 注意, 第二个参数可以直接给出定义函数
        todoList.addEventListener('click', function(event){
            var self = event.target //当前被点击的对象
            if(self.classList.contains('todo-update')){ // 找到了“update按钮”
                log('点击了 update ')
                //
                var editForm = self.parentElement //拿到整个editForm
                // querySelector 是 DOM 元素的方法
                // document.querySelector 中的 document 是所有元素的祖先元素
                var input = editForm.querySelector('.todo-edit-input') //找到editForm中的input框
                var title = input.value
                // 用 closest 方法可以找到最近的直系祖先
                var todoCell = self.closest('.todo-cell') // 找到“update按钮”最近的一个class名为.todo-cell的祖先
                var todo_id = todoCell.dataset.id // 从这个to中拿到自定义data-id的值
                var form = {
                    'id': todo_id,
                    'title': title,
                }
                apiTodoUpdate(form, function(r){ // 拿form去调用ajax(),以及即将执行下面的回调函数
                    log('更新成功', todo_id)
                    var todo = JSON.parse(r) //解析apiTodoUpdate后得到的HTTP响应
                    var selector = '#todo-' + todo.id //根据最开始{add todo} 时定义的id属性来
                    var todoCell = e(selector)        //找需要更新的todo
                    var titleSpan = todoCell.querySelector('.todo-title') //根据 这个todo的 {'.todo-title'的这个class属性}去找这个todo的一个子节点
                    titleSpan.innerHTML = todo.title //更新titleSpan的内嵌的html内容,同理可以修改17行的${ut}
    //                todoCell.remove()
                })
            }
        })
    }
    
    var bindEvents = function() {
        bindEventTodoAdd()
        bindEventTodoDelete()
        bindEventTodoEdit()
        bindEventTodoUpdate()
    }
    
    var __main = function() {
        bindEvents()
        loadTodos()
    }
    
    __main()
    
    
    // 例如下图,待会儿在blog中逐个解释每一个CURD背后的逻辑和流程到底是怎样的?
    // 可以很好地梳理清楚js和ajax的运行过程及效果
    
    /*
    给 删除 按钮绑定删除的事件
    1, 绑定事件
    2, 删除整个 todo-cell 元素
    */
    // var todoList = e('.todo-list')
    // // 事件响应函数会被传入一个参数, 就是事件本身
    // todoList.addEventListener('click', function(event){
    //     // log('click todolist', event)
    //     // 我们可以通过 event.target 来得到被点击的元素
    //     var self = event.target
    //     // log('被点击的元素是', self)
    //     // 通过比较被点击元素的 class 来判断元素是否是我们想要的
    //     // classList 属性保存了元素的所有 class
    //     // 在 HTML 中, 一个元素可以有多个 class, 用空格分开
    //     // log(self.classList)
    //     // 判断是否拥有某个 class 的方法如下
    //     if (self.classList.contains('todo-delete')) {
    //         log('点到了 删除按钮')
    //         // 删除 self 的父节点
    //         // parentElement 可以访问到元素的父节点
    //         self.parentElement.remove()
    //     } else {
    //         // log('点击的不是删除按钮******')
    //     }
    // })
    
    
    # api_todo.py
    import json
    from routes.session import session
    from utils import (
        log,
        redirect,
        http_response,
        json_response,
    )
    from models.todo import Todo
    from models.weibo import Weibo
    
    
    def all_weibo(request):
        """
        返回所有 todo
        """
        ms = Weibo.all()
        # 要转换为 dict 格式才行
        data = [m.json() for m in ms]
        return json_response(data)
    
    
    def add_weibo(request):
        """
        接受浏览器发过来的添加 weibo 请求
        添加数据并返回给浏览器
        """
        form = request.json()
        # 创建一个 model
        m = Weibo.new(form)
        # 把创建好的 model 返回给浏览器
        return json_response(m.json())
    
    
    # 本文件只返回 json 格式的数据
    # 而不是 html 格式的数据
    def all(request):
        """
        返回所有 todo
        """
        todo_list = Todo.all()
        # 要转换为 dict 格式才行
        todos = [t.json() for t in todo_list]
        return json_response(todos)
    
    
    def add(request):
        """
        接受浏览器发过来的添加 todo 请求
        添加数据并返回给浏览器
        """
        # 得到浏览器发送的 json 格式数据
        # 浏览器用 ajax 发送 json 格式的数据过来
        # 所以这里我们用新增加的 json 函数来获取格式化后的 json 数据
        form = request.json() # 字符串格式转化成dict
        # 创建一个 todo
        t = Todo.new(form)
        # 把创建好的 todo 返回给浏览器
        return json_response(t.json())
    
    
    def delete(request):
        """
        通过下面这样的链接来删除一个 todo
        /delete?id=1
        """
        todo_id = int(request.query.get('id'))
        t = Todo.delete(todo_id)
        return json_response(t.json())
    
    
    def update(request):
        form = request.json()
        todo_id = int(form.get('id'))
        t = Todo.update(todo_id, form)
        return json_response(t.json())
    
    
    route_dict = {
        '/api/todo/all': all,
        '/api/todo/add': add,
        '/api/todo/delete': delete,
        '/api/todo/update': update,
        # weibo api
        '/api/weibo/all': all_weibo,
        '/api/weibo/add': add_weibo,
    
    }
    
    

    分析lesson_10中,地址栏中输入localhost:3000/todo/index后程序是怎么走的:

    1. 首先,浏览器向localhost:3000/todo/index后,
      Server.py解析路由,根据routes包下面的todo.py找到了/todo/index这个路由信息,
      定位到todo.py下的index(),
      然后调用jinja2渲染todo_index.html,然后把首页返回给浏览器。
      而todo_index.html中又引入了gua.js和todo.js

    2. todo.js的__main()执行会导致bindEvents()执行和loadTodos()执行;

    3. 一方面,bindEvents()执行会导致
      bindEventTodoAdd()
      bindEventTodoDelete()
      bindEventTodoEdit()
      bindEventTodoUpdate()
      四个函数执行,会依次激发后续操作,以bindEventTodoAdd()为例:
      bindEventTodoAdd()会定义一个对id-button-add的按钮的点击事件的监听器,如果此按钮被点击,就会调用监听器,
      收集id-input-todo中的值,并把这个值和一个匿名的回调
      函数传入apiTodoAdd(),并调用apiTodoAdd(),{这个函数的
      定义体apiTodoAdd也在gua.js},然后apiTodoAdd首先会调用
      ajax()发送一个HTTP请求到/api/todo/add,然后得到响应后,自动把response送到apiTodoAdd上一步匿名传入的回调函数中,然后执行回调函数,然后就会把当前的response中的todo插入到todo_index.html中。
      {其余监听器的执行流程类似,只是需要理解js的执行逻辑和代码执行后的前端效果}

    4. 另一方面,bindEvents()执行会导致loadTodos()执行,todos()执行会引发apiTodoAll(监听器)执行,而apiTodoAll的定义体在gua.js,它会调用ajax()首先发送一个HTTP请求到/api/todo/all,然后得到一个HTTP response作为实参传入到回调函数的新参,然后自动激发回调函数,然后就会把response中每一个todo依次插入到todo_index.html中。

    待会儿再详细重构下本篇文字:

    专门把server.py、todo.py、todo_index.html、gua.js、todo.js、api_todo.py 的源码单独列出来,结合代码中注释和上述文字分析,彻底把ajax的实现过程讲清楚。

  • 相关阅读:
    LeetCode15 3Sum
    LeetCode10 Regular Expression Matching
    LeetCode20 Valid Parentheses
    LeetCode21 Merge Two Sorted Lists
    LeetCode13 Roman to Integer
    LeetCode12 Integer to Roman
    LeetCode11 Container With Most Water
    LeetCode19 Remove Nth Node From End of List
    LeetCode14 Longest Common Prefix
    LeetCode9 Palindrome Number
  • 原文地址:https://www.cnblogs.com/LS1314/p/8542827.html
Copyright © 2011-2022 走看看