04 使用 ClojureScript 進(jìn)行客戶端驗(yàn)證练湿,并使用 Reagent 組件化頁面

04 使用 ClojureScript 進(jìn)行客戶端驗(yàn)證,并使用 Reagent 組件化頁面.png

配置 project.clj

添加本章依賴

;; Domina 庫 
[domina "1.0.3"]

;; 前端組件庫 
[reagent "0.8.1"]

;; 前端組件工具庫
[reagent-utils "0.3.1"]

配置前后端共享代碼文件夾

修改 project.clj 涌庭,將共享代碼路徑添加到源文件路徑配置中去

;; 指定源文件和資源文件路徑
:source-paths ["src"   "src/cljc"]

;; 設(shè)置 cljsbuild 編譯器參數(shù)
:cljsbuild {
    :builds {
        ;; 開發(fā)環(huán)境
        :dev {
            ;; 源代碼目錄
            :source-paths ["src-cljs"   "src/cljc"] 
            
            ......

靜態(tài)文件

修改 index.html 以適用于組件化

  • 需要一個空的 div 元素芥被,作為組件掛載的容器即可
  • 另外要調(diào)用組件化腳本中的函數(shù)

文件:resources/index.html

{% extends "base.html" %}

{% block content %}
<div id="app">

</div>
{% endblock %}


{% block page-script %}
<script>soul_talk.core.init();</script>
{% endblock %}

修改 login.html 以適用于組件化

文件:resources/login.html

{% extends "base.html" %}


{% block page-title %}
Soul Talk Login 
{% endblock %}


{% block page-css %}
<link rel="stylesheet" href="/css/login.css">
{% endblock %}


{% block content %}

<!-- 掛載組件的元素 -->
<div class="container" id="content">
</div>

{% endblock %}

{% block page-script %}
<script>soul_talk.login.init();</script>
{% endblock %}

ClojureScript

命名空間的問題(本節(jié)不是項目中的代碼,只是作為講解)

如果多個 JS 模塊中都有 init 函數(shù)坐榆,最后都被編譯到 main.js 中拴魄,會出現(xiàn)命名沖突沖突

解決問題的方法:

  • ^:export 標(biāo)記 init 函數(shù),則函數(shù)必須使用命名空間名限定才能訪問
  • 不再將 init 函數(shù)綁定到 window.onload 上席镀,而是直接再頁面中調(diào)用該函數(shù) <script>soul_talk.core.init();</script>

core.cljs 腳本代碼如下修改:

;; 為 Form 綁定 onsubmit 處理函數(shù)
;; 導(dǎo)出該函數(shù)
(defn ^:export init []
  (if (and js/document (.-getElementById js/document))
    (let [login-form (.getElementById js/document "loginForm")]
      (set! (.-onsubmit login-form) validate-form))))

;; 為 Window 綁定 onload 處理函數(shù)匹中,不再需要
;;(set! (.-onload js/window) init)

login.html 頁面代碼修改如下:

{% block page-script %}
<script>soul_talk.core.init();</script>
{% endblock %}

ClojureScript 命名空間的相互引用(本節(jié)不是項目中的代碼,只是作為講解)

soul-talk.core 為什么要引入 soul-talk.login豪诲?顶捷?

  • core.cljs 是全局入口,其代碼會被編譯到 main.js 中屎篱;main.js 又被 base.html 模板 引入服赎,其中的代碼會被自動執(zhí)行
  • login.cljs 沒有被頁面明確引入,因此其中的代碼頁面看不到
  • core.cljs 中引入 login.cljs交播,相當(dāng)于 main.js 引入了 login.cljs 中的代碼重虑。 之后,任何引入了 main.js 的頁面都能看到 login 命名空間了

因此在 soul-talk.core 中有以下代碼

(ns soul-talk.core
  (:require 
    [soul-talk.login]))

創(chuàng)建前后端共享代碼

新建 cljc/soul_talk/auth_validate.cljc 文件

注意:文件和文件夾必須使用下劃線秦士,在代碼中使用中劃線

(ns soul-talk.auth-validate)

;; 密碼格式
(def ^:dynamic *password-re* #"^(?=.*\d).{4,128}$")
;; Email 格式
(def ^:dynamic *email-re* #"^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$")


;; 驗(yàn)證 Email 是否為空
;; 參數(shù)變?yōu)槲谋救崩鳎皇?HTML 元素
(defn validate-email [email]
  (if (and (string? email)
           (re-matches *email-re* email))
    true
    false))
    

;; 驗(yàn)證密碼是否為空
;; 參數(shù)變?yōu)槲谋荆皇?HTML 元素
(defn validate-passoword [password]
  (if (and (string? password)
           (re-matches *password-re* password))
    true
    false))

組件化首頁面

修改 soul-talk/core.cljs 文件隧土,原先只有原生代碼提针,現(xiàn)在增加客戶端庫的相關(guān)引用。注意:

  • Session 庫是客戶端 Session次洼,和服務(wù)端沒任何關(guān)系
  • 當(dāng)前代碼关贵,登錄狀態(tài)并不會顯示出來,因?yàn)榭蛻舳?Session 并沒有設(shè)置
(ns soul-talk.core
  (:require [soul-talk.login :as login]
            [reagent.core :as r]

            ;; 可以創(chuàng)建和管理客戶端 Session 卖毁,注意和服務(wù)端沒關(guān)系
            [reagent.session :as session]
            
            [domina :as dom]))


(defonce posts (r/atom []))
(defonce navs (r/atom []))
(defonce archives (r/atom []))

(defn blog-header-component []
  (fn []
    [:div.blog-header.py-3
     [:div.row.flex-nowrap.justify-content-between.align-items-center
      [:div.col-4.pt-1
       [:a.text-muted {:href "#"} "訂閱"]]
      [:div.col-4.text-center
       [:a.blog-header-logo.text-dark {:href "/"} "Soul Talk"]]
      [:div.col-4.d-flex.justify-content-end.align-items-center
       (if (session/get :identity)
         (let [name (session/get :identity)]
           [:span.navbar-text (str "歡迎你 " name)]
           [:a.btn.btn-sm.btn-outline-secondary {:href "/logout"} "退出"])
         [:a.btn.btn-sm.btn-outline-secondary {:href "/login"} "登錄"])]]]))

(defn nav-scroller-header-component [navs]
  (fn []
    [:div.nav-scroller.py-1.mb-2
     [:nav.nav.d-flex.justify-content-between
      (for [{:keys [href value] :as nav} navs]
        ^{:key nav} [:a.p-2.text-muted {:href href :id value} value])]]))

(defn jumbotron-header-component []
  (fn []
    [:div.jumbotron.p-3.p-md-5.text-white.rounded.bg-dark
     [:div.col-md-6.px-0
      [:h1.display-4.font-italic "Title of a longer featured blog post"]
      [:p.lead.mb-0
       [:a.text-white.font-weight-bold {:href "#"} "Continue reading..."]]]]))

(defn header-component []
  (fn []
    [:div.container
     [blog-header-component]
     [nav-scroller-header-component @navs]
     [jumbotron-header-component]]))

(defn footer-component []
  (fn []
    [:div.container.blog-footer
     [:p "Blog template built for"
      [:a {:href "https://getbootstrap.com/"} "Bootstrap"]
      " by "
      [:a {:href "https://twitter.com/mdo"} "@mdo"]
      "."]
     [:p
      [:a {:href "#"} "Back to top"]]]))

(defn blog-post-component [posts]
  (fn []
    [:div.col-md-8.blog-main
     [:h3.pb-3.mb-4.font-italic.border-bottom
      "From the Firehose"]
     (for [{:keys [id title meta author content] :as post} posts]
       ^{:key post} [:div.blog-post
                     [:h2.blog-post-title title]
                     [:p.blog-post-meta meta
                      [:a {:href "#" :id id} author]]
                     [:p content]])
     [:nav.blog-pagination
      [:a.btn.btn-outline-primary {:href "#"} "Older"]
      [:a.btn.btn-outline-secondary.disabled {:href "#"} "Newer"]]]))

(defn main-component []
  (fn []
    [:div.container {:role "main"}
     [:div.row
      [blog-post-component @posts]
      [:aside.col-md-4.blog-sidebar
       [:div.p-3.mb-3.bg-light.rounded
        [:h4.font-italic "About"]
        [:p.mb-0 "Etiam porta <em>sem malesuada magna</em> mollis euismod."]]
       [:div.p-3
        [:h4.font-italic "Archives"]
        [:ol.list-unstyled.mb-0
         (for [{:keys [time href] :as archive} @archives]
           ^{:key archive} [:li [:a {:href href} time]])]]
       [:div.p-3
        [:h4.font-italic "Elsewhere"]
        [:ol.list-unsty
         [:li [:a {:href "#"} "GitHub"]]
         [:li [:a {:href "#"} "Weibo"]]
         [:li [:a {:href "#"} "Twitter"]]]]]]]))

(defn home-component []
  [:div
   [header-component]
   [main-component]
   [footer-component]])

(reset! navs [{:href "#"
                :value "World"}
              {:href "#"
               :value "China"}
              {:href "#"
               :value "China1"}
              {:href "#"
               :value "China2"}])

(reset! posts [{:id "post1"
               :title   "Sample blog post"
               :meta    "January 1, 2014 by"
               :author  "soul"
               :content "asasfasfasffsd"}
              {:id "post2"
               :title   "Another blog post"
               :meta    "December 23, 2013 by "
               :author  "jiesoul"
               :content "Cum sociis natoque penatibus et magnis"}])

(reset! archives [{:href "#"
                    :time "March 2018"}
                  {:href "#"
                   :time "May 2018"}])


(defn ^:export init []
  (if (and js/document
           (.-getElementById js/document))
    (r/render
      [home-component]
      (dom/by-id "app"))))

組件化登陸頁面

文件:src-cljs/soul_talk/login.cljs

注意:兩個輸入框的 required 屬性得刪除揖曾,否則會影響邏輯流程

(ns soul-talk.login
  (:require [domina :as dom]
            [domina.events :as ev]
            [reagent.core :as reagent :refer [atom]]
            ;; 引入共享代碼
            [soul-talk.auth-validate :refer [validate-email validate-password]]))


;; 這個函數(shù)提交的時候被調(diào)用,驗(yàn)證輸入是否正確
(defn validate-form []
  (let [email (dom/by-id "email")
        password (dom/by-id "password")]
    (if (and (-> email dom/value validate-email ) (-> password dom/value validate-password))
      true
      (do
        (js/alert "email和密碼不能為空")
        false))))


;; 如果驗(yàn)證不成功亥啦,則在輸入框上增加樣式炭剪;
;; 如果驗(yàn)證成功,則移除樣式
;; 這個函數(shù)翔脱,輸入框失去焦點(diǎn)的時候被調(diào)用
(defn validate-invalid [input-id vali-fun]
  (if-not (vali-fun (dom/value input-id)) ;; 修改奴拦,驗(yàn)證函數(shù)傳入文本,而不是 HTML 元素
    (dom/add-class! input-id "is-invalid")
    (dom/remove-class! input-id "is-invalid")))
    
    
;; 組件化登陸表單
(defn login-component []
  ;; 登陸表單
  [:form#loginForm.form-signin {:action "/login" :method "post"}
    ;; 標(biāo)題
    [:h1.h3.mb-3.font-weight-normal "Please sign in"]

    ;; Email 部分
    [:div.form-group
      ;; Email 標(biāo)簽
      [:label.sr-only "email" "email"]
      ;; Email 輸入框
      [:input#email.form-control
        {:type "text" 
         :name "email" 
         :auto-focus true 
         :placeholder "Email Address"
         ;; 焦點(diǎn)丟失的時候届吁,調(diào)用驗(yàn)證函數(shù)
         :on-blur #(validate-invalid (dom/by-id "email") validate-email)}] 
      ;; 錯誤提示信息
      [:div.invalid-feedback "無效的 Email"]] 

    ;; 密碼部分
    [:div.form-group
      ;; 密碼輸入框
      [:label.sr-only "password" "password"]
      [:input#password.form-control
        {:type "password" 
         :name "password" 
         :placeholder "password"
         ;; 焦點(diǎn)丟失的時候错妖,調(diào)用驗(yàn)證函數(shù)
         :on-blur    #(validate-invalid (dom/by-id "password") validate-password)}]
      ;; 錯誤提示信息
      [:div.invalid-feedback "無效的密碼"]] 


    ;; “記住我” 復(fù)選框
    [:div.form-group.form-check
      [:input#rememeber.form-check-input {:type "checkbox"}]
      [:label "記住我"]]

    ;; 錯誤信息
    [:div#error]

    ;; 提交按鈕
    [:input#submit.btn.btn-lg.btn-primary.btn-block {:type "submit" :value "登錄"}]

    ;; 版權(quán)信息
    [:p.mt-5.mb-3.text-muted "&copy @2018"]])


;; 渲染登陸表單組件绿鸣,并掛載到 `content` div元素上
(reagent/render
  [login-component] (dom/by-id "content"))



;; 為 Form 綁定 onsubmit 處理函數(shù)
;; 導(dǎo)出該函數(shù),從頁面調(diào)用
(defn ^:export init []
  ;; 渲染登陸表單組件暂氯,并掛載到 `div#content` 元素上
  (reagent/render
    [login-component] (dom/by-id "content"))

  (if (and js/document (.-getElementById js/document))
    (let [login-form (dom/by-id "loginForm")]
      (set! (.-onsubmit login-form) validate-form))))

注意最后這里:必須先掛在組件潮模,然后再綁定元素事件,否則元素不存在會報錯痴施。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末擎厢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辣吃,更是在濱河造成了極大的恐慌动遭,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件神得,死亡現(xiàn)場離奇詭異厘惦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)循头,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門绵估,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卡骂,你說我怎么就攤上這事国裳。” “怎么了全跨?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵缝左,是天一觀的道長。 經(jīng)常有香客問我浓若,道長渺杉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任挪钓,我火速辦了婚禮是越,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碌上。我一直安慰自己倚评,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布馏予。 她就那樣靜靜地躺著天梧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霞丧。 梳的紋絲不亂的頭發(fā)上呢岗,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼后豫。 笑死悉尾,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挫酿。 我是一名探鬼主播焕襟,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饭豹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起务漩,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤拄衰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饵骨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翘悉,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年居触,在試婚紗的時候發(fā)現(xiàn)自己被綠了妖混。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡轮洋,死狀恐怖制市,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弊予,我是刑警寧澤祥楣,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站汉柒,受9級特大地震影響误褪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碾褂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一兽间、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧正塌,春花似錦嘀略、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至问裕,卻和暖如春逮壁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粮宛。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工窥淆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卖宠,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓忧饭,卻偏偏與公主長得像扛伍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子词裤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,090評論 1 32
  • # 傳智播客vue 學(xué)習(xí)## 1. 什么是 Vue.js* Vue 開發(fā)手機(jī) APP 需要借助于 Weex* Vu...
    再見天才閱讀 3,530評論 0 6
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫刺洒、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,059評論 4 62
  • 文/冰雪伊人 —詞曲系列— 微雨暗長汀吼砂,春愁向晚燈逆航。待風(fēng)來,一縷飄輕渔肩。紙上墨痕詩里字因俐。江南韻、總分明周偎。 湖色正疏凝...
    西域冰雪閱讀 3,571評論 14 43
  • 借口之一: 我要考慮考慮 借口之二:太貴了 借口之三:別家更便宜 借口之四:超出預(yù)算 借口之五:我很滿意目前的所用...
    胖子叔閱讀 4,409評論 0 2