Skip to content

Instantly share code, notes, and snippets.

@JulienRouse
Last active May 9, 2019 15:09
Show Gist options
  • Save JulienRouse/32b3119f305491f70c70b30df0b64859 to your computer and use it in GitHub Desktop.
Save JulienRouse/32b3119f305491f70c70b30df0b64859 to your computer and use it in GitHub Desktop.
Little table for employee with filtering. Use Reagent, Re-Frame and spec (and there is a klipse version for trying it out in the browser)
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="http://app.klipse.tech/css/codemirror.css">
<script>
window.klipse_settings = {
eval_idle_msec: 200, // idle time in msec before the snippet is evaluated
selector: '.clojure',// css selector for the html elements you want to klipsify
selector_reagent: '.reagent', // selector for reagent snippets
codemirror_options_in: {
indentUnit: 2,
lineWrapping: true,
lineNumbers: true,
autoCloseBrackets: true
},
codemirror_options_out: {
lineWrapping: true,
lineNumbers: true
}
};
</script>
<style>
table {
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #ddd;
padding: 8px;
}
tr:nth-child(even){background-color: #f2f2f2;}
tr:hover {background-color: #ddd;}
th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4CAF50;
color: white;
}
</style>
</head>
<body>
<div class="clojure">
(ns my.reagent-examples
(:require [reagent.core :as reagent]
[cljs.spec.alpha :as s]
[re-frame.db :as db]
[re-frame.core :as rf]))
;; -- Specs --
;; -----------
(s/def ::name string?) ; Name is a string
(s/def ::date-fin-mandat inst?) ; date is of type #inst (a timestamp)
(s/def ::role keyword?) ;role is a keyword
; One people is a map with required keys name, date and role and no optional keys
(s/def ::people (s/keys :req [::name ::date-fin-mandat ::role] ;; ?Should I use :req or :un-req ?
:opt []))
; Peoples is a vector of 0 to many people
(s/def ::peoples (s/coll-of ::people :kind vector? :min-count 0))
; A filter is a 2 elements vector of keyword like [:role :dev]
(s/def ::filter (s/tuple keyword? (s/or :s string? :k keyword?)))
; Filters is a vector of 0 to many filter
(s/def ::filters (s/coll-of ::filter :kind vector? :min-count 0))
; Spec for the whole db
(s/def ::db (s/keys :req-un [::peoples ::filters]))
;; -- Data --
;; --------
(def peoples [
{::name "julien"
::date-fin-mandat (js/Date.)
::role :dev}
{::name "juscellino"
::date-fin-mandat (js/Date. 2019 7 21)
::role :dev}
{::name "danny"
::date-fin-mandat (js/Date. 2019 4 15)
::role :dev}
{::name "nathalie"
::date-fin-mandat (js/Date. 2031 9 22)
::role :rh}
{::name "malik"
::date-fin-mandat (js/Date. 2019 1 22)
::role :analyste}
{::name "daniel"
::date-fin-mandat (js/Date. 2019 8 15)
::role :dev}])
;; -- Helpers --
;; -------------
(defn filter-people
[peoples filters]
(into
[]
(set
(flatten
(mapv
(fn [[k v]]
(filter
#(= (k %1) v)
peoples))
filters)))))
(def end-soon-style {:background-color "red"})
(def end-not-so-soon-style {:background-color "orange"})
(def end-very-not-soon-style {:background-color "green"})
(defn date+30
[date]
(js/Date. (.getFullYear date) (.getMonth date) (+ (.getDate date) 30)))
(defn date+90
[date]
(js/Date. (.getFullYear date) (.getMonth date) (+ (.getDate date) 90)))
(defn style-by-date
[date end-soon end-not-so-soon end-very-not-soon]
(let [today (js/Date.)]
(cond
(>= today date) end-soon
(< date (date+30 today)) end-not-so-soon
:else end-very-not-soon)))
;; -- 1 - Dispatch event --
;; ------------------------
; Do i need this?
(defn dispatch-add-filter-event
[new-filter]
(rf/dispatch [:add-filter [:role :dev]]))
;; -- 2 - Event Handler --
;; -----------------------
;; -- Interceptor --
;; -----------------
; Interceptor for validating spec after every add into db
(defn check-and-throw
"Throws an exception if `db` doesn't match the Spec `a-spec`."
[a-spec db]
(when-not (s/valid? a-spec db)
(throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {}))))
;; now we create an interceptor using `after`
(def check-spec-interceptor (rf/after (partial check-and-throw ::db)))
;; -- Define Event Handlers --
;; ---------------------------
; Initialize the app state
(rf/reg-event-db
:initialize
[check-spec-interceptor]
(fn [_ _]
{:peoples peoples
:filters [[::role :dev] [::name "julien"]]}))
; Add a new filter in app state
(rf/reg-event-db
:add-filter
[check-spec-interceptor]
(fn [db [_ new-filter]]
(assoc db :filters (conj (:filters db) new-filter))))
; Add all filters for a particular key
(rf/reg-event-db
:add-all-filters-for-key
[check-spec-interceptor]
(fn [db [_ key1 values]]
(assoc db
:filters
(into
(:filters db)
(map
#(vec [key1 %1])
values)))))
; Remove a filter from app state
(rf/reg-event-db
:remove-filter
[check-spec-interceptor]
(fn [db [_ old-filter]]
(assoc db
:filters
(filterv
#(not (= %1 old-filter))
(:filters db)))))
; Remove all filters from app state for a particular key
(rf/reg-event-db
:remove-all-filters-for-key
[check-spec-interceptor]
(fn [db [_ key1]]
(assoc db
:filters
(filterv
#(not (= (first %1) key1))
(:filters db)))))
;; -- 3 - Effect Handler --
;; ------------------------
;; ?? Probably nothing here??
;; -- 4 - Subscription Handler --
;; ------------------------------
; Return the vector of filters
(rf/reg-sub
:filters
(fn [db _]
(:filters db)))
; Return the peoples, unfiltered
(rf/reg-sub
:peoples
(fn [db _]
(:peoples db)))
; Return a list of filtered peoples
(rf/reg-sub
:peoples-filtered
(fn [db _]
(filter-people (:peoples db) (:filters db))))
; Given a key k, return a vector of values representing all the values present in peoples
(rf/reg-sub
:values-for-key
(fn [db [_ k]]
(map #(k %1) (:peoples db))))
; Does filter contains the value k in it?
(rf/reg-sub
:filter-contains?
(fn [db [_ k]]
(contains? (set (:filters db)) k)))
;; -- 5 - View Function --
;; -----------------------
(defn list-people
[]
[:p (pr-str @(rf/subscribe [:peoples]))])
(defn list-people-filtered
[]
[:p (pr-str @(rf/subscribe [:peoples-filtered]))])
(defn list-filter
[]
[:p (pr-str @(rf/subscribe [:filters]))])
(defn button-add-role
[role]
[:button
{:on-click (fn [e] (rf/dispatch [:add-filter [::role role]]))}
(pr-str "Add filter" role)])
(defn button-remove-role
[role]
[:button
{:on-click (fn [e] (rf/dispatch [:remove-filter [::role role]]))}
(pr-str "Remove filter" role)])
(defn ressource
[data]
[:tr
[:td (data ::name)]
[:td {:style (style-by-date
(data ::date-fin-mandat)
end-soon-style
end-not-so-soon-style
end-very-not-soon-style)}(str (data ::date-fin-mandat))]
[:td (data ::role)]
])
(defn checklist-filter
[key1]
(let [list-values (set @(rf/subscribe [:values-for-key key1]))]
(into
[:form
[:input
{:type "checkbox"
:id (str "all-" key1)
:name (str "all-" key1)
:on-change (fn [e] (if (.. e -target -checked)
(rf/dispatch [:add-all-filters-for-key key1 list-values])
(rf/dispatch [:remove-all-filters-for-key key1])))}]
[:label {:for (str "all-" key1)} "All"]
]
(for [value list-values]
[:div
[:input
{:type "checkbox"
:id value
:name value
:on-change (fn [e] (if (.. e -target -checked)
(rf/dispatch [:add-filter [key1 value]])
(rf/dispatch [:remove-filter [key1 value]])))
:checked @(rf/subscribe [:filter-contains? [key1 value]])
}]
[:label {:for value} value]]
))))
(defn peoples-ui-filtered
[]
(let [pf @(rf/subscribe [:peoples-filtered])]
(into
[:table
[:tr
[:th "NAME"]
[:th "DATE FIN MANDAT"]
[:th "ROLE"]]
[:tr
[:td [checklist-filter ::name]]
[:td ]
[:td [checklist-filter ::role]]]
]
(doall
(for [p pf]
[ressource p])))))
(defn ui
[]
[:div
[peoples-ui-filtered]
])
;; -- Kickstart the application --
;; -------------------------------
(defn ^:export run
[]
(rf/dispatch-sync [:initialize]) ;; puts a value into application state
(reagent/render [ui] ;; mount the application's ui into ''
(js/document.getElementById "app")))
(run)
</div>
<div id="app"></div>
<script src="https://storage.googleapis.com/app.klipse.tech/plugin/js/klipse_plugin.js"></script>
</body>
(ns my.reagent-examples
(:require [reagent.core :as reagent]
[cljs.spec.alpha :as s]
[re-frame.db :as db]
[re-frame.core :as rf]))
;; -- Specs --
;; -----------
(s/def ::name string?) ; Name is a string
(s/def ::date-fin-mandat inst?) ; date is of type #inst (a timestamp)
(s/def ::role keyword?) ;role is a keyword
; One people is a map with required keys name, date and role and no optional keys
(s/def ::people (s/keys :req [::name ::date-fin-mandat ::role] ;; ?Should I use :req or :un-req ?
:opt []))
; Peoples is a vector of 0 to many people
(s/def ::peoples (s/coll-of ::people :kind vector? :min-count 0))
; A filter is a 2 elements vector of keyword like [:role :dev]
(s/def ::filter (s/tuple keyword? (s/or :s string? :k keyword?)))
; Filters is a vector of 0 to many filter
(s/def ::filters (s/coll-of ::filter :kind vector? :min-count 0))
; Spec for the whole db
(s/def ::db (s/keys :req-un [::peoples ::filters]))
;; -- Data --
;; --------
(def peoples [
{::name "julien"
::date-fin-mandat (js/Date.)
::role :dev}
{::name "juscellino"
::date-fin-mandat (js/Date. 2019 7 21)
::role :dev}
{::name "danny"
::date-fin-mandat (js/Date. 2019 4 15)
::role :dev}
{::name "nathalie"
::date-fin-mandat (js/Date. 2031 9 22)
::role :rh}
{::name "malik"
::date-fin-mandat (js/Date. 2019 1 22)
::role :analyste}
{::name "daniel"
::date-fin-mandat (js/Date. 2019 8 15)
::role :dev}])
;; -- Helpers --
;; -------------
(defn filter-people
[peoples filters]
(into
[]
(set
(flatten
(mapv
(fn [[k v]]
(filter
#(= (k %1) v)
peoples))
filters)))))
(def end-soon-style {:background-color "red"})
(def end-not-so-soon-style {:background-color "orange"})
(def end-very-not-soon-style {:background-color "green"})
(defn date+30
[date]
(js/Date. (.getFullYear date) (.getMonth date) (+ (.getDate date) 30)))
(defn date+90
[date]
(js/Date. (.getFullYear date) (.getMonth date) (+ (.getDate date) 90)))
(defn style-by-date
[date end-soon end-not-so-soon end-very-not-soon]
(let [today (js/Date.)]
(cond
(>= today date) end-soon
(< date (date+30 today)) end-not-so-soon
:else end-very-not-soon)))
;; -- 1 - Dispatch event --
;; ------------------------
; Do i need this?
(defn dispatch-add-filter-event
[new-filter]
(rf/dispatch [:add-filter [:role :dev]]))
;; -- 2 - Event Handler --
;; -----------------------
;; -- Interceptor --
;; -----------------
; Interceptor for validating spec after every add into db
(defn check-and-throw
"Throws an exception if `db` doesn't match the Spec `a-spec`."
[a-spec db]
(when-not (s/valid? a-spec db)
(throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {}))))
;; now we create an interceptor using `after`
(def check-spec-interceptor (rf/after (partial check-and-throw ::db)))
;; -- Define Event Handlers --
;; ---------------------------
; Initialize the app state
(rf/reg-event-db
:initialize
[check-spec-interceptor]
(fn [_ _]
{:peoples peoples
:filters [[::role :dev] [::name "julien"]]}))
; Add a new filter in app state
(rf/reg-event-db
:add-filter
[check-spec-interceptor]
(fn [db [_ new-filter]]
(assoc db :filters (conj (:filters db) new-filter))))
; Add all filters for a particular key
(rf/reg-event-db
:add-all-filters-for-key
[check-spec-interceptor]
(fn [db [_ key1 values]]
(assoc db
:filters
(into
(:filters db)
(map
#(vec [key1 %1])
values)))))
; Remove a filter from app state
(rf/reg-event-db
:remove-filter
[check-spec-interceptor]
(fn [db [_ old-filter]]
(assoc db
:filters
(filterv
#(not (= %1 old-filter))
(:filters db)))))
; Remove all filters from app state for a particular key
(rf/reg-event-db
:remove-all-filters-for-key
[check-spec-interceptor]
(fn [db [_ key1]]
(assoc db
:filters
(filterv
#(not (= (first %1) key1))
(:filters db)))))
;; -- 3 - Effect Handler --
;; ------------------------
;; ?? Probably nothing here??
;; -- 4 - Subscription Handler --
;; ------------------------------
; Return the vector of filters
(rf/reg-sub
:filters
(fn [db _]
(:filters db)))
; Return the peoples, unfiltered
(rf/reg-sub
:peoples
(fn [db _]
(:peoples db)))
; Return a list of filtered peoples
(rf/reg-sub
:peoples-filtered
(fn [db _]
(filter-people (:peoples db) (:filters db))))
; Given a key k, return a vector of values representing all the values present in peoples
(rf/reg-sub
:values-for-key
(fn [db [_ k]]
(map #(k %1) (:peoples db))))
; Does filter contains the value k in it?
(rf/reg-sub
:filter-contains?
(fn [db [_ k]]
(contains? (set (:filters db)) k)))
;; -- 5 - View Function --
;; -----------------------
(defn list-people
[]
[:p (pr-str @(rf/subscribe [:peoples]))])
(defn list-people-filtered
[]
[:p (pr-str @(rf/subscribe [:peoples-filtered]))])
(defn list-filter
[]
[:p (pr-str @(rf/subscribe [:filters]))])
(defn button-add-role
[role]
[:button
{:on-click (fn [e] (rf/dispatch [:add-filter [::role role]]))}
(pr-str "Add filter" role)])
(defn button-remove-role
[role]
[:button
{:on-click (fn [e] (rf/dispatch [:remove-filter [::role role]]))}
(pr-str "Remove filter" role)])
(defn ressource
[data]
[:tr
[:td (data ::name)]
[:td {:style (style-by-date
(data ::date-fin-mandat)
end-soon-style
end-not-so-soon-style
end-very-not-soon-style)}(str (data ::date-fin-mandat))]
[:td (data ::role)]
])
(defn checklist-filter
[key1]
(let [list-values (set @(rf/subscribe [:values-for-key key1]))]
(into
[:form
[:input
{:type "checkbox"
:id (str "all-" key1)
:name (str "all-" key1)
:on-change (fn [e] (if (.. e -target -checked)
(rf/dispatch [:add-all-filters-for-key key1 list-values])
(rf/dispatch [:remove-all-filters-for-key key1])))}]
[:label {:for (str "all-" key1)} "All"]
]
(for [value list-values]
[:div
[:input
{:type "checkbox"
:id value
:name value
:on-change (fn [e] (if (.. e -target -checked)
(rf/dispatch [:add-filter [key1 value]])
(rf/dispatch [:remove-filter [key1 value]])))
:checked @(rf/subscribe [:filter-contains? [key1 value]])
}]
[:label {:for value} value]]
))))
(defn peoples-ui-filtered
[]
(let [pf @(rf/subscribe [:peoples-filtered])]
(into
[:table
[:tr
[:th "NAME"]
[:th "DATE FIN MANDAT"]
[:th "ROLE"]]
[:tr
[:td [checklist-filter ::name]]
[:td ]
[:td [checklist-filter ::role]]]
]
(doall
(for [p pf]
[ressource p])))))
(defn ui
[]
[:div
[peoples-ui-filtered]
])
;; -- Kickstart the application --
;; -------------------------------
(defn ^:export run
[]
(rf/dispatch-sync [:initialize]) ;; puts a value into application state
(reagent/render [ui] ;; mount the application's ui into ''
(js/document.getElementById "app")))
(run)
@JulienRouse
Copy link
Author

table.cljs is a little project with the aim to have visual clue on when colleague will end their missions. The table allow filtering either by 'role' or by 'name'.

klipse.html is the same project, you can paste this code into a file and then open it with a browser to play with live, thanks to Klipse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment