Skip to content

Instantly share code, notes, and snippets.

@danielneal
Last active August 29, 2015 14:01
Show Gist options
  • Save danielneal/5f140fc0899e7551c54a to your computer and use it in GitHub Desktop.
Save danielneal/5f140fc0899e7551c54a to your computer and use it in GitHub Desktop.
Hoplon - How to handle collections of cells?
(page "index.html")
;; ---------------
;; View cell
;;
;; A view cell is a type of formula cell that only uses lookup operations.
;; This allows it to stay updatable (unlike other formula cells)
;; It is defined by calling `view-cell` with the root and a path.
;; The path is a vector of keys or indexes, as used in assoc-in, or update-in
;;
;; Creating a view-cell which points to another view-cell will create
;; a view cell which shares the same root but combines the path.
;;
;; The purpose of the view cell is to allow components to be decoupled
;; from the implementation of the state stem-cell.
;;
;; A component can simply take a cell to represent its state.
;; The state which might be a straightforward cell, or it could be a view
;; onto a much larger nested data structure.
;; ---------------
(defprotocol IViewCell
(__root [this])
(__path [this]))
(defn view-cell
"Create a view cell from a root cell and a path"
[cell path]
(let [view-cell? (satisfies? IViewCell cell)
path (if view-cell?
(into [] (concat (__path cell) path))
path)
root (if view-cell?
(__root cell)
cell)
view (cell= (get-in root path))]
(specify! view
IViewCell
(__root [this] root)
(__path [this] path)
cljs.core/IReset
(-reset! [this v] (swap! root #(assoc-in % path v)))
cljs.core/ISwap
(-swap! [this f] (swap! root #(update-in % path f))))))
(defn map-view-cell
"Experimental: Map over a cell, returning a formula cell of updatable view-cells.
Would be nicer to act more like loop-tpl to provide smoother update"
[f cell-coll]
(let [rng (cell= (range 0 (count cell-coll)))
index->view-cell #(f (view-cell cell-coll [%]))]
(cell= (map index->view-cell rng))))
;; ---------------
;; Model
;; ---------------
(defc questions [{:question "Do you want to go for a walk?"
:answer nil}
{:question "Do you like sushi?"
:answer nil}])
(defc= answered (->> questions
(map :answer)
(remove nil?)
(count)))
;; ---------------
;; View elements
;; ---------------
(defelem question [{:keys [title state]}]
(div :class "form-group"
(label :class "col-sm-7 control-label"
title)
(div :class "col-sm-5"
(div :class "btn-group"
(a :on-click #(reset! state true)
:class (cell= {:btn true
:btn-success (true? state)
:btn-default (or (false? state) (nil? state))}) "Yes")
(a :on-click #(reset! state false)
:class (cell= {:btn true
:btn-danger (false? state)
:btn-default (or (true? state) (nil? state))}) "No")))))
;; ----------------
;; View
;; ----------------
(html
(head
(link :rel "stylesheet" :href "http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css")
(link :rel "stylesheet" :href "http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css")
(script :src "http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"))
(body :class "page4"
(div :class "navbar navbar-inverse navbar-fixed-top" :role "navigation"
(div :class "container"
(div :class "navbar-header"
(button :type "button"
:class "navbar-toggle"
:data-toggle "collapse"
:data-target ".navbar-collapse")
(span :class "sr-only" "Toggle Navigation")
(span :class "icon-bar")
(span :class "icon-bar")
(span :class "icon-bar")
(a :class "navbar-brand" :href "demo2.html" "Question Test"))))
(div :class "collapse navbar-collapse"
(ul :class "nav navbar-nav"
(li :class "active"
(a :href "demo2.html" "Home"))))
(div :class "well"
(p "The questions")
(form :class "form-horizontal" :role "form"
(map-view-cell
#(question :state (view-cell % [:answer]) :title (:question @%)) questions))
(p (text "Answered ~{answered} of ~(count questions)"))
(button :on-click #(swap! questions conj {:question "hey" :answer nil}) "Add question"))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment