Skip to content

Instantly share code, notes, and snippets.

@onetom
Forked from micha/paging.clj
Created September 12, 2016 15:27
Show Gist options
  • Save onetom/26b55b0f01e592658c1f0ff876af2880 to your computer and use it in GitHub Desktop.
Save onetom/26b55b0f01e592658c1f0ff876af2880 to your computer and use it in GitHub Desktop.
(ns ui.paging
(:require [hoplon.core :refer (def-values)]))
(defmacro defp
[name & args]
`(def ~name (paginate ~@args)))
(ns ui.paging
(:require [ui.util :as util]
[clojure.walk :as walk]))
(defn deref-vals [x]
(walk/postwalk #(try @% (catch js/Error e %)) x))
(defn window
[n current-page pages]
(if (> n pages) ; the window is bigger than the total number of pages; return everything
(range 1 (inc pages))
(let [start (- current-page (.ceil js/Math (/ n 2)))
end (+ start n)
r (range start end)]
(cond
; shift right until it starts at 1
(<= start 0) (range 1 (inc n))
; shift left until end = pages
(> end pages) (range (inc (- pages n)) (inc pages))
:else r))))
(defn refresh!
[{:keys [params]}]
(swap! params update :refresh inc))
(defn clear!
[{:keys [data] :as this}]
(reset! data nil)
this)
(defn activate!
[{:keys [params] :as this}]
(swap! params assoc :active true))
(defn deactivate!
[{:keys [params] :as this}]
(clear! this)
(swap! params assoc :active false))
(defn params->page-opts
[{:keys [page-size current-page] :as params}]
(merge (select-keys params [:filters :sorting])
{:paging {:size page-size
:from (* (dec current-page) page-size)}}))
(defn page-opts
[{:keys [params]}]
(params->page-opts @params))
(defn fetch!
[remote data fetch-state params]
(reset! fetch-state :fetching)
(doto (remote (params->page-opts params))
(.done #(do (reset! data %)
(reset! fetch-state :idle)))))
(defn set-page-size!
[{:keys [current-page page-size] :as this} new-size]
(reset! current-page 1)
(reset! page-size new-size)
this)
(defn next!
[{:keys [current-page] :as this}]
(swap! current-page inc)
this)
(defn prev!
[{:keys [current-page] :as this}]
(swap! current-page dec)
this)
(defn set-page!
[{:keys [current-page] :as this} n]
(reset! current-page n)
this)
(defn filter-equals!
[{:keys [filters] :as this} & kv-pairs]
(->> kv-pairs
(partition-all 2)
(reduce (fn [r [k v]] (assoc r k {:type := :val v}))
{})
(swap! filters merge))
this)
(defn filter-equals?
[{:keys [filters]} k v]
(= (k @filters)
{:type := :val v}))
(defn set-filters!
[{:keys [filters] :as this} filter-spec]
(reset! filters filter-spec)
this)
(defn set-sorting!
[{:keys [sorting] :as this} sorting-spec]
(reset! sorting sorting-spec)
this)
(defn sort!
[{:keys [sorting] :as this} column]
(let [{:keys [by order]} @sorting]
(reset! sorting
{:by column
:order (if (not= by column)
:asc
(get {:asc :desc :desc :asc} order))})
this))
(defn watch-map
"When cell `c` changes, update the filter of pager `this` according to `mapping`.
Example: (watch-map rpc/creatives {:flightid :id} flight)
When flight changes, rpc/creatives will look something like
{:filters {:flightid {:type := :val 123}}}
where `123` is (:id flight)"
[this mapping c]
(let [source-keys (vals mapping)
watch-map (cell= (and c (select-keys c source-keys)))]
(cell= (when watch-map
(clear! this)
(apply filter-equals!
this
(mapcat (fn [[filter-attr cell-attr]]
[filter-attr (get watch-map cell-attr)])
mapping))
(activate! this)))))
(defn watch-val
[this k c]
(util/on-change c (fn [v] (when (and v (not (filter-equals? this k v)))
(clear! this)
(filter-equals! this k v)))))
(defn paginate
[remote & {:keys [filters sorting page-size]}]
(let [fetch-state (cell :idle)
params (cell {:page-size (or page-size 25)
:current-page 1
:filters (or filters {})
:sorting (or sorting {:by :id :order :desc})
:refresh 0
:active false})
current-page (util/path-cell params [:current-page])
page-size (util/path-cell params [:page-size])
things (cell nil)
rows (cell= (map :__row__ things))
total-rows (cell= (:__rows__ (first things)))
pages (cell= (.ceil js/Math (/ total-rows page-size)))
page-window (cell= (window 10 current-page pages))
at-beginning (cell= (or (zero? pages) (= current-page (first page-window))))
at-end (cell= (or (zero? pages) (= current-page (last page-window))))]
(util/on-change params
(fn [{:keys [active] :as new-params}]
(when active (fetch! remote things fetch-state new-params))))
{:data things
:remote remote
:fetching (cell= (= :fetching fetch-state))
:params params
:filters (util/path-cell params [:filters])
:sorting (util/path-cell params [:sorting])
:current-page current-page
:page-size page-size
:rows rows
:total-rows total-rows
:pages pages
:page-window page-window
:at-beginning at-beginning
:at-end at-end}))
(defn paginate-with-localstorage
[remote & {:keys [storage params]}]
(let [fetch-state (cell :idle)
params (cell (merge params (select-keys @storage [:page-size])))
current-page (util/path-cell params [:current-page])
page-size (util/path-cell params [:page-size])
things (cell nil)
rows (cell= (map :__row__ things))
total-rows (cell= (:__rows__ (first things)))
pages (cell= (.ceil js/Math (/ total-rows page-size)))
page-window (cell= (window 10 current-page pages))
at-beginning (cell= (or (zero? pages) (= current-page (first page-window))))
at-end (cell= (or (zero? pages) (= current-page (last page-window))))]
(util/on-change params
(fn [{:keys [active] :as new-params}]
(when active (fetch! remote things fetch-state new-params))
(reset! storage (select-keys new-params [:page-size]))))
{:data things
:remote remote
:fetching (cell= (= :fetching fetch-state))
:params params
:filters (util/path-cell params [:filters])
:sorting (util/path-cell params [:sorting])
:current-page current-page
:page-size page-size
:rows rows
:total-rows total-rows
:pages pages
:page-window page-window
:at-beginning at-beginning
:at-end at-end}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment