05 - 使用 AJAX 通信

01 - Clojure Web 程序基本结构.png

客户端使用 Ajax

引入依赖

;; Ajax 库
[cljs-ajax "0.7.4"]

;; 支持 JSON 格式的中间件
[ring-middleware-format "0.7.2"]

配置中间件

添加支持 JSON 格式的中间件(本段讲解,没有代码)

注意:因为我们将会为客户端 Ajax 请求设置 :format :json 选项,因此服务端收到的请求参数将会是下面这样:

:params {email jiesoul@gmail.com, password 12345678}

而我们希望的格式应该如下:

:params {:email jiesoul@gmail.com, :password 12345678}

解决方法是开启 ring-middleware-format 中间件的 :formats [:json-kw] 选项,他会自动为 JSON 数据设置关键字

(wrap-format/wrap-restful-format :formats [:json-kw])

代码

文件:src/soul_talk/core.clj

(ns soul-talk.core
  (:require 
    ......
    ;; 支持 JSON 格式的中间件
    [ring.middleware.format :as wrap-format]))


(def app
  (-> app-routes  
      (wrap-nocache)
      (wrap-reload)
      (wrap-webjars) 
      
      ;; 这行是添加的
      (wrap-format/wrap-restful-format :formats [:json-kw])

      (wrap-defaults (assoc-in site-defaults [:security :anti-forgery] false))))

(defn -main []
  (jetty/run-jetty app {:port 3000 :join? false}))

ClojureScript

改造 login.cljs ,加入 Ajax 功能

主要进行了以下几项修改

  • 创建了一个 login-data 变量,保存客户端登陆数据,他会通过 Ajax 被发送到服务端
  • 之前输入框丢失焦点后,仅仅将输入框和要使用的验证函数传给 validate-invalid ;现在还需要向 login-data 变量中添加数据
  • 之前输入框丢失焦点后,直接通过 id 获取组件 ,现在则是通过事件对象 e 获得组件
  • 之前点击提交按钮后,validate-form 直接从数据框中读取数据进行验证,现在从 JSON 变量中读取数据进行验证
  • 之前点击提交按钮,验证成功后,返回 true ,然后提交到服务器;现在验证成功后,通过 Ajax 提交数据,页面不刷新
  • 把客户端页面的 form 改为 div 元素

==注意:服务端登录无论成功还是失败,最后 Ajax 都会调用 haddler-ok 函数,那是因为服务端返回的状态码被转换成了 JSON 数据 {"status":404,"errors":"用户名密码不对"},而状态码总是 200,后面会改进这个问题==

修改 login.cljs

(ns soul-talk.login
  (:require [domina :as dom]
            [domina.events :as ev]
            [reagent.core :as reagent :refer [atom]]
            ;; 引入共享代码
            [soul-talk.auth-validate :as validate]
            ;; 引入 Ajax 支持
            [ajax.core :as ajax]))


(def login-data (atom {:email "" :password ""}))


;; 如果验证不成功,则在输入框上增加样式;
;; 如果验证成功,则移除样式
;; 这个函数,输入框失去焦点的时候被调用
(defn validate-invalid [input vali-fun]
  (if-not (vali-fun (.-value input))  ;; 验证函数传入文本,而不是 HTML 元素
    (dom/add-class! input "is-invalid")
    (dom/remove-class! input "is-invalid")))


;; Ajax 成功后调用
(defn handler-ok [response]
  (js/alert @login-data))

;; Ajax 失败后调用
(defn handler-error [{:keys [status status-text]}]
  (js/alert (sstr status status-text)))


(defn login! []
  (ajax/POST
    "/login"
    {:format        :json
     :headers       {"Accept" "application/transit+json"}
     :params        @login-data
     :handler       handler-ok
     :error-handler handler-error}))


;; 这个函数提交的时候被调用,类客户端验证输入格式是否正确
(defn validate-form []
  ;; 注意这里的变化
  ;; 数据不再是从元素中直接读取,而是从 JSON 数据中读取
  (if (and (validate/validate-email (:email @login-data))
           (validate/validate-passoword (:password @login-data)))

    ;; 注意这里的变化:之前验证成功返回 true ,则表单可以提交
    ;; 现在是调用 login! 函数,利用 ajax 从后台读取据,不提交也不刷新页面
    (login!)
    (do
      (js/alert "email和密码不合法")
      false)))



;; 组件化登陆表单
(defn login-component []
  [:div.container
   ;; 登陆表单
   [:form#loginForm.form-signin
    ;; 标题
    [:h1.h3.mb-3.font-weight-normal.text-center "Please sign in"]

    ;; Email 部分
    [:div.form-group
     ;; Email 标签
     [:label "Email address"]
     ;; Email 输入框
     [:input#email.form-control
      {:type        "text"
       :name        "email"
       :auto-focus  true
       :placeholder "Email Address"
       ;; 焦点丢失的时候,调用验证函数
       :on-blur   (fn [e]
                      (let [d (.. e -target)]
                        (swap! login-data assoc :email (.-value d))
                        (validate-invalid d validate/validate-email)))}]
     ;; 错误提示信息
     [:div.invalid-feedback "无效的 Email"]]


    ;; 密码部分
    [:div.form-group
     [:label "Password"]
     ;; 密码输入框
     [:input#password.form-control
      {:type        "password"
       :name        "password"
       :placeholder "password"
       ;; 焦点丢失的时候,调用验证函数
       ;; 之前的代码仅仅将元素和要使用的函数传给 validate-invalid
       ;; 现在还需要向 JSON 中添加数据
       ;; 此外,之前直接通过 id 获取组件 ,现在则是通过事件对象 e 获得组件
       :on-blur     (fn [e]
                      (let [d (.-target e)]
                        (swap! login-data assoc :password (.-value d))
                        (validate-invalid d validate/validate-passoword)))}]
     ;; 错误提示信息
     [:div.invalid-feedback "无效的密码"]]

    ;; “记住我” 复选框
    [:div.form-group.form-check
     [:input#rememeber.form-check-input {:type "checkbox"}]
     [:label "记住我"]]

    ;; 错误信息
    [:div#error.invalid-feedback]

    ;; 提交按钮
    [:input#submit.btn.btn-lg.btn-primary.btn-block
     {:type  "button"
      :value "登录"
      :on-click #(validate-form)}]

    ;; 版权信息
    [:p.mt-5.mb-3.text-muted "&copy @2018"]]])



;; 渲染登陆表单组件,并挂载到 `content` div元素上
(defn load-page []
  (reagent/render
    [login-component]
    (dom/by-id "content")))


(defn ^:export init []
  (if (and js/document
           (.-getElementById js/document))
    (load-page)))


Clojure

完成服务端的 Ajax 支持

修改登录 Handler,和之前的代码相比有以下变化:

  • 使用了共享代码中的验证函数
  • request 中添加了 :session 字段,但是没有任何意义,因为这个数据没有传输给其他函数
  • 登陆成功,直接给客户端返回了一个 200,其他处理有客户端完成
  • 登陆失败,给客户端返回 400 ,但是要注意:这里的 400 是以 JSON 格式返回的,因此客户端解析不出来
(ns soul-talk.core
  (:require
    ......
    ;; 响应简化库,这个库暂时没用了
    [ring.util.http-response :as resp]
    
    ;; 内置响应库
    [ring.util.response :as res]
    
    ;; 引入共享代码
    [soul-talk.auth-validate :as auth-validate]))
    
    
;; Post 登录数据
(defn handle-login [{:keys [params] :as request}]
  (let [email (:email params)
        password (:password params)]
    (cond
      ;; 使用共享代码中的函数,之前没有使用
      (not (auth-validate/validate-email email)) (res/response {:status 400 :errors "Email不合法"})
      (not (auth-validate/validate-passoword password)) (res/response {:status 400 :errors "密码不合法"})
      (and (= email "jiesoul@gmail.com")
           (= password "12345678"))
      (do
        ;; 这行代码没任何意义
        (assoc-in request [:session :identity] email)
        
        ;; 仅仅给客户端返回一个 200 状态码,有客户端完成页面跳转
        (res/response {:status :ok}))
      
      :else (res/response {:status 400 :errors "用户名密码不对"}))))

修改路由

修改了 login 请求的 POST 路由,请求参数改成在 Hadler 中提取(好像意义不大)

(def app-routes
  (routes
    (GET "/" request (home-handle request))
    (GET "/about" [] (str "这是关于我的页面"))
    (GET "/login" request (login-page request))
    
    ;; Post 路由修改
    ;; (POST "/login" [email password :as req] (handle-login email password req))
    (POST "/login" req (handle-login req)) 


    (GET "/logout" request (handle-logout request))
    (route/not-found error-page)))  

推荐阅读更多精彩内容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 19,590评论 1 42
  • Ajax 技术 第1章 认识Ajax 1.1 初识 ajax 我们平常上网,不管是注册账号,还是浏览网页,其本质就...
    春风之旅阅读 2,170评论 0 26
  • Ajax 表单提交 在HTML中提供了表单提交的功能,我们可以通过表单把数据从前台提交到后台 在HTML的DOM中...
    羊烊羴阅读 359评论 0 4
  • 一、 认识Ajax 1、 初识 ajax 我们平常上网,不管是注册账号,还是浏览网页,其本质就是通过客户端向服务器...
    宠辱不惊丶岁月静好阅读 302评论 0 2
  • 你拿着医院的诊断书回家, 口腔癌晚期,我噙着泪送你远去, 你才年过半百,满头白发, 你蹒跚的身影清瘦、凋零。 你喜...
    亚涓阅读 2,357评论 47 114