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的實現過程講清楚。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM