
客户端使用 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 "© @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)))
网友评论