Skip to content

Instantly share code, notes, and snippets.

@sritchie
Created July 17, 2014 22:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sritchie/9014c14c15d72c8b6458 to your computer and use it in GitHub Desktop.
Save sritchie/9014c14c15d72c8b6458 to your computer and use it in GitHub Desktop.
(ns paddleguru.client.render.input
(:require [clojure.string :as string]
[om.core :as om]
[om-tools.dom :as d :include-macros true]
[schema.core :as s])
(:require-macros [schema.macros :as sm]))
;; ## Bootstrap Inputs for Om
;;
;; The following fields come from converting:
;;
;; https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Input.jsx
;; React Bootstrap has some good stuff to learn from on how to make
;; inputs.
;;
;; https://github.com/react-bootstrap/react-bootstrap
;;
;; ### Schemas
(def Component
(s/named s/Any "Alias for an om component, since I don't know what type to put here."))
(def Addons
{(s/optional-key :addon-before) (s/either s/Str Component)
(s/optional-key :addon-after) (s/either s/Str Component)})
(def FeedbackIcons
"Helps render feedback icons."
{(s/optional-key :bs-style) (s/enum "success" "warning" "error")
(s/optional-key :has-feedback) s/Bool})
(def Input
"Input fields that match these bad dawgs:
https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Input.jsx"
(merge Addons
FeedbackIcons
{:type s/Str
(s/optional-key :attrs) (-> {s/Keyword s/Any}
(s/named "Custom attributes for the dom element."))
(s/optional-key :children) s/Any
(s/optional-key :label) s/Str
(s/optional-key :help) s/Str
(s/optional-key :group-classname) s/Str
(s/optional-key :wrapper-classname) s/Str
(s/optional-key :label-classname) s/Str}))
;; ### Utilities
(sm/defn class-set :- s/Str
"Mimics the class-set behavior from React. Pass in a map of
potential class to Boolean; you'll get back a class string that
represents the final class to apply.
TODO: Use class-set from om-tools."
[klasses :- {(s/either s/Str s/Keyword) s/Bool}]
(->> (mapcat (fn [[k keep?]]
(when keep? [(name k)]))
klasses)
(string/join " ")))
(sm/defn glyph :- Component
"To be used with :addon-before or :addon-after."
[glyph-name :- s/Str]
(d/span {:class (str "glyphicon glyphicon-" glyph-name)}))
(sm/defn render-icon :- Component
[{:keys [has-feedback bs-style]} :- FeedbackIcons]
(when has-feedback
(let [klasses {:glyphicon true
:form-control-feedback true
:glyphicon-ok (= "success" bs-style)
:glyphicon-warning-sign (= "warning" bs-style)
:glyphicon-remove (= "error" bs-style)}]
(d/span {:class (class-set klasses)}))))
(sm/defn render-help
[help :- (s/maybe s/Str)]
(when help
(d/span {:class "help-block"} help)))
(sm/defn render-input-group
"Items is a vector of render instances."
[{:keys [addon-before addon-after]} :- Addons
items :- s/Any]
(if (or addon-before addon-after)
(d/div {:class "input-group"}
(when addon-before
(d/span {:class "input-group-addon"} addon-before))
items
(when addon-after
(d/span {:class "input-group-addon"} addon-after)))
items))
(sm/defn checkbox-or-radio? :- s/Bool
"Returns true if the supplied input is of type checkbox or radio,
false otherwise."
[{type :type} :- Input]
(or (= type "checkbox")
(= type "radio")))
(sm/defn checkbox-or-radio-wrapper :- Component
"Wraps this business in a div."
[{type :type} :- Input
children]
(let [klasses {:checkbox (= "checkbox" type)
:radio (= "radio" type)}]
(d/div {:class (class-set klasses)}
children)))
(sm/defn render-label
"This doesn't handle any control group stuff."
([input :- Input] (render-label input nil))
([{lc :label-classname label :label :as input} :- Input
child]
(let [classes (merge {:control-label (not (checkbox-or-radio? input))}
(when lc {lc (boolean lc)}))]
(if label
(d/label {:class (class-set classes)}
child
label)
child))))
(sm/defn render-wrapper
[{wc :wrapper-classname} :- Input
child]
(if wc
(d/div {:class wc} child)
child))
(sm/defn render-form-group :- Component
"Wraps the entire form group."
[{bs-style :bs-style cn :group-classname :as input} :- Input
children]
(let [classes (merge {:form-group true
:has-feedback (boolean (:has-feedback input))
:has-success (= "success" bs-style)
:has-warning (= "warning" bs-style)
:has-error (= "error" bs-style)}
(when cn {cn (boolean cn)}))]
(d/div {:class (class-set classes)}
children)))
(sm/defn render-input [input :- Input]
(case (:type input)
"select" (d/select {:class "form-control" :ref "input"}
(:children input))
"textarea" (d/textarea {:class "form-control"} :ref "input")
"static" (d/p {:class "form-control-static"
:ref "input"}
(:children input))
(d/input
(merge (:attrs input)
{:ref "input"
:class (if (checkbox-or-radio? input)
""
"form-control")
:type (:type input)})
(:children input))))
;; ### API Methods
(sm/defn input :- Component
"Returns an input component. This currently does NOT handle any of
the default values or validation messages that we'll need to make
this work, though."
[input :- Input]
(if (checkbox-or-radio? input)
(->> [(->> (render-input input)
(render-label input)
(checkbox-or-radio-wrapper input))
(render-help (:help input))]
(render-wrapper input)
(render-form-group input))
(->> [(render-label input)
(->> [(render-input-group (select-keys input [:addon-before :addon-after])
(render-input input))
(render-icon (select-keys input [:has-feedback :bs-style]))
(render-help (:help input))]
(render-wrapper input))]
(render-form-group input))))
;; TODO: Have a better story for class-set - maybe allow a set too?
;; ## Helpers
(defn make-horizontal
"Adds suitable container boundaries for a horizontal form
field. TODO: Give this thing custom arguments."
[item]
(assoc item
:label-classname "col-xs-2"
:wrapper-classname "col-xs-10"))
;; TODO: Add an inline-fields helper that can build a group of these.
;;
;; Some help on how to do this with bootstrap 3: http://www.bootply.com/127826
(sm/defn inline-fields
[inputs :- [Input]]
)
(def example-fields
[{:type "text" :addon-before "$"
:help "Label before the input field."}
{:type "text" :label "Your Address:"
:label-classname "col-sm-3"
:wrapper-classname "col-sm-5"}
{:type "text" :addon-after ".00"
:label "label!"
:help "Label AFTER the input field."}
{:type "text" :addon-before "$" :addon-after ".00"
:help "Label both before and after the input field."}
{:type "text" :bs-style "success" :label "Success" :has-feedback true}
{:type "text" :bs-style "warning" :label "Warning" :has-feedback true}
{:type "text" :bs-style "error" :label "Error" :has-feedback true}
(make-horizontal
{:type "select"
:label "Pick Events"
:children (map (fn [i]
(d/option {:value i} i))
(range 10))
:selected 3})])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment