Last active
August 29, 2015 14:01
-
-
Save danielneal/5f140fc0899e7551c54a to your computer and use it in GitHub Desktop.
Hoplon - How to handle collections of cells?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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