暫無描述

clarity.cljs 5.5KB

    (ns clarity (:require [cljs.reader :as r] [om.core :as om :include-macros true] [om.dom :as dom :include-macros true])) (enable-console-print!) ; data for one node: type and optionally data ; if data is present, fill the node with the data ; on input, check if input conforms to data, if it ; does change the data, if it doesn't signal the ; error and don't change the data (defn valid? [el] (.. el -validity -valid)) (defn read-keyword [str] (let [name (.substr str 1)] (if (seq name) (keyword name) nil))) (defmulti empty-value (fn get-type [type] (cond (sequential? type) (first type) (map? type) (get-type (:type type)) :else type))) (defmethod empty-value 'Boolean [{:keys [default]}] (if-not (nil? default) default false)) (defmethod empty-value 'Number [{:keys [default]}] (or default 0)) (defmethod empty-value 'Keyword [{:keys [default]}] (or default :keyword)) (defmethod empty-value 'String [{:keys [default]}] (or default "")) (defmethod empty-value 'Value [[_ v]] v) (defmethod empty-value 'Option [[_ v]] (empty-value v)) (defmethod empty-value 'U [[_ & [[_ v]]]] v) (defmethod empty-value 'HMap [spec] (let [entries (nth spec 2)] (into {} (map (fn [[k v]] [k (empty-value v)]) entries)))) (defn update-on-change! [m k {:keys [extract-fn transform-fn optional? type] :or {extract-fn #(.-value %) transform-fn identity type 'String}}] (fn [ev] (let [new-val (transform-fn (extract-fn (.-target ev)))] (if (and optional? (= new-val (empty-value type))) (om/update! m dissoc k) (om/transact! m k (fn [_ n] n) new-val))))) (defmulti make-typed-input (fn [_ _ {type :type} & _] (cond (sequential? type) (first type) (map? type) (:type type) :else type))) (defmethod make-typed-input 'Boolean [m owner {:keys [type key val] :as opts}] (om/component (dom/input #js {:type "checkbox" :checked (or val (empty-value type)) :onChange (update-on-change! m key (assoc opts :extract-fn #(.-checked %)))}))) (defmethod make-typed-input 'Number [m owner {:keys [key val] :as opts}] (om/component (dom/input #js {:type "number" :value val :onChange (update-on-change! m key (assoc opts :transform-fn js/parseFloat))}))) (def keyword-pattern "^:(\\w+|\\w+(\\.\\w+)*\\/\\w+)$") (defmethod make-typed-input 'Keyword [m owner {:keys [type key val] :as opts}] (om/component (dom/input #js {:type "text" :value val :pattern keyword-pattern :onChange (update-on-change! m key (assoc opts :transform-fn #(or (read-keyword %) (empty-value type))))}))) (defmethod make-typed-input 'String [m owner {:keys [type key val] :as opts}] (om/component (dom/input #js {:type "text" :value val :onChange (update-on-change! m key opts)}))) (defmethod make-typed-input 'Value [m owner {type :type}] (let [[_ value] type] (om/component (dom/input (clj->js (into {:value (str value) :readOnly "readOnly"} (cond (or (true? value) (false? value)) {:type "checkbox", :checked value} (number? value) {:type "number"} (keyword? value) {:type "text", :pattern keyword-pattern} :else {:type "text"}))))))) (defmethod make-typed-input 'Option [m owner {type :type :as opts}] (let [[_ type] type] (make-typed-input m owner (assoc opts :type type)))) (defmethod make-typed-input 'U [m owner {:keys [type key val] :as opts}] (om/component (dom/select #js {:onChange (update-on-change! m key (assoc opts :transform-fn r/read-string))} (into-array (map (fn [[_ v]] (dom/option nil (str v))) (rest type)))))) (defn make-typed-field [m optional?] (fn [[k t]] (dom/div #js {:className "field"} (dom/label nil (str k (when optional? " (optional)"))) (om/build make-typed-input m {:opts {:type (om/value t), :key k, :val (k m) :optional? optional?}})))) (defmethod make-typed-input 'HMap [m owner {type :type}] (let [hmap (apply hash-map (rest type)) required (:mandatory hmap) optional (:optional hmap)] (om/component (dom/div nil (dom/span nil "{") (into-array (map (make-typed-field m false) required)) (into-array (map (make-typed-field m true) optional)) (dom/span nil "}"))))) (def app-state (let [type '[HMap :mandatory {:name {:type String :default "Paul"}, :age {:type Number, :default 10}, :language (U (Value :en) (Value :de) (Value :fr) (Value :jp)) :fun Boolean :gender Keyword} :optional {:secret-skill String}]] (atom {:type type :data (empty-value type)}))) (defn typed-input [{:keys [type data]} owner] (reify om/IWillUpdate (will-update [_ p s] (prn (:data p) s)) om/IRender (render [_] (om/build make-typed-input data {:opts {:type type}})))) (om/root app-state typed-input (.getElementById js/document "typed_input"))