Add a few re-com utilities
This commit is contained in:
parent
8727b556f8
commit
1c456acaae
|
@ -0,0 +1,33 @@
|
||||||
|
(ns day8.re-frame.trace.utils.re-com)
|
||||||
|
|
||||||
|
;; There is a trap when writing DOM event handlers. This looks innocent enough:
|
||||||
|
;;
|
||||||
|
;; :on-mouse-out #(reset! my-over-atom false)
|
||||||
|
;;
|
||||||
|
;; But notice that it inadvertently returns false. returning false means something!!
|
||||||
|
;; v0.11 of ReactJS will invoke both stopPropagation() and preventDefault()
|
||||||
|
;; on the event. Almost certainly not what we want.
|
||||||
|
;;
|
||||||
|
;; Note: v0.12 of ReactJS will do the same as v0.11, except it also issues a
|
||||||
|
;; deprecation warning about false returns.
|
||||||
|
;;
|
||||||
|
;; Note: ReactJS only tests explicitly for false, not falsy values. So 'nil' is a
|
||||||
|
;; safe return value.
|
||||||
|
;;
|
||||||
|
;; So 'handler-fn' is a macro which will stop you from inadvertently returning
|
||||||
|
;; false in a handler.
|
||||||
|
;;
|
||||||
|
;;
|
||||||
|
;; Examples:
|
||||||
|
;;
|
||||||
|
;; :on-mouse-out (handler-fn (reset! my-over-atom false)) ;; notice no # in front reset! form
|
||||||
|
;;
|
||||||
|
;;
|
||||||
|
;; :on-mouse-out (handler-fn
|
||||||
|
;; (reset! over-atom false) ;; notice: no need for a 'do'
|
||||||
|
;; (now do something else)
|
||||||
|
;; (.preventDefault event)) ;; notice access to the 'event'
|
||||||
|
|
||||||
|
(defmacro handler-fn
|
||||||
|
([& body]
|
||||||
|
`(fn [~'event] ~@body nil))) ;; force return nil
|
|
@ -0,0 +1,222 @@
|
||||||
|
(ns day8.re-frame.trace.utils.re-com
|
||||||
|
"Shameless pilfered from re-com."
|
||||||
|
(:require-macros [day8.re-frame.trace.utils.re-com :refer [handler-fn]])
|
||||||
|
(:require [reagent.ratom :as reagent :refer [RAtom Reaction RCursor Track Wrapper]]
|
||||||
|
[clojure.string :as string]))
|
||||||
|
|
||||||
|
(defn deref-or-value
|
||||||
|
"Takes a value or an atom
|
||||||
|
If it's a value, returns it
|
||||||
|
If it's a Reagent object that supports IDeref, returns the value inside it by derefing
|
||||||
|
"
|
||||||
|
[val-or-atom]
|
||||||
|
(if (satisfies? IDeref val-or-atom)
|
||||||
|
@val-or-atom
|
||||||
|
val-or-atom))
|
||||||
|
|
||||||
|
(defn flex-flow-style
|
||||||
|
"A cross-browser helper function to output flex-flow with all it's potential browser prefixes"
|
||||||
|
[flex-flow]
|
||||||
|
{:-webkit-flex-flow flex-flow
|
||||||
|
:flex-flow flex-flow})
|
||||||
|
|
||||||
|
(defn flex-child-style
|
||||||
|
"Determines the value for the 'flex' attribute (which has grow, shrink and basis), based on the :size parameter.
|
||||||
|
IMPORTANT: The term 'size' means width of the item in the case of flex-direction 'row' OR height of the item in the case of flex-direction 'column'.
|
||||||
|
Flex property explanation:
|
||||||
|
- grow Integer ratio (used with other siblings) to determined how a flex item grows it's size if there is extra space to distribute. 0 for no growing.
|
||||||
|
- shrink Integer ratio (used with other siblings) to determined how a flex item shrinks it's size if space needs to be removed. 0 for no shrinking.
|
||||||
|
- basis Initial size (width, actually) of item before any growing or shrinking. Can be any size value, e.g. 60%, 100px, auto
|
||||||
|
Note: auto will cause the initial size to be calculated to take up as much space as possible, in conjunction with it's siblings :flex settings.
|
||||||
|
Supported values:
|
||||||
|
- initial '0 1 auto' - Use item's width/height for dimensions (or content dimensions if w/h not specifed). Never grow. Shrink (to min-size) if necessary.
|
||||||
|
Good for creating boxes with fixed maximum size, but that can shrink to a fixed smaller size (min-width/height) if space becomes tight.
|
||||||
|
NOTE: When using initial, you should also set a width/height value (depending on flex-direction) to specify it's default size
|
||||||
|
and an optional min-width/height value to specify the size it can shrink to.
|
||||||
|
- auto '1 1 auto' - Use item's width/height for dimensions. Grow if necessary. Shrink (to min-size) if necessary.
|
||||||
|
Good for creating really flexible boxes that will gobble as much available space as they are allowed or shrink as much as they are forced to.
|
||||||
|
- none '0 0 auto' - Use item's width/height for dimensions (or content dimensions if not specifed). Never grow. Never shrink.
|
||||||
|
Good for creating rigid boxes that stick to their width/height if specified, otherwise their content size.
|
||||||
|
- 100px '0 0 100px' - Non flexible 100px size (in the flex direction) box.
|
||||||
|
Good for fixed headers/footers and side bars of an exact size.
|
||||||
|
- 60% '60 1 0px' - Set the item's size (it's width/height depending on flex-direction) to be 60% of the parent container's width/height.
|
||||||
|
NOTE: If you use this, then all siblings with percentage values must add up to 100%.
|
||||||
|
- 60 '60 1 0px' - Same as percentage above.
|
||||||
|
- grow shrink basis 'grow shrink basis' - If none of the above common valaues above meet your needs, this gives you precise control.
|
||||||
|
If number of words is not 1 or 3, an exception is thrown.
|
||||||
|
Reference: http://www.w3.org/TR/css3-flexbox/#flexibility
|
||||||
|
Diagram: http://www.w3.org/TR/css3-flexbox/#flex-container
|
||||||
|
Regex101 testing: ^(initial|auto|none)|(\\d+)(px|%|em)|(\\d+)\\w(\\d+)\\w(.*) - remove double backslashes"
|
||||||
|
[size]
|
||||||
|
;; TODO: Could make initial/auto/none into keywords???
|
||||||
|
(let [split-size (string/split (string/trim size) #"\s+") ;; Split into words separated by whitespace
|
||||||
|
split-count (count split-size)
|
||||||
|
_ (assert (contains? #{1 3} split-count) "Must pass either 1 or 3 words to flex-child-style")
|
||||||
|
size-only (when (= split-count 1) (first split-size)) ;; Contains value when only one word passed (e.g. auto, 60px)
|
||||||
|
split-size-only (when size-only (string/split size-only #"(\d+)(.*)")) ;; Split into number + string
|
||||||
|
[_ num units] (when size-only split-size-only) ;; grab number and units
|
||||||
|
pass-through? (nil? num) ;; If we can't split, then we'll pass this straign through
|
||||||
|
grow-ratio? (or (= units "%") (= units "") (nil? units)) ;; Determine case for using grow ratio
|
||||||
|
grow (if grow-ratio? num "0") ;; Set grow based on percent or integer, otherwise no grow
|
||||||
|
shrink (if grow-ratio? "1" "0") ;; If grow set, then set shrink to even shrinkage as well
|
||||||
|
basis (if grow-ratio? "0px" size) ;; If grow set, then even growing, otherwise set basis size to the passed in size (e.g. 100px, 5em)
|
||||||
|
flex (if (and size-only (not pass-through?))
|
||||||
|
(str grow " " shrink " " basis)
|
||||||
|
size)]
|
||||||
|
{:-webkit-flex flex
|
||||||
|
:flex flex}))
|
||||||
|
|
||||||
|
|
||||||
|
(defn justify-style
|
||||||
|
"Determines the value for the flex 'justify-content' attribute.
|
||||||
|
This parameter determines how children are aligned along the main axis.
|
||||||
|
The justify parameter is a keyword.
|
||||||
|
Reference: http://www.w3.org/TR/css3-flexbox/#justify-content-property"
|
||||||
|
[justify]
|
||||||
|
(let [js (case justify
|
||||||
|
:start "flex-start"
|
||||||
|
:end "flex-end"
|
||||||
|
:center "center"
|
||||||
|
:between "space-between"
|
||||||
|
:around "space-around")]
|
||||||
|
{:-webkit-justify-content js
|
||||||
|
:justify-content js}))
|
||||||
|
|
||||||
|
|
||||||
|
(defn align-style
|
||||||
|
"Determines the value for the flex align type attributes.
|
||||||
|
This parameter determines how children are aligned on the cross axis.
|
||||||
|
The justify parameter is a keyword.
|
||||||
|
Reference: http://www.w3.org/TR/css3-flexbox/#align-items-property"
|
||||||
|
[attribute align]
|
||||||
|
(let [attribute-wk (->> attribute name (str "-webkit-") keyword)
|
||||||
|
as (case align
|
||||||
|
:start "flex-start"
|
||||||
|
:end "flex-end"
|
||||||
|
:center "center"
|
||||||
|
:baseline "baseline"
|
||||||
|
:stretch "stretch")]
|
||||||
|
{attribute-wk as
|
||||||
|
attribute as}))
|
||||||
|
|
||||||
|
(defn gap-f
|
||||||
|
"Returns a component which produces a gap between children in a v-box/h-box along the main axis"
|
||||||
|
[& {:keys [size width height class style attr]
|
||||||
|
:as args}]
|
||||||
|
(let [s (merge
|
||||||
|
(when size (flex-child-style size))
|
||||||
|
(when width {:width width})
|
||||||
|
(when height {:height height})
|
||||||
|
style)]
|
||||||
|
[:div
|
||||||
|
(merge
|
||||||
|
{:class (str "rc-gap " class) :style s}
|
||||||
|
attr)]))
|
||||||
|
|
||||||
|
(defn h-box
|
||||||
|
"Returns hiccup which produces a horizontal box.
|
||||||
|
It's primary role is to act as a container for components and lays it's children from left to right.
|
||||||
|
By default, it also acts as a child under it's parent"
|
||||||
|
[& {:keys [size width height min-width min-height max-width max-height justify align align-self margin padding gap children class style attr]
|
||||||
|
:or {size "none" justify :start align :stretch}
|
||||||
|
:as args}]
|
||||||
|
(let [s (merge
|
||||||
|
(flex-flow-style "row nowrap")
|
||||||
|
(flex-child-style size)
|
||||||
|
(when width {:width width})
|
||||||
|
(when height {:height height})
|
||||||
|
(when min-width {:min-width min-width})
|
||||||
|
(when min-height {:min-height min-height})
|
||||||
|
(when max-width {:max-width max-width})
|
||||||
|
(when max-height {:max-height max-height})
|
||||||
|
(justify-style justify)
|
||||||
|
(align-style :align-items align)
|
||||||
|
(when align-self (align-style :align-self align-self))
|
||||||
|
(when margin {:margin margin}) ;; margin and padding: "all" OR "top&bottom right&left" OR "top right bottom left"
|
||||||
|
(when padding {:padding padding})
|
||||||
|
style)
|
||||||
|
gap-form (when gap [gap-f
|
||||||
|
:size gap
|
||||||
|
:width gap]) ;; TODO: required to get around a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=423112. Remove once fixed.
|
||||||
|
children (if gap
|
||||||
|
(interpose gap-form (filter identity children)) ;; filter is to remove possible nils so we don't add unwanted gaps
|
||||||
|
children)]
|
||||||
|
(into [:div
|
||||||
|
(merge
|
||||||
|
{:class (str "rc-h-box display-flex " class) :style s}
|
||||||
|
attr)]
|
||||||
|
children)))
|
||||||
|
|
||||||
|
(defn- input-text-base
|
||||||
|
"Returns markup for a basic text input label"
|
||||||
|
[& {:keys [model input-type] :as args}]
|
||||||
|
(let [external-model (reagent/atom (deref-or-value model)) ;; Holds the last known external value of model, to detect external model changes
|
||||||
|
internal-model (reagent/atom (if (nil? @external-model) "" @external-model))] ;; Create a new atom from the model to be used internally (avoid nil)
|
||||||
|
(fn
|
||||||
|
[& {:keys [model on-change status status-icon? status-tooltip placeholder width height rows change-on-blur? validation-regex disabled? class style attr]
|
||||||
|
:or {change-on-blur? true}
|
||||||
|
:as args}]
|
||||||
|
(let [latest-ext-model (deref-or-value model)
|
||||||
|
disabled? (deref-or-value disabled?)
|
||||||
|
change-on-blur? (deref-or-value change-on-blur?)
|
||||||
|
showing? (reagent/atom false)]
|
||||||
|
(when (not= @external-model latest-ext-model) ;; Has model changed externally?
|
||||||
|
(reset! external-model latest-ext-model)
|
||||||
|
(reset! internal-model latest-ext-model))
|
||||||
|
[h-box
|
||||||
|
:class "rc-input-text "
|
||||||
|
:align :start
|
||||||
|
:width (if width width "250px")
|
||||||
|
:children [[:div
|
||||||
|
{:class (str "rc-input-text-inner " ;; form-group
|
||||||
|
(case status
|
||||||
|
:success "has-success "
|
||||||
|
:warning "has-warning "
|
||||||
|
:error "has-error "
|
||||||
|
"")
|
||||||
|
(when (and status status-icon?) "has-feedback"))
|
||||||
|
:style (flex-child-style "auto")}
|
||||||
|
[(if (= input-type :password) :input input-type)
|
||||||
|
(merge
|
||||||
|
{:class (str "form-control " class)
|
||||||
|
:type (case input-type
|
||||||
|
:input "text"
|
||||||
|
:password "password"
|
||||||
|
nil)
|
||||||
|
:rows (when (= input-type :textarea) (or rows 3))
|
||||||
|
:style (merge
|
||||||
|
(flex-child-style "none")
|
||||||
|
{:height height
|
||||||
|
:padding-right "12px"} ;; override for when icon exists
|
||||||
|
style)
|
||||||
|
:placeholder placeholder
|
||||||
|
:value @internal-model
|
||||||
|
:disabled disabled?
|
||||||
|
:on-change (handler-fn
|
||||||
|
(let [new-val (-> event .-target .-value)]
|
||||||
|
(when (and
|
||||||
|
on-change
|
||||||
|
(not disabled?)
|
||||||
|
(if validation-regex (re-find validation-regex new-val) true))
|
||||||
|
(reset! internal-model new-val)
|
||||||
|
(when-not change-on-blur?
|
||||||
|
(on-change @internal-model)))))
|
||||||
|
:on-blur (handler-fn
|
||||||
|
(when (and
|
||||||
|
on-change
|
||||||
|
change-on-blur?
|
||||||
|
(not= @internal-model @external-model))
|
||||||
|
(on-change @internal-model)))
|
||||||
|
:on-key-up (handler-fn
|
||||||
|
(if disabled?
|
||||||
|
(.preventDefault event)
|
||||||
|
(case (.-which event)
|
||||||
|
13 (when on-change (on-change @internal-model))
|
||||||
|
27 (reset! internal-model @external-model)
|
||||||
|
true)))}
|
||||||
|
attr)]]]]))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn input-text
|
||||||
|
[& args]
|
||||||
|
(apply input-text-base :input-type :input args))
|
Loading…
Reference in New Issue