Luminus 를 이용한 Clojure 방명록 만들기 (4)
Creating Pages and Handling Form Input
guestbook.routes.home
네임스페이스에 경로가 정의되어 있습니다. 그러면 파일을 열어서 데이터베이스로부터 메시지를 렌더링하는 로직을 넣어보겠습니다. Bouncer 검증과 ring.util.response 레퍼런스와 함께 db 네임스페이스 레퍼런스를 추가해야합니다.
/home/fukaerii/guestbook/src/clj/guestbook/routes/home.clj
1 2 3 4 5 6 7 8 | (ns guestbook.routes.home (:require [guestbook.layout :as layout] [compojure.core :refer [defroutes GET POST]] [ring.util.http-response :as response] [clojure.java.io :as io] [guestbook.db.core :as db] [struct.core :as st])) | cs |
다음으로 폼 매개변수를 정의하는 스키마를 생성하고, 이를 검증하는 함수를 추가합니다.
1 2 3 4 5 6 7 8 9 10 11 | (def message-schema [[:name st/required st/string] [:message st/required st/string {:message "message must contain at least 10 characters" :validate #(> (count %) 9)}]]) (defn validate-message [params] (first (st/validate params message-schema))) | cs |
:name 과 :message 키가 우리가 정의한 규칙에 부합하는지 확인하기 위해 Struct 라이브러리로부터 검증 함수를 가져와 사용합니다. 특히 이름은 최소 10자 이상이어야 합니다. 그러면 메시지를 검증하고 저장하는 함수를 추가해보겠습니다.
1 2 3 4 5 6 7 8 | (defn save-message! [{:keys [params]}] (if-let [errors (validate-message params)] (-> (response/found "/") (assoc :flash (assoc params :errors errors))) (do (db/save-message! (assoc params :timestamp (java.util.Date.))) (response/found "/")))) | cs |
폼 매개변수를 포함한 요청으로부터 :params
키를 뽑아냅니다. validate-message
함수가 에러를 반환해내면 / 로 리다이렉트 되도록 정의합니다.
홈페이지 핸들러 함수를 다음과 같이 변경합니다.
[기존]
1 2 3 | (defn home-page [] (layout/render "home.html" {:docs (-> "docs/docs.md" io/resource slurp)})) | cs |
[변경]
1 2 3 4 5 | (defn home-page [{:keys [flash]}] (layout/render "home.html" (merge {:messages (db/get-messages)} (select-keys flash [:name :message :errors])))) | cs |
홈페이지 템플릿을 렌더링하고, 예를 들어, 검증 에러 등 :flash 세션으로부터 파라메터와 함께 현재 저장된 메시지에 전달합니다.
guestbook.db.core 네임스페이스에 있는
(conman/bind-connection *db* "sql/queries.sql") 명령을 통해 데이터베이스 접근 함수가 자동으로 생성되었음을 기억하시기 바랍니다. 이러한 함수의 명칭은
resources/sq/queries.sql 파일의 SQL 템플릿의 :name
코멘트로부터 추론이 가능합니다.
1 2 3 4 5 | (defroutes home-routes (GET "/" request (home-page request)) (POST "/" request (save-message! request)) (GET "/about" [] (about-page))) | cs |
compojure.core 으로부터 POST 를 참조하는 것을 잊지 마세요.
1 2 3 4 5 6 7 | (ns guestbook.routes.home (:require [guestbook.layout :as layout] [compojure.core :refer [defroutes GET POST]] [ring.util.http-response :as response] [clojure.java.io :as io] [guestbook.db.core :as db] [struct.core :as st])) | cs |
resources/html
디렉토리에 위치한 home.html
템플릿을 열어봅시다. 지금은 단순하게 content 블록 안에서 content 변수의 내용만 렌더링하고 있습니다. 1 2 3 4 5 6 7 8 | {% extends "base.html" %} {% block content %} <div class="row"> <div class="col-sm-12"> {{docs|markdown}} </div> </div> {% endblock %} | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | {% extends "base.html" %} {% block content %} <div class="row"> <div class="span12"> <ul class="messages"> {% for item in messages %} <li> <time>{{item.timestamp|date:"yyyy-MM-dd HH:mm"}}</time> <p>{{item.message}}</p> <p> - {{item.name}}</p> </li> {% endfor %} </ul> </div> </div> {% endblock %} | cs |
마지막으로 사용자가 메시지를 입력할 수 있도록 폼을 만들어 봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <div class="row"> <div class="span12"> <form method="POST" action="/"> {% csrf-field %} <p> Name: <input class="form-control" type="text" name="name" value="{{name}}" /> </p> {% if errors.name %} <div class="alert alert-danger">{{errors.name|join}}</div> {% endif %} <p> Message: <textarea class="form-control" rows="4" cols="50" name="message">{{message}}</textarea> </p> {% if errors.message %} <div class="alert alert-danger">{{errors.message|join}}</div> {% endif %} <input type="submit" class="btn btn-primary" value="comment" /> </form> </div> </div> | cs |
최종 home.html
템플릿의 모양은 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | {% extends "base.html" %} {% block content %} <div class="row"> <div class="span12"> <ul class="messages"> {% for item in messages %} <li> <time>{{item.timestamp|date:"yyyy-MM-dd HH:mm"}}</time> <p>{{item.message}}</p> <p> - {{item.name}}</p> </li> {% endfor %} </ul> </div> </div> <div class="row"> <div class="span12"> <form method="POST" action="/"> {% csrf-field %} <p> Name: <input class="form-control" type="text" name="name" value="{{name}}" /> </p> {% if errors.name %} <div class="alert alert-danger">{{errors.name|join}}</div> {% endif %} <p> Message: <textarea class="form-control" rows="4" cols="50" name="message">{{message}}</textarea> </p> {% if errors.message %} <div class="alert alert-danger">{{errors.message|join}}</div> {% endif %} <input type="submit" class="btn btn-primary" value="comment" /> </form> </div> </div> {% endblock %} | cs |
마지막으로
파일을 업데이트합니다.resources/public/css 폴더 아래의
screen.css
/home/fukaerii/guestbook/resources/public/css/screen.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; height: 100%; } .navbar { margin-bottom: 10px; border-radius: 0px; } .navbar-brand { float: none; } .navbar-nav .nav-item { float: none; } .navbar-divider, .navbar-nav .nav-item+.nav-item, .navbar-nav .nav-link + .nav-link { margin-left: 0; } @media (min-width: 34em) { .navbar-brand { float: left; } .navbar-nav .nav-item { float: left; } .navbar-divider, .navbar-nav .nav-item+.nav-item, .navbar-nav .nav-link + .nav-link { margin-left: 1rem; } } | cs |
'프로그래밍 Programming' 카테고리의 다른 글
Luminus 를 이용한 Clojure 방명록 만들기 (5) - 테스트 및 패키지화 (0) | 2018.08.23 |
---|---|
Test failed in clojure luminus guestbook tutorial (0) | 2018.08.23 |
Luminus 를 이용한 Clojure 방명록 만들기 (3) - 어플리케이션 구동 (0) | 2018.08.21 |
Luminus 를 이용한 Clojure 방명록 만들기 (2) - H2 데이터베이스 생성 및 접근 (0) | 2018.08.21 |
Luminus 를 이용한 Clojure 방명록 만들기 (1) - 프로젝트 생성 및 루미너스 어플리케이션 구조 (0) | 2018.08.21 |