Skip to content

Commit

Permalink
[simple-v-table] Add TSV export button to simple-v-table
Browse files Browse the repository at this point in the history
  • Loading branch information
kimo-k committed Feb 2, 2024
1 parent 87a3c8e commit 96c9b65
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 22 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

> Committed but unreleased changes are put here, at the top. Older releases are detailed chronologically below.
## 2.18.0 (2024-02-02)

#### Added

- `simple-v-table` - `:on-export`, `:export-button-renderer` and `:show-export-button?`

## 2.17.1 (2024-01-24)

#### Fixed
Expand Down
41 changes: 33 additions & 8 deletions src/re_com/simple_v_table.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
[re-com.validate :refer [validate-args-macro]])
(:require
[reagent.core :as reagent]
[re-com.buttons :refer [hyperlink row-button]]
[re-com.config :refer [include-args-desc?]]
[re-com.box :refer [box h-box gap]]
[re-com.util :refer [px deref-or-value assoc-in-if-empty ->v position-for-id item-for-id remove-id-item]]
[re-com.util :refer [px deref-or-value assoc-in-if-empty ->v position-for-id item-for-id remove-id-item clipboard-write! table->tsv]]
[re-com.text :refer [label]]
[re-com.validate :refer [vector-of-maps? vector-atom? parts?]]
[re-com.v-table :as v-table]))
Expand Down Expand Up @@ -204,12 +205,15 @@

(def simple-v-table-args-desc
(when include-args-desc?
[{:name :model :required true :type "r/atom containing vec of maps" #_#_:validate-fn vector-atom? :description "one element for each row in the table."}
[{:name :model :required true :type "r/atom containing vec of maps" :validate-fn vector-atom? :description "one element for each row in the table."}
{:name :columns :required true :type "vector of maps" :validate-fn vector-of-maps? :description [:span "one element for each column in the table. Must contain " [:code ":id"] "," [:code ":header-label"] "," [:code ":row-label-fn"] "," [:code ":width"] ", and " [:code ":height"] ". Optionally contains " [:code ":sort-by"] ", " [:code ":align"] " and " [:code ":vertical-align"] ". " [:code ":sort-by"] " can be " [:code "true"] " or a map optionally containing " [:code ":key-fn"] " and " [:code ":comp"] " ala " [:code "cljs.core/sort-by"] "."]}
{:name :fixed-column-count :required false :default 0 :type "integer" :validate-fn number? :description "the number of fixed (non-scrolling) columns on the left."}
{:name :fixed-column-border-color :required false :default "#BBBEC0" :type "string" :validate-fn string? :description [:span "The CSS color of the horizontal border between the fixed columns on the left, and the other columns on the right. " [:code ":fixed-column-count"] " must be > 0 to be visible."]}
{:name :column-header-height :required false :default 31 :type "integer" :validate-fn number? :description [:span "px height of the column header section. Typically, equals " [:code ":row-height"] " * number-of-column-header-rows."]}
{:name :column-header-renderer :required false :type "cols parts sort-by-col -> hiccup" :validate-fn ifn? :description "You can provide a renderer function to override the inbuilt renderer for the columns headings"}
{:name :column-header-renderer :required false :type "cols parts sort-by-col -> hiccup" :validate-fn ifn? :description [:span "You can provide a renderer function to override the inbuilt renderer for the columns headings"]}
{:name :show-export-button? :required false :default false :type "boolean" :description [:span "When non-nil, adds a hiccup of " [:code ":export-button-render"] " to the component tree."]}
{:name :on-export :required false :type "columns, sorted-rows -> nil" :validate-fn ifn? :description "Called whenever the export button is clicked."}
{:name :export-button-renderer :required false :type "{:keys [columns rows on-export]} -> hiccup" :validate-fn ifn? :description [:span "Pass a component function to override the built-in export button. Declares a hiccup of your component in the " [:code ":top-right-renderer"] "position of the underlying " [:code "v-table"] "."]}
{:name :max-width :required false :type "string" :validate-fn string? :description "standard CSS max-width setting of the entire table. Literally constrains the table to the given width so that if the table is wider than this it will add scrollbars. Ignored if value is larger than the combined width of all the columns and table padding."}
{:name :max-rows :required false :type "integer" :validate-fn number? :description "The maximum number of rows to display in the table without scrolling. If not provided will take up all available vertical space."}
{:name :row-height :required false :default 31 :type "integer" :validate-fn number? :description "px height of each row."}
Expand Down Expand Up @@ -238,6 +242,13 @@
(remove zero?)
(first)) 0)))

(defn clipboard-export-button [{:keys [columns rows on-export]}]
[row-button :src (at)
:md-icon-name "zmdi zmdi-copy"
:mouse-over-row? true
:tooltip (str "Copy " (count rows) " rows, " (count columns) " columns to clipboard.")
:on-click on-export])

(defn simple-v-table
"Render a v-table and introduce the concept of columns (provide a spec for each).
Of the nine possible sections of v-table, this table only supports four:
Expand All @@ -254,6 +265,7 @@
(fn simple-v-table-render
[& {:keys [model columns fixed-column-count fixed-column-border-color column-header-height column-header-renderer
max-width max-rows row-height table-padding table-row-line-color on-click-row on-enter-row on-leave-row
show-export-button? on-export export-button-renderer
striped? row-style cell-style class parts src debug-as]

:or {column-header-height 31
Expand All @@ -262,7 +274,12 @@
table-padding 19
table-row-line-color "#EAEEF1"
fixed-column-border-color "#BBBEC0"
column-header-renderer column-header-renderer}
column-header-renderer column-header-renderer
show-export-button? false
on-export (fn [{:keys [columns rows]}] (-> (remove (comp false? :export?) columns)
(table->tsv rows)
clipboard-write!))
export-button-renderer clipboard-export-button}
:as args}]
(or
(validate-args-macro simple-v-table-args-desc args)
Expand Down Expand Up @@ -312,12 +329,20 @@
:row-content-width content-width
:row-height row-height
:max-row-viewport-height (when max-rows (* max-rows row-height))
;:max-width (px (or max-width (+ fixed-content-width content-width v-table/scrollbar-tot-thick))) ; :max-width handled by enclosing parent above
;:max-width (px (or max-width (+ fixed-content-width content-width v-table/scrollbar-tot-thick))) ; :max-width handled by enclosing parent above

;; ===== Corners (section 1)
;; ===== Corners (section 1, 3)
:top-left-renderer (partial column-header-renderer fixed-cols parts sort-by-column) ;; Used when there are fixed columns

;; ===== Styling
:top-right-renderer (when show-export-button?
#(let [rows (deref-or-value model)
columns (deref-or-value columns)
sort-by-column (deref-or-value sort-by-column)]
[export-button-renderer {:rows rows
:columns columns
:on-export (fn [_] (on-export {:columns columns
:rows (cond->> rows
sort-by-column (sort (multi-comparator (->v sort-by-column))))}))}]))
;; ===== Styling
:class class
:parts (cond-> (->
;; Remove the parts that are exclusive to simple-v-table, or v-table part
Expand Down
17 changes: 16 additions & 1 deletion src/re_com/util.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
(:require
[reagent.ratom :refer [RAtom Reaction RCursor Track Wrapper]]
[goog.date.DateTime]
[goog.date.UtcDateTime]))
[goog.date.UtcDateTime]
[clojure.string :as str]))

(defn fmap
"Takes a function 'f' amd a map 'm'. Applies 'f' to each value in 'm' and returns.
Expand Down Expand Up @@ -208,3 +209,17 @@
(.getMonth local-date-time)
(.getDate local-date-time)
0 0 0 0)))

(defn clipboard-write! [s]
^js (-> js/navigator .-clipboard (.writeText s)))

(defn table->tsv [columns rows]
(let [header-value-fn (some-fn :export-header-label :header-label (comp name :id))
row-value-fn (some-fn :row-export-fn :row-label-fn :id)
row->cells (apply juxt (map row-value-fn columns))
tsv-line #(str (str/join "\t" %) "\n")]
(->> rows
(map row->cells)
(cons (map header-value-fn columns))
(map tsv-line)
(apply str))))
23 changes: 18 additions & 5 deletions src/re_demo/simple_v_table.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,35 @@
[:ul
[:li "A table's dimensions will grow and shrink, to fit the space provided by its parent. When the parent imposes dimensions that are insufficient to show all of the table, scrollbars will appear."]
[:li "Other times, we want a table to impose certain dimensions. Eg, it should always show 10 rows, and have no horizontal scrollbar, and we want the parent dimensions to change to accommodate."]
[:li "Width"
[:li [:strong "Width"]
[:ul
[:li "The full horizontal extent of the table is determined by the accumulated width of the columns"]
[:li "If the width provided by the table's parent container is less than this extent, then horizontal scrollbars will appear for the unfixed columns"]
[:li "Where you wish to be explicit about the table's viewable width, use the " [:code ":max-width"] " arg"]]]
[:li "Height"
[:li [:strong "Height"]
[:ul
[:li "The full vertical extent of the table is determined by the accumulated height of all the rows"]
[:li "If the height provided by the table's parent container is less than this extent, then vertical scrollbars will appear"]
[:li "Where you wish to be explicit about the table's viewable height, use the " [:code ":max-rows"] " arg"]]]
[:li "Even if you are explicit via " [:code ":max-width"] " or " [:code ":max-rows"] ", the parent's dimensions will always dominate, if they are set"]]
[title3 "Sorting"]
[:ul
[:li "Items in " [:code ":columns"] " have an optional " [:code ":sort-by"] " key."]
[:li "If the value is " [:code "true"] ", clicking the column header will sort all the rows, using the result of the column's " [:code ":row-label-fn"] " as a sort key."]
[:li [:code ":sort-by"] " can also be map, with optional keys " [:code ":comp"] " and " [:code ":keyfn"] ", corresponding to the parameters of " [:code "clojure.core/sort-by"] "."]]
[:li [:strong "sort-by"]
[:ul
[:li "Items in " [:code ":columns"] " have an optional " [:code ":sort-by"] " key."]
[:li "If the value is " [:code "true"] ", clicking the column header will sort all the rows, using the result of the column's " [:code ":row-label-fn"] " as a sort key."]
[:li [:code ":sort-by"] " can also be map, with optional keys " [:code ":comp"] " and " [:code ":keyfn"] ", corresponding to the parameters of " [:code "clojure.core/sort-by"] "."]]]
[:li [:strong "hierarchical sort"]
[:ul
[:li "Shift-clicking a column header will conjoin that column into a hierarchical sort. A number will appear next to the sort icon, indicating its sorting precedence."]
[:li "For instance, click " [:strong "name"] ", then shift-click " [:strong "units"] ". The columns will appear as " [:strong "name ↓1 ... units ↓2."]]
[:li "Rows will sort by the column marked " [:strong "1"] ", except when any two rows compare equally, they will sort by " [:strong "2"] "."]]]]
[title3 "Export"]
[:ul
[:li "Pass " [:code ":show-export-button? true"] " to mount the export-button component into section 3 of the table (see " [:code ":top-right-renderer"] " in " [:code "v-table"] "."]
[:li "Clicking this button invokes " [:code ":on-export"] " with keyword arguments " [:code ":rows"] " and " [:code ":columns"] ". " [:code ":rows"] " is sorted."]
[:li "The default " [:code ":on-export"] " handler removes any columns declared with " [:code ":export? false"] ", serializes a TSV string and writes it to the clipboard."]
[:li "You can also pass your own component function as " [:code ":export-button-renderer"] ". It can accept keyword arguments " [:code ":rows, :columns, :on-export"] "."]]
[p "The \"Sales Table Demo\" (to the right) allows you to experiment with these concepts."]]])

(defn dependencies
Expand Down
20 changes: 12 additions & 8 deletions src/re_demo/simple_v_table_sales.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[re-demo.utils :refer [title3]]
[re-com.util :refer [px]]))

;; 50 randomly sampled names from most popular baby names in 2019.
;; 50 randomly sampled names from most popular baby names in 2019.
(def names
["Harris" "Jake" "Reece" "Aston" "Barry" "Oran" "Ritchie" "Crawford" "Raphael" "Clayton" "Johan" "Rhylen" "Caelin"
"Calen" "Cassius" "Dakota" "Fabien" "Fraser-Lee" "Jonathin" "Khabat" "Lyotard" "Manpreet" "Mousa" "Rajvir" "Shadan"
Expand Down Expand Up @@ -318,14 +318,18 @@
:src (at)
:model sales-rows

;; ==== Exporting
:show-export-button? true

;; ===== Columns
:columns [{:id :id :header-label "Code" :row-label-fn :id :width 70 :align "center" :vertical-align "middle"}
{:id :region :header-label "Region" :row-label-fn :region :width 100 :align "left" :vertical-align "middle"}
{:id :name :header-label "Name" :row-label-fn :person :width 100 :align "left" :vertical-align "middle" :sort-by {}}
{:id :email :header-label "Email" :row-label-fn email-row-label-fn :width 200 :align "left" :vertical-align "middle"}
{:id :method :header-label "Method" :row-label-fn method-row-label-fn :width 100 :align "center" :vertical-align "middle"}
{:id :sales :header-label "Sales" :row-label-fn #(str "$" (:sales %)) :width 100 :align "right" :vertical-align "middle" :sort-by {:key-fn :sales}}
{:id :units :header-label "Units" :row-label-fn :units :width 100 :align "right" :vertical-align "middle" :sort-by true}]
:columns
[{:id :id :header-label "Code" :row-label-fn :id :width 70 :align "center" :vertical-align "middle" :export? false}
{:id :region :header-label "Region" :row-label-fn :region :width 100 :align "left" :vertical-align "middle"}
{:id :name :header-label "Name" :row-label-fn :person :width 100 :align "left" :vertical-align "middle" :sort-by {}}
{:id :email :header-label "Email" :row-label-fn email-row-label-fn :width 200 :align "left" :vertical-align "middle" :row-export-fn :email}
{:id :method :header-label "Method" :row-label-fn method-row-label-fn :width 100 :align "center" :vertical-align "middle" :row-export-fn (comp name :method)}
{:id :sales :header-label "Sales" :row-label-fn #(str "$" (:sales %)) :width 100 :align "right" :vertical-align "middle" :sort-by {:key-fn :sales}}
{:id :units :header-label "Units" :row-label-fn :units :width 100 :align "right" :vertical-align "middle" :sort-by true}]
:fixed-column-count @fixed-column-count
:fixed-column-border-color "#333"

Expand Down

0 comments on commit 96c9b65

Please sign in to comment.