[Weblocks] Weblocks - widgets - DataGrid, GridEdit

Common LispのWeb Application Framework, Weblocksには便利な widgetがいろいろと用意されています。前回の Login widgetのように Webアプリケーションを作っていると使うことになる画面部品は標準で用意されていますし、それ以外のモノも結構簡単に自分で定義することが出来ます。今回は標準で用意されたwidgetの中から DataGrid と GridEditの2つです。

なぜ2つ同時かと言うと、DataGridは GridEditのReadOnly版だからです。使い方や見た目はほぼ同じっぽいので纏めました。

DataGrid & GridEditは表を作成する為のWidgetです。GridEditは表への追加、変更、削除の機能も付いています。

GridEditで定義されている Slotは以下。

drilldown-type (or :view :edit)。
:viewを指定すると表のレコードをクリックした際に詳細内容表示→ [Modify]リンク押下 → 内容編集という流れになる。
:editを指定すると詳細表示を飛ばして、即内容編集可能な状態になる。

DataGridではSlotは定義していませんが dataseqというWidgetを継承しています(ちなみにGridEditはDataGridを継承しています)。

DataSeqのslotは以下。

view 一覧表示時のview。
defviewの際、:type tableを指定したviewを使用。
data-class 一覧表示の1レコードを表すクラス名
class-store 特に指定しなければ data-classの内容から勝手に生成
on-query まだ調べてない。
allow-sorting-p 一覧のヘッダをクリックしたときにソートするか否か。
sort ソート条件。(ソート項目名 . :asc)か(ソート項目名 . :desc)
allow-select-p 削除のための選択チェックボックスを付けるか否か。
nilにすると レコードの削除が出来なくなる。
selection まだ調べてない。
allow-drilldown-p レコードをクリックした際の詳細表示、内容編集フォームを出さない。
on-drilldown レコードクリック時に呼び出す関数?
drilled-down-item まだ調べてない。
autoset-drilled-item-p まだ調べてない。
allow-operations-p まだ調べてない。
item-ops まだ調べてない。
common-ops まだ調べてない。
allow-pagination-p ページングするか否か
pagination-widget ページングwidget。指定しなければ勝手に作られる。
show-total-items-count-p 総レコード数を表示するか否か。デフォルトでは表示(t)
flash 追加/変更/削除後のメッセージ用Widget。普通はflash widget
rendered-data-sequence まだ調べてない。

調べてないばかりでスミマセン・・・

また GridEditは dataedit-mixinというクラスを継承しています。dataedit-mixinのslotは以下。

on-add-item 調べてないけど、レコード追加時に呼ばれる関数?
on-add-item-completed 調べてないけど、レコード追加完了時に呼ばれる関数?
allow-add-p 新規レコードを追加するための ADDボタンを付けるか否か。
show-add-form-when-empty-p t を指定した場合、データが0件のときは最初から新規追加フォームが開いている。
flash-message-on-first-add-p t を指定した場合、1レコード目の登録時も登録完了メッセージを表示。
on-delete-items 調べてないけど、レコード削除時に呼ばれる関数?
on-delete-items-completed 調べてないけど、レコード削除完了時に呼ばれる関数?
cascade-delete-mixins-p まだ調べてない。
allow-delete-p レコードを削除するための DELETEボタンをつけるか否か。
item-data-view データの View。defviewの際、:type dataで定義した view。
item-form-view フォームの View。defviewの際、:type formで定義した view。
auteset-drilled-down-item-p initargが無い・・・
ui-state まだ調べてない。
item-widget initargが無い・・・

dataedit-mixinでとても大事なのが item-data-viewと item-form-viewの2つです。
この2つはそれぞれ 詳細表示時の表示項目、新規追加/データ編集時の表示項目として使用されます。*1

;;; 1. 表示するクラスを定義
(defclass user (store-template)
  ((id)
   (login-id :accessor user-login-id
             :initarg :login-id
             :type string)
   (password :accessor user-password
             :initarg :password
             :type string)
   (name :accessor user-name
         :initarg :name
         :type string)
   (birthday :accessor user-birthday
             :initarg :birthday)))

;;; 2. TABLE VIEWを定義
(defview user-table-view (:type table :inherit-from '(:scaffold user))
  (id :hidep t)
  (login-id)
  (password :hidep t)
  (name :label "氏名")
  (birthday :label "誕生日"
            :reader (lambda (user)
                      (multiple-value-bind (s m h d mo y)
                          (decode-universal-time (user-birthday user))
                        (declare (ignore s m h))
                        (format nil "~4,'0d/~2,'0d/~2,'0d" y mo d)))))

;;; 3. DATA VIEWを定義
(defview user-data-view (:type data :inherit-from '(:scaffold user))
  (id :hidep t)
  (login-id)
  (password :hidep t)
  (name :label "氏名")
  (birthday :label "誕生日"
            :reader (lambda (user)
                      (multiple-value-bind (s m h d mo y)
                          (decode-universal-time (user-birthday user))
                        (declare (ignore s m h))
                        (format nil "~4,'0d/~2,'0d/~2,'0d" y mo d)))))

;;; 4. FORM VIEWを定義
(defview user-form-view (:type form :inherit-from '(:scaffold user))
  (id :hidep t)
  (login-id :requiredp t)
  (password :label "パスワード"
            :reader (lambda (obj)
                      "")
            :writer (lambda (value obj)
                      (setf (user-password obj)
                            (hash-password value))))
  (name :label "氏名" :requiredp t)
  (birthday :label "誕生日"
            :reader (lambda (user)
                      (multiple-value-bind (s m h d mo y)
                          (decode-universal-time (user-birthday user))
                        (declare (ignore s m h))
                        (format nil "~4,'0d/~2,'0d/~2,'0d" y mo d)))
            :writer (lambda (value obj)
                      (setf (slot-value obj 'birthday)
                            (yyyy/mm/dd->utime value)))))

;;; 5. 初期ページを作る関数
(defun initial-page ()
  (make-instance
     'widget :children
     (list
        (f_% (with-html (:p "Data Grid")))
        (make-instance
           'datagrid
           :name 'user-data-grid
           :data-class 'user
           :view 'user-table-view)

        (f_% (with-html (:hr) (:p "Grid Edit[DrillDown-Type=:view]")))
        (make-instance
           'gridedit
           :name 'user-edit-grid-view
           :drilldown-type :view
           :data-class 'user
           :view 'user-table-view
           :item-data-view 'user-data-view
           :item-form-view 'user-form-view
           )
        )))

;;; 6. 最初に呼ばれてるっぽい関数
(defun init-user-session (comp)
  (with-flow comp
    (yield (initial-page)) ))

やっていることは

  1. 一覧に表示するためのクラスを定義(項目 id login-id password name birthday)
  2. TABLE VIEWを定義(userクラスから自動生成, idとpasswordは非表示, birthdayはyyyy/mm/dd形式)
  3. DATA VIEWを定義(userクラスから自動生成, idとpasswordは非表示, birthdayはyyyy/mm/dd形式)
  4. FORM VIEWを定義(userクラスから自動生成, idは非表示, passwordは空欄表示で入力値は 暗号化して保存, birthdayはyyyy/mm/dd形式で表示し、yyyy/mm/dd形式の文字列をuniversal-timeに変換して保存)
  5. DataGridとGridEditを画面表示するための関数を定義
  6. Sessionが無い状態で表示される画面にする

自分で書いておいてなんですが、この説明全然わかんない。

*1:viewを作成する defviewはまた後日