;; Copyright (c) Alan Dipert and Micha Niskin. All rights reserved. ;; The use and distribution terms for this software are covered by the ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) ;; which can be found in the file epl-v10.html at the root of this distribution. ;; By using this software in any fashion, you are agreeing to be bound by ;; the terms of this license. ;; You must not remove this notice, or any other, from this software. (page "index.html" (:refer-clojure :exclude [nth]) (:require [tailrecursion.hoplon.reload :refer [reload-all]] [tailrecursion.hoplon.util :refer [nth name pluralize]] [tailrecursion.hoplon.storage-atom :refer [local-storage]])) ;; utility functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (declare route state editing) (reload-all) (def mapvi (comp vec map-indexed)) (defn dissocv [v i] (let [z (- (dec (count v)) i)] (cond (neg? z) v (zero? z) (pop v) (pos? z) (into (subvec v 0 i) (subvec v (inc i)))))) (defn decorate [todo route editing i] (let [{done? :completed text :text} todo] (-> todo (assoc :editing (= editing i) :visible (and (not (empty? text)) (or (= "#/" route) (and (= "#/active" route) (not done?)) (and (= "#/completed" route) done?))))))) ;; persisted state cell (AKA: stem cell) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def state (-> (cell []) (local-storage ::store))) ;; local state cells ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defc loaded? false) (defc editing nil) (def route (route-cell "#/")) ;; formula cells (computed state) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defc= completed (filter :completed state)) (defc= active (remove :completed state)) (defc= plural-item (pluralize "item" (count active))) (defc= todos (mapvi #(list %1 (decorate %2 route editing %1)) state)) ;; state transition functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn todo [t] {:completed false :text t}) (defn destroy! [i] (swap! state dissocv i)) (defn done! [i v] (swap! state assoc-in [i :completed] v)) (defn clear-done! [& _] (swap! state #(vec (remove :completed %)))) (defn new! [t] (when (not (empty? t)) (swap! state conj (todo t)))) (defn all-done! [v] (swap! state #(mapv (fn [x] (assoc x :completed v)) %))) (defn editing! [i v] (reset! editing (if v i nil))) (defn text! [i v] (if (empty? v) (destroy! i) (swap! state assoc-in [i :text] v))) ;; page ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (html :lang "en" (head (meta :charset "utf-8") (meta :http-equiv "X-UA-Compatible" :content "IE=edge,chrome=1") (link :rel "stylesheet" :href "base.css") (title "Hoplon • TodoMVC")) (body (noscript (div :id "noscript" (p "JavaScript is required to view this page."))) (div (section :id "todoapp" (header :id "header" (h1 "todos") (form :on-submit #(do (new! (val-id :new-todo)) (do! (by-id :new-todo) :value "")) (input :id "new-todo" :type "text" :autofocus true :placeholder "What needs to be done?" :on-blur #(do! (by-id :new-todo) :value "")))) (section :id "main" :do-toggle (cell= (not (and (empty? active) (empty? completed)))) (input :id "toggle-all" :type "checkbox" :do-attr (cell= {:checked (empty? active)}) :on-click #(all-done! (val-id :toggle-all))) (label :for "toggle-all" "Mark all as complete") (ul :id "todo-list" (loop-tpl :reverse true :bind-ids [done# edit#] :bindings [[i {edit? :editing done? :completed todo-text :text show? :visible}] todos] (li :do-class (cell= {:completed done? :editing edit?}) :do-toggle show? (div :class "view" :on-dblclick #(editing! @i true) (input :id done# :type "checkbox" :class "toggle" :do-attr (cell= {:checked done?}) :on-click #(done! @i (val-id done#))) (label (text "~{todo-text}")) (button :type "submit" :class "destroy" :on-click #(destroy! @i))) (form :on-submit #(editing! @i false) (input :id edit# :type "text" :class "edit" :do-value todo-text :do-focus edit? :on-blur #(when @edit? (editing! @i false)) :on-change #(when @edit? (text! @i (val-id edit#))))))))) (footer :id "footer" :do-toggle (cell= (not (and (empty? active) (empty? completed)))) (span :id "todo-count" (strong (text "~(count active) ")) (span (text "~{plural-item} left"))) (ul :id "filters" (li (a :href "#/" :do-class (cell= {:selected (= "#/" route)}) "All")) (li (a :href "#/active" :do-class (cell= {:selected (= "#/active" route)}) "Active")) (li (a :href "#/completed" :do-class (cell= {:selected (= "#/completed" route)}) "Completed"))) (button :type "submit" :id "clear-completed" :on-click #(clear-done!) (text "Clear completed (~(count completed))")))) (footer :id "info" (p "Double-click to edit a todo") (p "Part of " (a :href "http://github.com/tailrecursion/hoplon-demos/" "hoplon-demos"))))))