Created
June 30, 2014 14:13
-
-
Save edbond/a57c635b86be56c831da to your computer and use it in GitHub Desktop.
marionette contacts editor in om
This file contains 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
;; src/contacts/core.cljs | |
(ns contacts.core | |
(:require [secretary.core :as secretary | |
:include-macros true :refer [defroute]] | |
[goog.events :as events] | |
[om.core :as om :include-macros true] | |
[sablono.core :as html :refer-macros [html]] | |
[alandipert.storage-atom :refer [local-storage]] | |
[domina :refer [set-styles! by-id value]] | |
[domina.events :refer (listen!)] | |
[cljs-uuid-utils]) | |
(:import goog.History | |
goog.history.EventType)) | |
(enable-console-print!) | |
(secretary/set-config! :prefix "#") | |
(defn uuid [] | |
(cljs-uuid-utils/make-random-uuid)) | |
;; Quick and dirty history configuration. | |
(let [h (History.)] | |
(goog.events/listen h EventType/NAVIGATE #(secretary/dispatch! (.-token %))) | |
(doto h (.setEnabled true))) | |
(def contacts (local-storage (atom []) :contacts)) | |
;; top menu | |
(def active-menu (atom "Contacts")) | |
(defroute contacts-path "contacts" [] | |
(reset! active-menu "Contacts")) | |
(defroute about-path "about" [] | |
(reset! active-menu "About")) | |
(defn top-menu [state] | |
(om/component | |
(html [:div.navbar-collapse.collapse | |
[:ul.nav.navbar-nav | |
[:li {:class (if (= state "Contacts") "active")} | |
[:a {:href "#contacts"} "Contacts"]] | |
[:li {:class (if (= state "About") "active")} | |
[:a {:href "#about"} "About"]]]]))) | |
(defn filter-contacts [query] | |
(prn "FILTER" query) | |
(swap! contacts | |
(fn [cs] | |
(mapv #(assoc % :visible | |
(or (= query "") | |
(> (.indexOf (.toLowerCase (:first-name %)) | |
(.toLowerCase query)) -1))) | |
cs)))) | |
(defn hide-dialog [] | |
(set-styles! (by-id "shade") {:display "none"}) | |
(set-styles! (by-id "dialog") {:display "none"})) | |
(defn add-contact [contact] | |
(swap! contacts conj (assoc contact :uuid (str (uuid)))) | |
(hide-dialog) | |
(filter-contacts "")) | |
(defn replace-contact-by-uuid [uuid updated-contact old-contact] | |
(if (= (:uuid old-contact) uuid) | |
(-> updated-contact | |
(assoc :first-name (:new-first-name updated-contact)) | |
(dissoc :new-first-name)) | |
old-contact)) | |
(defn display-dialog [widget state] | |
(prn "display dialog") | |
(set-styles! (by-id "shade") {:display "block"}) | |
(let [d (set-styles! (by-id "dialog") {:display "block"})] | |
(om/root widget state {:target d}))) | |
(defn update-contact [updated-contact] | |
(swap! contacts | |
(fn [cs] | |
(map (partial replace-contact-by-uuid | |
(:uuid updated-contact) updated-contact) | |
cs))) | |
(hide-dialog) | |
(filter-contacts "")) | |
(defn edit-contact [state] | |
(om/component | |
(html [:div.panel.panel-default | |
[:div.panel-body | |
[:h2 "New Contact"] | |
[:hr] | |
[:div.form-group | |
(html/label "first-name" "First Name:") | |
[:input {:name "first-name" | |
:class "form-control" | |
:on-change #(om/update! state [:new-first-name] | |
(value (.-target %))) | |
:on-key-down #(if (= 13 (.-keyCode %)) | |
(update-contact @state)) | |
:value (:first-name state)}]] | |
[:div.form-group | |
[:button {:class "btn" | |
:on-click #(hide-dialog)} "Cancel"] | |
[:button {:class (str "btn btn-primary pull-right" | |
(if (= "" (:first-name state)) " disabled")) | |
:on-click #(when (not= (:new-first-name @state) | |
(:first-name @state)) | |
(update-contact @state))} "Update"]]]]))) | |
(defn new-contact [state] | |
(om/component | |
(html [:div.panel.panel-default | |
[:div.panel-body | |
[:h2 "New Contact"] | |
[:hr] | |
[:div.form-group | |
(html/label "first-name" "First Name:") | |
[:input {:name "first-name" | |
:class "form-control" | |
:on-change #(om/update! state [:first-name] | |
(value (.-target %))) | |
:on-key-down #(if (= 13 (.-keyCode %)) | |
(add-contact @state)) | |
:value (:first-name state)}]] | |
[:div.form-group | |
[:button {:class "btn" | |
:on-click #(hide-dialog)} "Cancel"] | |
[:button {:class "btn btn-primary pull-right" | |
:on-click #(when-not (= "" (:first-name @state)) | |
(add-contact @state))} "Create"]]]]))) | |
(def filter-state (local-storage (atom nil) :filter)) | |
(defn new-and-filter [state] | |
(om/component | |
(html [:div.container | |
[:div.row | |
[:div.col-md-4 | |
[:button {:class "btn btn-primary" | |
:on-click #(display-dialog new-contact {})} "New Contact"]] | |
[:div.col-md-4] | |
[:div.col-md-4 | |
[:input {:name "filter" | |
:class "form-control" | |
:placeholder "Filter" | |
:on-change (fn [e] | |
(filter-contacts (value (.-target e)))) | |
:value @filter-state}]]]]))) | |
(defn remove-contact [contact state] | |
(let [uuid (:uuid @contact)] | |
(filterv #(not= uuid (:uuid %)) state))) | |
(defn contacts-table [state] | |
(om/component | |
(html | |
[:div.container | |
[:h2 "Contacts"] | |
[:table.table | |
[:thead | |
[:tr | |
[:th "First Name"] | |
[:th "Edit"] | |
[:th "Delete"]]] | |
[:tbody | |
(for [contact state | |
:when (:visible contact)] | |
[:tr | |
[:td (:first-name contact)] | |
[:td | |
[:button {:class "btn btn-default" | |
:on-click #(display-dialog edit-contact contact)} "Edit"]] | |
[:td | |
[:button {:class "btn btn-danger" | |
:on-click #(om/transact! state (partial remove-contact contact))} | |
"Delete"]]])]]]))) | |
(om/root top-menu active-menu | |
{:target (by-id "nav")}) | |
(om/root new-and-filter filter-state | |
{:target (by-id "new-and-filter")}) | |
(om/root contacts-table contacts | |
{:target (by-id "contacts")}) | |
(hide-dialog) | |
(filter-contacts "") | |
(listen! js/document :keydown | |
(fn [e] | |
(let [kc (-> e .-evt .-keyCode)] | |
(if (= kc 27) | |
(hide-dialog) | |
false)))) |
This file contains 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
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Contacts</title> | |
<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"> | |
<style type="text/css"> | |
#shade { | |
width: 100%; | |
height: 100%; | |
position: fixed; | |
left: 0; right: 0; top: 0; bottom: 0; | |
background-color: #eee; | |
opacity: 0.5; | |
} | |
#dialog { | |
z-index: 10; | |
position: absolute; | |
left:0; right:0; | |
margin-left: auto; margin-right: auto; | |
max-width: 300px; | |
top: 120px; | |
} | |
</style> | |
</head> | |
<body> | |
<nav class="navbar navbar-default" role="navigation"> | |
<div class="container"> | |
<!-- Brand and toggle get grouped for better mobile display --> | |
<div class="navbar-header"> | |
<a class="navbar-brand" href="#contacts">Contacts</a> | |
</div> | |
<!-- Collect the nav links, forms, and other content for | |
toggling --> | |
<div id="nav"> | |
</div><!-- /.navbar-collapse --> | |
</div><!-- /.container-fluid --> | |
</nav> | |
<div id="new-and-filter"> | |
</div> | |
<div id="contacts"> | |
</div> | |
<div id="shade"> | |
</div> | |
<div id="dialog"> | |
</div> | |
<!-- <script src="http://fb.me/react-0.9.0.js"></script> --> | |
<!-- <script src="out/goog/base.js" type="text/javascript"></script> --> | |
<script src="contacts.js" type="text/javascript"></script> | |
<!-- <script type="text/javascript">goog.require("contacts.core");</script> --> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment