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的实现过程讲清楚。

  • 相关阅读:
    盘点三个网络赚零花钱的小项目,傻瓜式操作
    如何运营一个女性社区?
    女性社区TOP10
    微商怎么做月入过万?新手必看
    电脑设置 账号改名,远程无法复制
    sql server 安装
    C# HTTP
    电脑命令 重启电脑
    使用老毛桃备份 ,还原系统
    c# 截取字符串
  • 原文地址:https://www.cnblogs.com/LS1314/p/8542827.html
Copyright © 2011-2022 走看看