あのねノート

せんせいあのね、パソコンで字が書けるようになったよ

コメント機能も付けちゃう~Racketチュートリアル中級編をやってみた(第5回)

帰省先でパソコンが使えなかったため更新が途絶えてました。めげずにがんばります。

前回に引き続き、Racketドキュメントの中級チュートリアルをやっています。
Continue: Web Applications in Racket

前回までで、新規投稿した後のブログのページが他ユーザからも見られるようになりました。今回はコメントをつけるようです。

post構造体にコメントを加えます。変更可能でないといけないのでmutableです。

(struct post (title body comments) #:mutable)

初期状態BLOG内のpost構造体にもコメントのために空リストをつけておきます。

(define BLOG (blog (list (post "ふたつめ"
                         "表示されてるかな?"
                         (list))
                   (post "ひとつめ"
                         "はじめてだよ!"
                         (list))
                   )))

コメント投稿のための関数を作ります。

;post-insert-comment!:post string->void
;post構造体とコメントの文字列を受け取って、post構造体のコメントリストに追加する
(define (post-insert-comment! a-post a-comment)
  (set-post-comments!
   a-post
   (append (post-comments a-post) (list a-comment))))

parse-port関数はrender-blog-pageの内部定義になるみたい。
これにもコメントのためのリストが追加されます。

(define (parse-post bindings)
    (post (extract-binding/single 'title bindings)
          (extract-binding/single 'body bindings)
          (list)))

コメント投稿をするための詳細ページを用意します。

;render-post-detail-page:post request->doesn't return
;記事の詳細ページを表示する
;ここでコメント投稿ができる
(define (render-post-detail-page a-post request)
  (define (response-generator embed/url)
    (response/xexpr
     `(html (head (title "Post Details"))
            (body
             (h1 "Post Details")
             (h2 ,(post-title a-post))
             (p ,(post-body a-post))
             ,(render-as-itemized-list
               (post-comments a-post))
             (form ((action
                     ,(embed/url insert-comment-handler)))
                   (input ((name "comment")))
                   (input ((type "submit"))))))))
 
  (define (parse-comment bindings)
    (extract-binding/single 'comment bindings))
 
  (define (insert-comment-handler a-request)
    (post-insert-comment!
     a-post (parse-comment (request-bindings a-request)))
    (render-post-detail-page a-post a-request))
  (send/suspend/dispatch response-generator))

投稿したら詳細ページへのリンクも必要になるのでrender-post, render-postsも変わってきます。

;render-post:post (handler -> string)->xexpr
;post構造体を受け取ってX式に変換
;詳細ページへのリンクを張る
(define (render-post a-post embed/url)
  (define (view-post-handler request)
    (render-post-detail-page a-post request))
  `(div ((class "post"))
        (a ((href ,(embed/url view-post-handler)))
           ,(post-title a-post))
        (p ,(post-body a-post))
        (div ,(number->string (length (post-comments a-post)))
             " comment(s)")))

(define (render-posts embed/url)
  (define (render-post/embed/url a-post)
    (render-post a-post embed/url))
  `(div ((class "posts"))
        ,@(map render-post/embed/url (blog-posts BLOG))))

X式がらみで関数が追加されます。

;render-as-itemized-list:(listof xexpr)->xexpr
;X式のリストを1つのX式にして返す
(define (render-as-itemized-list fragments)
  `(ul ,@(map render-as-item fragments)))

(define (render-as-item a-fragment)
  `(li ,a-fragment))

実行してみると、詳細ページへのリンクができています。
f:id:momo_moemoe:20160815105430p:plain

追加もできます。
f:id:momo_moemoe:20160815105734p:plain

詳細ページからのコメント投稿はできるけど詳細ページから戻るときに戻るキーを使わないといけなくて、そのときに投稿したコメントが消えてしまうのが今の状態のようです。

戻るキーを追加します。
render-post-detail-page内のresponse-generatorにbackキーを追加します。

(define (response-generator embed/url)
    (response/xexpr
     `(html (head (title "Post Details"))
            (body
             (h1 "Post Details")
             (h2 ,(post-title a-post))
             (p ,(post-body a-post))
             ,(render-as-itemized-list
               (post-comments a-post))
             (form ((action
                     ,(embed/url insert-comment-handler)))
                   (input ((name "comment")))
                   (input ((type "submit"))))
             (a ((href ,(embed/url back-handler))) ;戻る
                "Back to the blog")))))

backする操作もrender-post-detail-page内に作ります。

(define (back-handler request)
    (render-blog-page request))

最初のページに戻る感じですね。

コメント追加の操作が変わります。

(define (insert-comment-handler request)
    (render-confirm-add-comment-page ;コメント確認ページを表示
     (parse-comment (request-bindings request))
     a-post
     request))

コメント追加確認ページを表示します。

;render-confirm-add-comment-page
;comment post request -> doesn't return
;コメントとリクエスト、対応する投稿を受け取ってコメント投稿確認ページを表示
(define (render-confirm-add-comment-page a-comment a-post request)
  (define (response-generator embed/url)
    (response/xexpr
     `(html (head (title "Add a Comment"))
            (body
             (h1 "Add a Comment")
             "The comment: " (div (p ,a-comment))
             "will be added to "
             (div ,(post-title a-post))
 
             (p (a ((href ,(embed/url yes-handler))) ;コメントを追加する
                   "Yes, add the comment."))
             (p (a ((href ,(embed/url cancel-handler))) ;しない
                   "No, I changed my mind!"))))))
 
  (define (yes-handler request)
    (post-insert-comment! a-post a-comment) ;コメント追加
    (render-post-detail-page a-post request)) ;追加後の詳細ページを表示
 
  (define (cancel-handler request)
    (render-post-detail-page a-post request)) ;そのまま詳細ページに戻る
  (send/suspend/dispatch response-generator))

コメント確認してくれるなんて親切設計!

動作確認してみます。
詳細ページからコメントを入力できます。「Back to the page」ボタンもあります。
f:id:momo_moemoe:20160819083225p:plain

コメント確認画面が出ます。
f:id:momo_moemoe:20160819083300p:plain

コメントを追加する方を選択するとコメントが追加された詳細ページが出てきます。
f:id:momo_moemoe:20160819083329p:plain

「みっつめ」のところに「1 comment(s)」と表示されます。
f:id:momo_moemoe:20160819083410p:plain

これで機能的にはいい感じになったようです。