forked from kishanov/re-frame-datatable
-
Notifications
You must be signed in to change notification settings - Fork 1
/
rendering.cljc
162 lines (144 loc) · 6.61 KB
/
rendering.cljc
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
(ns re-frame-datatable.rendering
"Rendering functions for the table"
(:require [clojure.string :as string]
[clojure.set :as set]
[re-frame.core :as re-frame]
[re-frame-datatable.events :as e]
[re-frame-datatable.subs :as subs]
[re-frame-datatable.sorting :as sorting]))
(def enabled-key :re-frame-datatable.core/enabled?)
(defn- css-class-str [classes]
{:class (->> classes
(filter (complement nil?))
(string/join \space))})
(defn- drag-drop-attrs
"Generates the attributes according to the drag-drop configuration. If the configuration
is empty, this just returns an empty map. The `-fn` arguments are functions that should
accept the row as argument. All but the `draggable-fn` functions should return a new
handler function. The `draggable-fn` should return a boolean value, to indicate whether
the row is draggable or not (this supersedes the `draggable?` option)."
[{:keys [::draggable? ::draggable-fn ::drag-fn ::drop-fn ::drag-over-fn]} row]
(cond-> {}
;; TODO Allow dropping something on the table itself, in addition to dropping on specific rows
draggable? (assoc :draggable true)
draggable-fn (assoc :draggable (draggable-fn row))
drag-fn (assoc :on-drag-start (drag-fn row))
drop-fn (assoc :on-drop (drop-fn row))
drag-over-fn (assoc :on-drag-over (drag-over-fn row))))
(defn- checked? [x]
#?(:cljs (-> x .-target .-checked)
:clj (get-in x [:target :checked])))
(defn- header [visible-items state db-id columns-def options]
(let [{:keys [:re-frame-datatable.core/selection
:re-frame-datatable.core/extra-header-row-component]} options
sel (get-in state [:selection :selected-indexes])]
[:thead
(when extra-header-row-component
[extra-header-row-component])
[:tr
(when (enabled-key selection)
[:th {:style {:max-width "16em"}}
[:input {:type "checkbox"
:checked (set/subset?
(->> visible-items (map first) (set))
sel)
:on-change #(when-not (zero? (count visible-items))
(re-frame/dispatch [:re-frame-datatable.core/change-table-selection
db-id
(->> visible-items (map first) (set))
(checked? %)]))}]
[:br]
[:small (str (count sel) " selected")]])
(doall
(for [{:keys [:re-frame-datatable.core/column-key
:re-frame-datatable.core/column-label
:re-frame-datatable.core/sorting]} columns-def]
^{:key (str column-key)}
[:th
(merge
(when (enabled-key sorting)
{:style {:cursor "pointer"}
:on-click #(re-frame/dispatch [::sorting/set-sort-key db-id column-key
(:re-frame-datatable.core/comp-fn sorting)])
:class "sorted-by"})
(when (= column-key (get-in state [:sorting :sort-key]))
(css-class-str ["sorted-by"
(condp = (get-in state [:sorting :sort-comp])
::sorting/sort-asc "asc"
::sorting/sort-desc "desc"
"")])))
(cond
(string? column-label) column-label
(fn? column-label) [column-label]
:else "")]))]]))
(defn- empty-table [columns-def state options]
(let [{:keys [:selection]} state
{:keys [:re-frame-datatable.core/empty-tbody-component]} options]
[:tr
[:td {:col-span (+ (count columns-def)
(if (enabled-key selection) 1 0))
:style {:text-align "center"}}
(if empty-tbody-component
[empty-tbody-component]
"no items")]]))
(defn- table-row [db-id columns-def state options [i data-entry]]
(let [{:keys [:re-frame-datatable.core/tr-class-fn
:re-frame-datatable.core/tr-attr-fn
:re-frame-datatable.core/drag-drop
:re-frame-datatable.core/selection]} options]
[:tr
(merge
{}
(when tr-class-fn
(css-class-str (tr-class-fn data-entry)))
;; Add the attributes for drag/drop operations, if any
(drag-drop-attrs drag-drop data-entry)
(when tr-attr-fn (tr-attr-fn data-entry)))
(when (enabled-key selection)
[:td
[:input {:type "checkbox"
:checked (contains? (get-in state [:selection :selected-indexes]) i)
:on-change #(re-frame/dispatch [:re-frame-datatable.core/change-row-selection
db-id i (checked? %)])}]])
(doall
(for [{:keys [:re-frame-datatable.core/column-key
:re-frame-datatable.core/render-fn
:re-frame-datatable.core/td-class-fn
:re-frame-datatable.core/td-attr-fn]} columns-def]
^{:key (str i \- column-key)}
[:td
(merge
{}
;; If given, apply any custom attributes
(when td-attr-fn
(td-attr-fn (get-in data-entry column-key) data-entry))
(when td-class-fn
(css-class-str (td-class-fn (get-in data-entry column-key) data-entry))))
(if render-fn
[render-fn (get-in data-entry column-key) data-entry]
(get-in data-entry column-key))]))]))
(defn- body [visible-items state db-id columns-def options]
(if (empty? visible-items)
[:tbody [empty-table columns-def state options]]
;; Non-empty table
(->> visible-items
(map (partial table-row db-id columns-def state options))
(into [:tbody]))))
(defn render-table [db-id data-sub columns-def options]
;; Store initial configuration in db
(re-frame/dispatch [::e/on-will-mount db-id data-sub columns-def options])
(fn [& _]
(let [view-data (re-frame/subscribe [::subs/data db-id data-sub (:re-frame-datatable.core/pagination options)])
{:keys [:visible-items :state]} @view-data
{:keys [:re-frame-datatable.core/table-classes
:re-frame-datatable.core/header-enabled?
:re-frame-datatable.core/footer-component]} options]
[:table.re-frame-datatable
(when table-classes
(css-class-str table-classes))
(when-not (= header-enabled? false)
[header visible-items state db-id columns-def options])
[body visible-items state db-id columns-def options]
(when footer-component
[:tfoot
[footer-component]])])))