客戶端使用 Ajax
引入依賴
;; Ajax 庫
[cljs-ajax "0.7.4"]
;; 支持 JSON 格式的中間件
[ring-middleware-format "0.7.2"]
配置中間件
添加支持 JSON 格式的中間件(本段講解红碑,沒有代碼)
注意:因?yàn)槲覀儗?huì)為客戶端 Ajax 請(qǐng)求設(shè)置 :format :json
選項(xiàng),因此服務(wù)端收到的請(qǐng)求參數(shù)將會(huì)是下面這樣:
:params {email jiesoul@gmail.com, password 12345678}
而我們希望的格式應(yīng)該如下:
:params {:email jiesoul@gmail.com, :password 12345678}
解決方法是開啟 ring-middleware-format
中間件的 :formats [:json-kw]
選項(xiàng)泡垃,他會(huì)自動(dòng)為 JSON 數(shù)據(jù)設(shè)置關(guān)鍵字
(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 功能
主要進(jìn)行了以下幾項(xiàng)修改
- 創(chuàng)建了一個(gè)
login-data
變量,保存客戶端登陸數(shù)據(jù)蔑穴,他會(huì)通過 Ajax 被發(fā)送到服務(wù)端 - 之前輸入框丟失焦點(diǎn)后忠寻,僅僅將輸入框和要使用的驗(yàn)證函數(shù)傳給
validate-invalid
;現(xiàn)在還需要向login-data
變量中添加數(shù)據(jù) - 之前輸入框丟失焦點(diǎn)后存和,直接通過
id
獲取組件 奕剃,現(xiàn)在則是通過事件對(duì)象e
獲得組件 - 之前點(diǎn)擊提交按鈕后,
validate-form
直接從數(shù)據(jù)框中讀取數(shù)據(jù)進(jìn)行驗(yàn)證捐腿,現(xiàn)在從 JSON 變量中讀取數(shù)據(jù)進(jìn)行驗(yàn)證 - 之前點(diǎn)擊提交按鈕纵朋,驗(yàn)證成功后,返回
true
茄袖,然后提交到服務(wù)器操软;現(xiàn)在驗(yàn)證成功后,通過 Ajax 提交數(shù)據(jù)宪祥,頁面不刷新 - 把客戶端頁面的
form
改為div
元素
==注意:服務(wù)端登錄無論成功還是失敗聂薪,最后 Ajax 都會(huì)調(diào)用 haddler-ok
函數(shù)猪钮,那是因?yàn)榉?wù)端返回的狀態(tài)碼被轉(zhuǎn)換成了 JSON 數(shù)據(jù) {"status":404,"errors":"用戶名密碼不對(duì)"}
,而狀態(tài)碼總是 200
胆建,后面會(huì)改進(jìn)這個(gè)問題==
修改 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 ""}))
;; 如果驗(yàn)證不成功烤低,則在輸入框上增加樣式;
;; 如果驗(yàn)證成功笆载,則移除樣式
;; 這個(gè)函數(shù)扑馁,輸入框失去焦點(diǎn)的時(shí)候被調(diào)用
(defn validate-invalid [input vali-fun]
(if-not (vali-fun (.-value input)) ;; 驗(yàn)證函數(shù)傳入文本,而不是 HTML 元素
(dom/add-class! input "is-invalid")
(dom/remove-class! input "is-invalid")))
;; Ajax 成功后調(diào)用
(defn handler-ok [response]
(js/alert @login-data))
;; Ajax 失敗后調(diào)用
(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}))
;; 這個(gè)函數(shù)提交的時(shí)候被調(diào)用凉驻,類客戶端驗(yàn)證輸入格式是否正確
(defn validate-form []
;; 注意這里的變化
;; 數(shù)據(jù)不再是從元素中直接讀取腻要,而是從 JSON 數(shù)據(jù)中讀取
(if (and (validate/validate-email (:email @login-data))
(validate/validate-passoword (:password @login-data)))
;; 注意這里的變化:之前驗(yàn)證成功返回 true ,則表單可以提交
;; 現(xiàn)在是調(diào)用 login! 函數(shù)涝登,利用 ajax 從后臺(tái)讀取據(jù)雄家,不提交也不刷新頁面
(login!)
(do
(js/alert "email和密碼不合法")
false)))
;; 組件化登陸表單
(defn login-component []
[:div.container
;; 登陸表單
[:form#loginForm.form-signin
;; 標(biāo)題
[:h1.h3.mb-3.font-weight-normal.text-center "Please sign in"]
;; Email 部分
[:div.form-group
;; Email 標(biāo)簽
[:label "Email address"]
;; Email 輸入框
[:input#email.form-control
{:type "text"
:name "email"
:auto-focus true
:placeholder "Email Address"
;; 焦點(diǎn)丟失的時(shí)候,調(diào)用驗(yàn)證函數(shù)
:on-blur (fn [e]
(let [d (.. e -target)]
(swap! login-data assoc :email (.-value d))
(validate-invalid d validate/validate-email)))}]
;; 錯(cuò)誤提示信息
[:div.invalid-feedback "無效的 Email"]]
;; 密碼部分
[:div.form-group
[:label "Password"]
;; 密碼輸入框
[:input#password.form-control
{:type "password"
:name "password"
:placeholder "password"
;; 焦點(diǎn)丟失的時(shí)候胀滚,調(diào)用驗(yàn)證函數(shù)
;; 之前的代碼僅僅將元素和要使用的函數(shù)傳給 validate-invalid
;; 現(xiàn)在還需要向 JSON 中添加數(shù)據(jù)
;; 此外趟济,之前直接通過 id 獲取組件 ,現(xiàn)在則是通過事件對(duì)象 e 獲得組件
:on-blur (fn [e]
(let [d (.-target e)]
(swap! login-data assoc :password (.-value d))
(validate-invalid d validate/validate-passoword)))}]
;; 錯(cuò)誤提示信息
[:div.invalid-feedback "無效的密碼"]]
;; “記住我” 復(fù)選框
[:div.form-group.form-check
[:input#rememeber.form-check-input {:type "checkbox"}]
[:label "記住我"]]
;; 錯(cuò)誤信息
[:div#error.invalid-feedback]
;; 提交按鈕
[:input#submit.btn.btn-lg.btn-primary.btn-block
{:type "button"
:value "登錄"
:on-click #(validate-form)}]
;; 版權(quán)信息
[:p.mt-5.mb-3.text-muted "© @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
完成服務(wù)端的 Ajax 支持
修改登錄 Handler顷编,和之前的代碼相比有以下變化:
- 使用了共享代碼中的驗(yàn)證函數(shù)
- 在
request
中添加了:session
字段,但是沒有任何意義剑刑,因?yàn)檫@個(gè)數(shù)據(jù)沒有傳輸給其他函數(shù) - 登陸成功媳纬,直接給客戶端返回了一個(gè)
200
,其他處理有客戶端完成 - 登陸失敗施掏,給客戶端返回
400
钮惠,但是要注意:這里的400
是以 JSON 格式返回的,因此客戶端解析不出來
(ns soul-talk.core
(:require
......
;; 響應(yīng)簡化庫七芭,這個(gè)庫暫時(shí)沒用了
[ring.util.http-response :as resp]
;; 內(nèi)置響應(yīng)庫
[ring.util.response :as res]
;; 引入共享代碼
[soul-talk.auth-validate :as auth-validate]))
;; Post 登錄數(shù)據(jù)
(defn handle-login [{:keys [params] :as request}]
(let [email (:email params)
password (:password params)]
(cond
;; 使用共享代碼中的函數(shù)素挽,之前沒有使用
(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)
;; 僅僅給客戶端返回一個(gè) 200 狀態(tài)碼,有客戶端完成頁面跳轉(zhuǎn)
(res/response {:status :ok}))
:else (res/response {:status 400 :errors "用戶名密碼不對(duì)"}))))
修改路由
修改了 login
請(qǐng)求的 POST 路由抖苦,請(qǐng)求參數(shù)改成在 Hadler 中提然倭狻(好像意義不大)
(def app-routes
(routes
(GET "/" request (home-handle request))
(GET "/about" [] (str "這是關(guān)于我的頁面"))
(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)))