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

原文地址:https://www.cnblogs.com/LS1314/p/8542827.html