diff --git a/README b/README new file mode 100644 index 0000000..5db4817 --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +# webfui + +Client Side Web Development Framework For ClojureScript + +## Usage + +Documentation is still pending (expect it by November 2012). At this time, your best guide is to run the code in the "webfui-examples" directory- Just run "lein deps;lein run" and fire up your Safari, Chrome, or iOS browser to "localhost:8080" + +## License + +Distributed under the Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php), the same as Clojure. diff --git a/info.txt b/info.txt new file mode 100644 index 0000000..25d1608 --- /dev/null +++ b/info.txt @@ -0,0 +1,66 @@ +*presentation notes + +What is the DOM? + +Problems with the DOM + - It changes + - Changing it is slow + +What is EDN? + +What is AJAX? + +How to build a web framework: + - Should do things the Clojure Way + - Code should be in the functional style + - The browser DOM should be EDN data stored in an atom + +DOM is not a complete representation of browser state + - Can't set button as pushed + - Can't set scrollbar position + - Can't set text selection + +Problem: AJAX requires side effects. Reframed problem: Our state atom no longer contains the whole world + +Original sin: Our state is _always_ corrupt in a program that uses AJAX. + +We have to contain the corruption (Can be done with FP) +We have to repair the corruption (Requires imperative programming) + +"State Corruption" Design Pattern + +AJAX=Corruption of state + +Corruption is implicit- Make it explicit + +GET: State item has value of nil + or selection variable has id that points to nonexistent item + +POST: Create new item in state with id=nil + +PUT: Have "holding area" in state, set source of truth=nil. Move from holding area back to final area + +DELETE: Just delete item + +*todos +item insert/deletion in delta system +text selection +full IK artwork +"preview" in calculator + +*Hello World +(ns my-app.core + (:use [webfui.framework :only [launch-app]])) + +(defn render-all [] + "hello world!") + +(launch-app (atom nil) render-all) + + + +*timing +~250 for mouseup +~150 for webfui code +~90 for parsing html +~90 for delta detection diff --git a/lib/clojure-1.3.0.jar b/lib/clojure-1.3.0.jar new file mode 100644 index 0000000..370ef32 Binary files /dev/null and b/lib/clojure-1.3.0.jar differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d0921d0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + webfui + webfui + 0.2 + webfui + Alpha release of Webfui- All Webfui source included in this repository release under Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + http://lisperati.com + + src + test + + + resources + + + + + test-resources + + + + + + central + http://repo1.maven.org/maven2 + + + clojars + http://clojars.org/repo/ + + + + + diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..cfda7ee --- /dev/null +++ b/project.clj @@ -0,0 +1,3 @@ +(defproject webfui "0.2" + :description "Alpha release of Webfui- All Webfui source included in this repository release under Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)" + :url "http://lisperati.com") \ No newline at end of file diff --git a/src/webfui/core.cljs b/src/webfui/core.cljs new file mode 100644 index 0000000..6ea09a6 --- /dev/null +++ b/src/webfui/core.cljs @@ -0,0 +1,63 @@ +(ns webfui.core + (:use [webfui.html :only [html parse-html parsed-html render-attribute-value html-delta]] + [webfui.plugin.core :only [active-plugins Plugin declare-events fix-dom]] + [cljs.reader :only [read-string]])) + +;; This file contains the core engine for webfui. You usually don't want to load this file directly, instead load webfui.dom or webfui.framework. + +(defn body [] + (.-body js/document)) + +(defn select-path-dom [node path] + (if-let [[cur & more] (seq path)] + (recur (.item (.-childNodes node) cur) more) + node)) + +(defn dom-ready? [] + (= (.-readyState js/document) "complete")) + +(defn dom-ready [fun] + (set! (.-onload js/window) fun)) + +(defn parsed-html-watcher [key a old new] + (let [delta (html-delta (.-children old) (.-children new) [])] + (doseq [[typ path a b] delta] + (let [node (select-path-dom (body) path)] + (case typ + :att (if (= a :value) + (set! (.-value node) (str b)) + (.setAttribute node (name a) (render-attribute-value a b))) + :rem-att (.removeAttribute node (name a)) + :html (set! (.-innerHTML node) (apply str (map html a)))))) + (doseq [plugin @active-plugins] + (fix-dom plugin)))) + +(def parsed-html-atom (atom (parsed-html. :body {} nil))) + +(defn update-parsed-html-atom [new old] + (parsed-html. :body + {} + (if (or (seq? new) (list? new)) + (parse-html new) + (parse-html (list new))))) + +(defn html-watcher [key a old new] + (swap! parsed-html-atom (partial update-parsed-html-atom new))) + +(def dom-watchers (atom {})) + +(defn core-add-dom-watch [id fun] + (swap! dom-watchers assoc id fun)) + +(defn init-dom [html] + (let [b (body)] + (doseq [plugin @active-plugins] + (declare-events plugin (body) dom-watchers parsed-html-atom)) + (add-watch html :dom-watcher html-watcher) + (add-watch parsed-html-atom :parsed-html-watcher parsed-html-watcher) + (swap! html identity))) + +(defn core-defdom [clj-dom] + (if (dom-ready?) + (init-dom clj-dom) + (dom-ready (partial init-dom clj-dom)))) diff --git a/src/webfui/dom.cljs b/src/webfui/dom.cljs new file mode 100644 index 0000000..8b44e86 --- /dev/null +++ b/src/webfui/dom.cljs @@ -0,0 +1,11 @@ +(ns webfui.dom + (:use [webfui.core :only [core-add-dom-watch core-defdom]] + [webfui.plugin.core :only [register-plugin]] + [webfui.plugin.form-values :only [form-values]])) + +(def add-dom-watch core-add-dom-watch) + +(def defdom core-defdom) + +(register-plugin (form-values.)) + diff --git a/src/webfui/dom_manipulation.cljs b/src/webfui/dom_manipulation.cljs new file mode 100644 index 0000000..615c8d9 --- /dev/null +++ b/src/webfui/dom_manipulation.cljs @@ -0,0 +1,23 @@ +(ns webfui.dom-manipulation + (:use [webfui.html :only [unparse-html]])) + +;; This file is for DOM manipulation functions used by both the core of webfui as well as plugins. + +(defn path-dom [node] + (drop 3 + (reverse ((fn f [node] + (lazy-seq (when node + (cons (dec (count (take-while identity (iterate #(.-previousSibling %) node)))) (f (.-parentNode node)))))) + node)))) + +(defn select-path-html [html path] + (if-let [[cur & more] (seq path)] + (recur (nth (.-children html) cur) more) + html)) + +(defn resolve-target [parsed-html target] + (let [path (path-dom target) + parsed-element (select-path-html parsed-html path)] + (unparse-html parsed-element))) + + diff --git a/src/webfui/framework.cljs b/src/webfui/framework.cljs new file mode 100644 index 0000000..861960b --- /dev/null +++ b/src/webfui/framework.cljs @@ -0,0 +1,51 @@ +(ns webfui.framework + (:use [webfui.dom :only [defdom add-dom-watch]] + [webfui.plugin.core :only [register-plugin]] + [webfui.plugin.mouse :only [mouse add-mouse-watch]] + [webfui.state-patches :only [patch]] + [webfui.utilities :only [get-attribute]])) + +(register-plugin (mouse.)) + +(defn state-watcher [dom renderer key state old new] + (swap! dom (constantly (renderer new)))) + +(declare cur-state) + +(defn launch-app [state renderer] + (let [dom (atom (renderer @state))] + (defdom dom) + (add-watch state :state-watcher (partial state-watcher dom renderer))) + (def cur-state state)) + +(defn add-dom-watch-helper [id fun] + (add-dom-watch id + (fn [_ element-new] + (swap! cur-state + (fn [state] + (let [diff (fun state element-new)] + (patch state diff))))))) + +(def mouse-down-state (atom nil)) + +(defn add-mouse-watch-helper [id fun optimization] + (add-mouse-watch id + (fn [element-old element-new points] + (swap! mouse-down-state + (fn [old] + (or old @cur-state))) + (reset! cur-state + (if (= optimization :incremental) + (let [mds @mouse-down-state + diff (fun mds element-old element-new (subvec points (max 0 (- (count points) 2)))) + new-state (patch mds diff)] + (reset! mouse-down-state + (when (get-attribute element-old :active) + new-state)) + new-state) + (let [mds @mouse-down-state + diff (fun mds element-old element-new points)] + (when-not (get-attribute element-old :active) + (reset! mouse-down-state nil)) + (patch mds diff))))))) + diff --git a/src/webfui/framework/macros.clj b/src/webfui/framework/macros.clj new file mode 100644 index 0000000..c4aa914 --- /dev/null +++ b/src/webfui/framework/macros.clj @@ -0,0 +1,11 @@ +;*CLJSBUILD-MACRO-FILE*; + +(ns webfui.framework.macros) + +(defmacro add-dom-watch [id vars & more] + `(webfui.framework/add-dom-watch-helper ~id (fn ~vars ~@more))) + +(defmacro add-mouse-watch [id vars & more] + (if (vector? id) + `(webfui.framework/add-mouse-watch-helper ~(first id) (fn ~vars ~@more) ~(second id)) + `(webfui.framework/add-mouse-watch-helper ~id (fn ~vars ~@more) :full))) \ No newline at end of file diff --git a/src/webfui/html.cljs b/src/webfui/html.cljs new file mode 100644 index 0000000..1f523c3 --- /dev/null +++ b/src/webfui/html.cljs @@ -0,0 +1,145 @@ +(ns webfui.html + (:use [clojure.set :only [union]])) + +(deftype parsed-tagname [tagname id classes]) +(deftype parsed-html [tagname attributes children]) + +(defn parse-tagname [tagname] + (let [[_ name _ id classes] (re-matches #"^([^.^#]+)(#([^.]+))?(\..+)?" tagname)] + (parsed-tagname. (keyword name) + (when id + (keyword id)) + (when classes + (map second (re-seq #"\.([^.]+)" classes)))))) + +(let [cache (atom {})] + (defn parse-tagname-memoized [tagname] + (or (@cache tagname) + (let [val (parse-tagname tagname)] + (swap! cache assoc tagname val) + val)))) + +(declare parse-html) + +(defn parse-element [element] + (let [[tagname & more] element + parsed (parse-tagname-memoized tagname) + classes (.-classes parsed) + id (.-id parsed) + tagname (.-tagname parsed) + attributes {} + attributes (if classes + (assoc attributes + :class + (apply str (interpose \ classes))) + attributes) + attributes (if id + (assoc attributes :id id) + attributes) + [a & b] more + [attributes children] (if (map? a) + [(merge attributes a) b] + [attributes more])] + (parsed-html. tagname attributes (parse-html children)))) + +(defn merge-strings [lst] + (if-let [[x & more] lst] + (if-let [[y & more] more] + (if (and (string? x) (string? y)) + (merge-strings (cons (str x y) more)) + (cons x (merge-strings (cons y more)))) + lst) + lst)) + +(defn parse-html [html] + (mapcat (fn [x] + (cond (vector? x) [(parse-element x)] + (and (coll? x) (not (string? x))) (parse-html x) + :else [x])) + (merge-strings html))) + +(defn tag [tagname atts s] + (if (#{:br} tagname) + (str "<" (name tagname) atts ">") + (str "<" (name tagname) atts ">" (apply str s) ""))) + +(defn pixels [k] + (str (.toFixed k 3) "px")) + +(defn render-css [css] + (apply str + (interpose \; + (for [[k v] css] + (str (name k) + ":" + (cond (keyword? v) (name v) + (and (number? v) (#{:line-height :top :bottom :left :right :width :height} k)) (pixels v) + :else v)))))) + +(defn render-attribute-value [key value] + (cond (keyword? value) (name value) + (= key :data) (print-str value) + (= key :style) (render-css value) + :else value)) + +(defn render-attributes [atts] + (apply str + (for [[key value] atts] + (str " " + (name key) + "=\"" + (render-attribute-value key value) + \")))) + +(defn html [content] + (cond (instance? parsed-html content) (let [tagname (.-tagname content) + attributes (.-attributes content) + children (.-children content)] + (if tagname + (tag tagname (when attributes + (render-attributes attributes)) + (map html children)) + "")) + :else (str content))) + +(defn html-delta [old-html new-html path] + (if (= (count old-html) (count new-html)) + (let [pairs (map vector old-html new-html) + fixable (every? (fn [[old-child new-child]] + (if (and (instance? parsed-html old-child) (instance? parsed-html new-child)) + (= (.-tagname old-child) (.-tagname new-child)) + (= old-child new-child))) + pairs)] + (if fixable + (apply concat + (map-indexed (fn [i [old-element new-element]] + (when (instance? parsed-html old-element) + (let [old-tagname (.-tagname old-element) + old-attributes (.-attributes old-element) + old-children (.-children old-element) + new-tagname (.-tagname new-element) + new-attributes (.-attributes new-element) + new-children (.-children new-element) + path (conj path i) + att-delta (when (not= old-attributes new-attributes) + (mapcat (fn [key] + (let [old-val (old-attributes key) + new-val (new-attributes key)] + (cond (not new-val) [[:rem-att path key]] + (not= old-val new-val) [[:att path key new-val]] + :else []))) + (union (set (keys old-attributes)) (set (keys new-attributes))))) + child-delta (html-delta old-children new-children path)] + (concat att-delta child-delta)))) + pairs)) + [[:html path new-html]])) + [[:html path new-html]])) + +(defn unparse-html [html] + (if (or (string? html) (number? html)) + html + (let [tagname (.-tagname html) + attributes (.-attributes html) + children (.-children html)] + (vec (concat [tagname attributes] (map unparse-html children)))))) + diff --git a/src/webfui/macros.clj b/src/webfui/macros.clj new file mode 100644 index 0000000..8779a0e --- /dev/null +++ b/src/webfui/macros.clj @@ -0,0 +1,15 @@ +;*CLJSBUILD-MACRO-FILE*; + +(ns webfui.macros) + +(defmacro dbg [cur & more] + `(do (.log js/console (print-str '~cur "-->")) + (let [x# (~cur ~@more)] + (.log js/console (print-str '~cur "-->" (pr-str x#))) + x#))) + +(defmacro dbgv [& vars] + `(do ~@(map (fn [var] + `(do (.log js/console (print-str '~var "==>" (pr-str ~var))) + ~var)) + vars))) \ No newline at end of file diff --git a/src/webfui/plugin/core.cljs b/src/webfui/plugin/core.cljs new file mode 100644 index 0000000..9b797e9 --- /dev/null +++ b/src/webfui/plugin/core.cljs @@ -0,0 +1,10 @@ +(ns webfui.plugin.core) + +(def active-plugins (atom [])) + +(defprotocol Plugin + (declare-events [this body dom-watchers parsed-html]) + (fix-dom [this])) + +(defn register-plugin [plugin] + (swap! active-plugins conj plugin)) \ No newline at end of file diff --git a/src/webfui/plugin/form_values.cljs b/src/webfui/plugin/form_values.cljs new file mode 100644 index 0000000..e2fe523 --- /dev/null +++ b/src/webfui/plugin/form_values.cljs @@ -0,0 +1,25 @@ +(ns webfui.plugin.form-values + (:use [webfui.plugin.core :only [Plugin]] + [webfui.dom-manipulation :only [select-path-html parsed-html unparse-html path-dom resolve-target]])) + +(defn input [dom-watchers parsed-html event] + (let [target (.-target event) + [tagname attributes :as element] (resolve-target @parsed-html target) + event (@dom-watchers (keyword (:watch attributes)))] + (when (and event (contains? #{:input :textarea} tagname)) + (let [value (.-value target) + new-element (update-in element + [1 :value] + (fn [old] + (set! (.-value target) old) + value))] + (event element new-element))))) + +(deftype form-values [] + Plugin + (declare-events [this body dom-watchers parsed-html] + (.addEventListener body "input" (partial input dom-watchers parsed-html))) + (fix-dom [this] + nil)) + + diff --git a/src/webfui/plugin/mouse.cljs b/src/webfui/plugin/mouse.cljs new file mode 100644 index 0000000..c3f5e79 --- /dev/null +++ b/src/webfui/plugin/mouse.cljs @@ -0,0 +1,121 @@ +(ns webfui.plugin.mouse + (:use [webfui.plugin.core :only [Plugin]] + [webfui.utilities :only [body]] + [webfui.dom-manipulation :only [resolve-target]] + [cljs.reader :only [read-string]])) + +(def mouse-watchers (atom {})) + +(declare mouse-down-element) + +(defn nodelist-to-seq [nl] + (let [result-seq (map #(.item nl %) (range (.-length nl)))] + result-seq)) + +(defn offset [node] + (let [op (.-offsetParent node)] + (if op + (let [[x y] (offset op)] + [(+ x (.-offsetLeft node)) (+ y (.-offsetTop node))]) + [0 0]))) + +(defn all-elements-at-point [client-point] + ((fn f [element] + (let [[x y] client-point + chi (mapcat f (nodelist-to-seq (.-childNodes element)))] + (if (and (.-getBoundingClientRect element) + (let [rect (.getBoundingClientRect element)] + (and (<= (.-left rect) x) (<= (.-top rect) y) (> (.-right rect) x) (> (.-bottom rect) y)))) + (cons element chi) + chi))) + (body))) + +(defn merge-data [acc lst] + (if-let [[k & more] lst] + (cond (not k) (recur acc more) + (not acc) (recur k more) + (and (map? k) (map? acc)) (recur (merge acc k) more) + :else (recur k more)) + acc)) + +(defn mouse-element [parsed-html ev] + (let [target (.-target ev) + typ (.-type ev) + point (cond (#{"touchstart"} typ) (let [touch (.item (.-touches ev) 0)] + [(.-clientX touch) (.-clientY touch)]) + (#{"touchmove"} typ) (let [touch (.item (.-touches ev) 0)] + [(.-clientX touch) (.-clientY touch)]) + (= "touchend" typ) (let [touch (.item (.-changedTouches ev) 0)] + [(.-clientX touch) (.-clientY touch)]) + :else [(.-clientX ev) (.-clientY ev)]) + elements (all-elements-at-point point) + data (merge-data nil + (for [element elements] + (when-let [s (.getAttribute element "data")] + (read-string s))))] + [(update-in (resolve-target @parsed-html target) [1] assoc :offset (offset target) :data data) [(.-pageX ev) (.-pageY ev)]])) + +(defn update-offset [element target] + (update-in element + [1] + (fn [attr] + (assoc attr :offset (offset target) + :data (if-let [data (.getAttribute target "data")] + (read-string data) + (:data attr)))))) + +(defn mouse-event [element] + (@mouse-watchers (get-in element [1 :mouse]))) + +(defn add-mouse-watch [id fun] + (swap! mouse-watchers assoc id fun)) + +(defn mouse-down [parsed-html ev] + (.preventDefault ev) + (let [target (.-target ev) + [new-element point] (mouse-element parsed-html ev) + event (mouse-event new-element)] + (when event + (let [new-element (assoc-in new-element [1 :active] true)] + (def mouse-down-element new-element) + (def mouse-down-target target) + (def points [point]) + (event new-element new-element points))))) + +(defn mouse-move [parsed-html ev] + (.preventDefault ev) + (when mouse-down-element + (let [target (.-target ev) + [new-element point] (mouse-element parsed-html ev) + event (mouse-event mouse-down-element)] + (def points (conj points point)) + (event (update-offset mouse-down-element mouse-down-target) new-element points)))) + +(defn mouse-up [parsed-html ev] + (let [target (.-target ev) + [new-element point] (mouse-element parsed-html ev)] + (when mouse-down-element + (let [event (mouse-event mouse-down-element) + first-element (update-in mouse-down-element [1] #(dissoc % :active))] + (event (update-offset first-element mouse-down-target) new-element points) + (def points nil) + (def mouse-down-element nil) + (def mouse-down-target nil))))) + +(deftype mouse [] + Plugin + (declare-events [this body dom-watchers parsed-html] + (.setTimeout js/window + (fn [] + (.scrollTo js/window 0 1)) + 100) + (.addEventListener body "mousedown" (partial mouse-down parsed-html)) + (.addEventListener body "mousemove" (partial mouse-move parsed-html)) + (.addEventListener body "mouseup" (partial mouse-up parsed-html)) + (.addEventListener js/window "touchstart" (partial mouse-down parsed-html)) + (.addEventListener js/window "touchmove" (partial mouse-move parsed-html)) + (.addEventListener js/window "touchend" (partial mouse-up parsed-html))) + (fix-dom [this] + nil)) + + diff --git a/src/webfui/plugin/scrolling.cljs b/src/webfui/plugin/scrolling.cljs new file mode 100644 index 0000000..93eb322 --- /dev/null +++ b/src/webfui/plugin/scrolling.cljs @@ -0,0 +1,41 @@ +(ns webfui.plugin.scrolling + (:use [webfui.plugin.core :only [Plugin]] + [webfui.dom-manipulation :only [select-path-html parsed-html unparse-html path-dom resolve-target]])) + +(def dom-watchers-atom (atom nil)) +(def parsed-html-atom (atom nil)) + +(defn scroll [event] + (let [target (.-target event) + [tagname attributes :as element] (resolve-target @@parsed-html-atom target) + scroll-top (.-scrollTop target) + scroll-left (.-scrollLeft target) + event (@@dom-watchers-atom (keyword (:watch attributes))) + new-element (update-in element [1] assoc :scroll-top scroll-top :scroll-left scroll-left)] + (event element new-element))) + +(defn check-for-scroll [event] + (let [target (.-target event)] + (.addEventListener target "scroll" scroll))) + +(defn xpath [s] + (let [res (.evaluate js/document s js/document nil (.-ORDERED_NODE_ITERATOR_TYPE js/XPathResult) nil)] + (doall (take-while identity (repeatedly (fn [] (.iterateNext res))))))) + +(deftype scrolling [] + Plugin + (declare-events [this body dom-watchers parsed-html] + (reset! dom-watchers-atom dom-watchers) + (reset! parsed-html-atom parsed-html) + (.addEventListener body "mousedown" check-for-scroll) + (.addEventListener body "keydown" check-for-scroll)) + + (fix-dom [this] + (doseq [element (xpath "//*[@scroll-top]")] + (let [scroll-top (js/parseInt (.getAttribute element "scroll-top"))] + (when (> (js/Math.abs (- (.-scrollTop element) scroll-top)) 1) + (set! (.-scrollTop element) scroll-top) + (.addEventListener element "scroll" scroll) + (.removeAttribute element "scroll-top")))))) + + diff --git a/src/webfui/state_patches.cljs b/src/webfui/state_patches.cljs new file mode 100644 index 0000000..0ae97aa --- /dev/null +++ b/src/webfui/state_patches.cljs @@ -0,0 +1,22 @@ +(ns webfui.state-patches + (:use [clojure.set :only [union]])) + +(defn patch [state diff] + (if diff + (cond (map? state) (into {} + (for [key (union (set (keys state)) (set (keys diff)))] + [key (let [val1 (state key) + val2 (diff key)] + (cond (and val1 val2) (patch val1 val2) + (contains? diff key) val2 + :else val1))])) + (vector? state) (if (map? diff) + (vec (map-indexed (fn [index item] + (if-let [d (diff index)] + (patch item d) + item)) + state)) + diff) + :else diff) + state)) + diff --git a/src/webfui/utilities.cljs b/src/webfui/utilities.cljs new file mode 100644 index 0000000..76ec1cd --- /dev/null +++ b/src/webfui/utilities.cljs @@ -0,0 +1,11 @@ +(ns webfui.utilities) + +(defn body [] + (.-body js/document)) + +(defn get-attribute [element key] + (get-in element [1 key])) + +(defn clicked [first-element last-element] + (and (= first-element last-element) (not (get-attribute first-element :active)))) + diff --git a/test/webfui/test/core.clj b/test/webfui/test/core.clj new file mode 100644 index 0000000..4f45a9e --- /dev/null +++ b/test/webfui/test/core.clj @@ -0,0 +1,6 @@ +(ns webfui.test.core + (:use [webfui.core]) + (:use [clojure.test])) + +(deftest replace-me ;; FIXME: write + (is false "No tests have been written.")) diff --git a/webfui-examples/.gitignore b/webfui-examples/.gitignore new file mode 100644 index 0000000..b8c1b21 --- /dev/null +++ b/webfui-examples/.gitignore @@ -0,0 +1,5 @@ +pom.xml +*jar +/lib/ +/classes/ +.lein-deps-sum diff --git a/webfui-examples/.lein-plugins/checksum b/webfui-examples/.lein-plugins/checksum new file mode 100644 index 0000000..099bda8 --- /dev/null +++ b/webfui-examples/.lein-plugins/checksum @@ -0,0 +1 @@ +b0bea0f2cf6c59e422426393bfdfae292119cbec \ No newline at end of file diff --git a/webfui-examples/README.md b/webfui-examples/README.md new file mode 100644 index 0000000..73745b9 --- /dev/null +++ b/webfui-examples/README.md @@ -0,0 +1,17 @@ +# webfui-examples + +A website written in noir. + +## Usage + +```bash +lein deps +lein run +``` + +## License + +Copyright (C) 2011 FIXME + +Distributed under the Eclipse Public License, the same as Clojure. + diff --git a/webfui-examples/project.clj b/webfui-examples/project.clj new file mode 100644 index 0000000..29b05b5 --- /dev/null +++ b/webfui-examples/project.clj @@ -0,0 +1,42 @@ +(defproject webfui-examples "0.2" + :description "Examples for Webfui" + :source-path "src-clj" + :dependencies [[org.clojure/clojure "1.4.0"] + [webfui "0.2"] + [noir "1.3.0-beta10"] + [hiccup "1.0.1"]] + :main webfui-examples.server + :plugins [[lein-cljsbuild "0.2.7"]] + :cljsbuild {:builds {:add_two_numbers {:source-path "src-cljs/add_two_numbers" + :compiler {:output-to "resources/public/js/add_two_numbers.js" + :optimizations :whitespace + :pretty-print true}} + :add_two_numbers_low_level {:source-path "src-cljs/add_two_numbers_low_level" + :compiler {:output-to "resources/public/js/add_two_numbers_low_level.js" + :optimizations :whitespace + :pretty-print true}} + :calculator {:source-path "src-cljs/calculator" + :compiler {:output-to "resources/public/js/calculator.js" + :optimizations :whitespace + :pretty-print true}} + :calculator_many {:source-path "src-cljs/calculator_many" + :compiler {:output-to "resources/public/js/calculator_many.js" + :optimizations :advanced + :pretty-print false}} + :calculator_ajax {:source-path "src-cljs/calculator_ajax" + :compiler {:output-to "resources/public/js/calculator_ajax.js" + :optimizations :whitespace + :pretty-print true}} + :scrolling {:source-path "src-cljs/scrolling" + :compiler {:output-to "resources/public/js/scrolling.js" + :optimizations :whitespace + :pretty-print true}} + :mouse_tracking {:source-path "src-cljs/mouse_tracking" + :compiler {:output-to "resources/public/js/mouse_tracking.js" + :optimizations :whitespace + :pretty-print true}} + :inverse_kinematics {:source-path "src-cljs/inverse_kinematics" + :compiler {:output-to "resources/public/js/inverse_kinematics.js" + :optimizations :advanced + :pretty-print false}}}}) + diff --git a/webfui-examples/resources/public/css/POCKC___.eot b/webfui-examples/resources/public/css/POCKC___.eot new file mode 100644 index 0000000..a419478 Binary files /dev/null and b/webfui-examples/resources/public/css/POCKC___.eot differ diff --git a/webfui-examples/resources/public/css/POCKC___.svg b/webfui-examples/resources/public/css/POCKC___.svg new file mode 100644 index 0000000..bf79729 --- /dev/null +++ b/webfui-examples/resources/public/css/POCKC___.svg @@ -0,0 +1,603 @@ + + + + +Created by FontForge 20110222 at Mon Mar 7 11:34:23 2011 + By www-data +Copyright 1999 Blue Vinyl Fonts email: bluevinyl@aol.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webfui-examples/resources/public/css/POCKC___.ttf b/webfui-examples/resources/public/css/POCKC___.ttf new file mode 100644 index 0000000..7c61995 Binary files /dev/null and b/webfui-examples/resources/public/css/POCKC___.ttf differ diff --git a/webfui-examples/resources/public/css/POCKC___.woff b/webfui-examples/resources/public/css/POCKC___.woff new file mode 100644 index 0000000..76e67f1 Binary files /dev/null and b/webfui-examples/resources/public/css/POCKC___.woff differ diff --git a/webfui-examples/resources/public/css/add_two_numbers.css b/webfui-examples/resources/public/css/add_two_numbers.css new file mode 100644 index 0000000..876ddaf --- /dev/null +++ b/webfui-examples/resources/public/css/add_two_numbers.css @@ -0,0 +1,9 @@ +body{background-color:dodgerblue; + text-align:center; + font-size:3em} +div{padding:2em} +input{font-size:1em; + background-color:lightblue; + width:5em} +span{background-color:lightblue; + padding:0.1em} diff --git a/webfui-examples/resources/public/css/add_two_numbers_low_level.css b/webfui-examples/resources/public/css/add_two_numbers_low_level.css new file mode 100644 index 0000000..2c5e0ef --- /dev/null +++ b/webfui-examples/resources/public/css/add_two_numbers_low_level.css @@ -0,0 +1,10 @@ +body{background-color:dodgerblue; + text-align:center; + font-family:sans-serif; + font-size:3em} +div{padding:2em} +input{font-size:1em; + background-color:lightblue; + width:5em} +span{background-color:lightblue; + padding:0.1em} diff --git a/webfui-examples/resources/public/css/calculator.css b/webfui-examples/resources/public/css/calculator.css new file mode 100644 index 0000000..885df5b --- /dev/null +++ b/webfui-examples/resources/public/css/calculator.css @@ -0,0 +1,43 @@ +@font-face {font-family:"Pocket Calculator";src:url("POCKC___.eot?") format("eot"),url("POCKC___.woff") format("woff"),url("POCKC___.ttf") format("truetype"),url("POCKC___.svg#PocketCalculator") format("svg");font-weight:normal;font-style:normal;} +html, body, div, span, object,form,input,text,h1,h2,button,label,a,img{-moz-user-select:none; + -webkit-user-select:none; + -webkit-user-drag:none} +textarea,input[type='text']{-webkit-user-select:text; + -moz-user-select:text} +body{background-color:grey; + text-align:center; + font-weight:bold; + font-family:sans-serif; + font-size:3em} +table{margin-top:1em; + background-color:black; + margin-left:auto; + margin-right:auto; + padding:0.2em; + -webkit-border-radius:0.5em; + border-radius:0.5em} +div{text-align:center; + margin:0.2em; + width:2em; + padding-top:0.4em; + padding-bottom:0.35em; + -webkit-border-radius:0.3em; + border-radius:0.3em; + background-color:silver} +#ms,#mr,#ac{background-color:crimson} +#mult,#div,#plus,#minus,#period{background-color:tan} +#eq{width:4.5em;background-color:orange} +#display{background-color:turquoise; + width:6.5em; + -webkit-border-radius:0.2em; + border-radius:0.2em; + padding-top:0.1em; + padding-bottom:0.0em; + padding-right:0.35em; + font-family:"Pocket Calculator"; + font-size:1.4em; + text-align:right} +div{cursor:pointer} +div:active{-webkit-transform:scale(0.9); + -moz-transform:scale(0.9); + opacity:0.7} \ No newline at end of file diff --git a/webfui-examples/resources/public/css/calculator_ajax.css b/webfui-examples/resources/public/css/calculator_ajax.css new file mode 100644 index 0000000..885df5b --- /dev/null +++ b/webfui-examples/resources/public/css/calculator_ajax.css @@ -0,0 +1,43 @@ +@font-face {font-family:"Pocket Calculator";src:url("POCKC___.eot?") format("eot"),url("POCKC___.woff") format("woff"),url("POCKC___.ttf") format("truetype"),url("POCKC___.svg#PocketCalculator") format("svg");font-weight:normal;font-style:normal;} +html, body, div, span, object,form,input,text,h1,h2,button,label,a,img{-moz-user-select:none; + -webkit-user-select:none; + -webkit-user-drag:none} +textarea,input[type='text']{-webkit-user-select:text; + -moz-user-select:text} +body{background-color:grey; + text-align:center; + font-weight:bold; + font-family:sans-serif; + font-size:3em} +table{margin-top:1em; + background-color:black; + margin-left:auto; + margin-right:auto; + padding:0.2em; + -webkit-border-radius:0.5em; + border-radius:0.5em} +div{text-align:center; + margin:0.2em; + width:2em; + padding-top:0.4em; + padding-bottom:0.35em; + -webkit-border-radius:0.3em; + border-radius:0.3em; + background-color:silver} +#ms,#mr,#ac{background-color:crimson} +#mult,#div,#plus,#minus,#period{background-color:tan} +#eq{width:4.5em;background-color:orange} +#display{background-color:turquoise; + width:6.5em; + -webkit-border-radius:0.2em; + border-radius:0.2em; + padding-top:0.1em; + padding-bottom:0.0em; + padding-right:0.35em; + font-family:"Pocket Calculator"; + font-size:1.4em; + text-align:right} +div{cursor:pointer} +div:active{-webkit-transform:scale(0.9); + -moz-transform:scale(0.9); + opacity:0.7} \ No newline at end of file diff --git a/webfui-examples/resources/public/css/calculator_many.css b/webfui-examples/resources/public/css/calculator_many.css new file mode 100644 index 0000000..f1c1f16 --- /dev/null +++ b/webfui-examples/resources/public/css/calculator_many.css @@ -0,0 +1,43 @@ +@font-face {font-family:"Pocket Calculator";src:url("POCKC___.eot?") format("eot"),url("POCKC___.woff") format("woff"),url("POCKC___.ttf") format("truetype"),url("POCKC___.svg#PocketCalculator") format("svg");font-weight:normal;font-style:normal;} +html, body, div, span, object,form,input,text,h1,h2,button,label,a,img{-moz-user-select:none; + -webkit-user-select:none; + -webkit-user-drag:none} +textarea,input[type='text']{-webkit-user-select:text; + -moz-user-select:text} +body{background-color:grey; + text-align:center; + font-weight:bold; + font-family:sans-serif; + font-size:0.4em} +table.calc{margin-top:1em; + background-color:black; + margin-left:auto; + margin-right:auto; + padding:0.2em; + -webkit-border-radius:0.5em; + border-radius:0.5em} +div{text-align:center; + margin:0.2em; + width:2em; + padding-top:0.4em; + padding-bottom:0.35em; + -webkit-border-radius:0.3em; + border-radius:0.3em; + background-color:silver} +#ms,#mr,#ac{background-color:crimson} +#mult,#div,#plus,#minus,#period{background-color:tan} +#eq{width:4.5em;background-color:orange} +#display{background-color:turquoise; + width:6.5em; + -webkit-border-radius:0.2em; + border-radius:0.2em; + padding-top:0.1em; + padding-bottom:0.0em; + padding-right:0.35em; + font-family:"Pocket Calculator"; + font-size:1.4em; + text-align:right} +div{cursor:pointer} +div:active{-webkit-transform:scale(0.9); + -moz-transform:scale(0.9); + opacity:0.7} \ No newline at end of file diff --git a/webfui-examples/resources/public/css/inverse_kinematics.css b/webfui-examples/resources/public/css/inverse_kinematics.css new file mode 100644 index 0000000..d209a26 --- /dev/null +++ b/webfui-examples/resources/public/css/inverse_kinematics.css @@ -0,0 +1,22 @@ +html, body, div, span, object,form,input,text,h1,h2,button,label,a,img{-moz-user-select:none; + -webkit-user-select:none; + -webkit-user-drag:none} +body{margin:0; + padding:0; + position:absolute; + bottom:0px; + top:0px; + left:0px; + right:0px; + overflow:hidden} +.node{opacity:0.5; + position:absolute; + color:white; + padding:5px; + font-weight:bold; + background-color:red} +.destination{background-color:blue} +.box{opacity:0.5; + position:absolute; + background-color:black; + -webkit-transform-origin:0% 0%} \ No newline at end of file diff --git a/webfui-examples/resources/public/css/mouse_tracking.css b/webfui-examples/resources/public/css/mouse_tracking.css new file mode 100644 index 0000000..384da83 --- /dev/null +++ b/webfui-examples/resources/public/css/mouse_tracking.css @@ -0,0 +1,31 @@ +html, body, div, span, object,form,input,text,h1,h2,button,label,a,img{-moz-user-select:none; + -webkit-user-select:none; + -webkit-user-drag:none; +} +body{bottom:0px; + top:0px; + left:0px; + right:0px; + padding:0px; + margin:0px; + position:absolute; + font-size:3em; + overflow:hidden; + color:white} +div.layer{ +background-color:blue; + position:absolute; +top:0; +left:0; +} +div{background-color:blue; + text-align:center; + position:absolute; + font-size:1em; + padding:0px; + margin:0px; + -webkit-border-radius:0.6em; + cursor:pointer} +.dragged{position:absolute; + opacity:0.5; + background-color:black} diff --git a/webfui-examples/resources/public/css/scrolling.css b/webfui-examples/resources/public/css/scrolling.css new file mode 100644 index 0000000..e69de29 diff --git a/webfui-examples/resources/public/css/webfui_examples.css b/webfui-examples/resources/public/css/webfui_examples.css new file mode 100644 index 0000000..876ddaf --- /dev/null +++ b/webfui-examples/resources/public/css/webfui_examples.css @@ -0,0 +1,9 @@ +body{background-color:dodgerblue; + text-align:center; + font-size:3em} +div{padding:2em} +input{font-size:1em; + background-color:lightblue; + width:5em} +span{background-color:lightblue; + padding:0.1em} diff --git a/webfui-examples/resources/public/index.html b/webfui-examples/resources/public/index.html new file mode 100644 index 0000000..31ba7f4 --- /dev/null +++ b/webfui-examples/resources/public/index.html @@ -0,0 +1,16 @@ + +

Webfui Examples

+

(Right now Chrome & Safari work 100%, Firefox will be ready shortly.)

+

+

    +
  1. Add Two Numbers (low level version)
  2. +
  3. Add Two Numbers (version with elegant code)
  4. +
  5. Scrollbar Plugin Example
  6. +
  7. Mouse Plugin Example
  8. +
  9. Calculator
  10. +
  11. Many Calculators
  12. +
  13. Calculator with Serve-side Memory
  14. +
  15. Inverse Kinematics
  16. +
+

+ \ No newline at end of file diff --git a/webfui-examples/resources/public/js/core.js b/webfui-examples/resources/public/js/core.js new file mode 100644 index 0000000..0dff405 --- /dev/null +++ b/webfui-examples/resources/public/js/core.js @@ -0,0 +1,19235 @@ +var COMPILED = false; +var goog = goog || {}; +goog.global = this; +goog.DEBUG = true; +goog.LOCALE = "en"; +goog.provide = function(name) { + if(!COMPILED) { + if(goog.isProvided_(name)) { + throw Error('Namespace "' + name + '" already declared.'); + } + delete goog.implicitNamespaces_[name]; + var namespace = name; + while(namespace = namespace.substring(0, namespace.lastIndexOf("."))) { + if(goog.getObjectByName(namespace)) { + break + } + goog.implicitNamespaces_[namespace] = true + } + } + goog.exportPath_(name) +}; +goog.setTestOnly = function(opt_message) { + if(COMPILED && !goog.DEBUG) { + opt_message = opt_message || ""; + throw Error("Importing test-only code into non-debug environment" + opt_message ? ": " + opt_message : "."); + } +}; +if(!COMPILED) { + goog.isProvided_ = function(name) { + return!goog.implicitNamespaces_[name] && !!goog.getObjectByName(name) + }; + goog.implicitNamespaces_ = {} +} +goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { + var parts = name.split("."); + var cur = opt_objectToExportTo || goog.global; + if(!(parts[0] in cur) && cur.execScript) { + cur.execScript("var " + parts[0]) + } + for(var part;parts.length && (part = parts.shift());) { + if(!parts.length && goog.isDef(opt_object)) { + cur[part] = opt_object + }else { + if(cur[part]) { + cur = cur[part] + }else { + cur = cur[part] = {} + } + } + } +}; +goog.getObjectByName = function(name, opt_obj) { + var parts = name.split("."); + var cur = opt_obj || goog.global; + for(var part;part = parts.shift();) { + if(goog.isDefAndNotNull(cur[part])) { + cur = cur[part] + }else { + return null + } + } + return cur +}; +goog.globalize = function(obj, opt_global) { + var global = opt_global || goog.global; + for(var x in obj) { + global[x] = obj[x] + } +}; +goog.addDependency = function(relPath, provides, requires) { + if(!COMPILED) { + var provide, require; + var path = relPath.replace(/\\/g, "/"); + var deps = goog.dependencies_; + for(var i = 0;provide = provides[i];i++) { + deps.nameToPath[provide] = path; + if(!(path in deps.pathToNames)) { + deps.pathToNames[path] = {} + } + deps.pathToNames[path][provide] = true + } + for(var j = 0;require = requires[j];j++) { + if(!(path in deps.requires)) { + deps.requires[path] = {} + } + deps.requires[path][require] = true + } + } +}; +goog.ENABLE_DEBUG_LOADER = true; +goog.require = function(name) { + if(!COMPILED) { + if(goog.isProvided_(name)) { + return + } + if(goog.ENABLE_DEBUG_LOADER) { + var path = goog.getPathFromDeps_(name); + if(path) { + goog.included_[path] = true; + goog.writeScripts_(); + return + } + } + var errorMessage = "goog.require could not find: " + name; + if(goog.global.console) { + goog.global.console["error"](errorMessage) + } + throw Error(errorMessage); + } +}; +goog.basePath = ""; +goog.global.CLOSURE_BASE_PATH; +goog.global.CLOSURE_NO_DEPS; +goog.global.CLOSURE_IMPORT_SCRIPT; +goog.nullFunction = function() { +}; +goog.identityFunction = function(var_args) { + return arguments[0] +}; +goog.abstractMethod = function() { + throw Error("unimplemented abstract method"); +}; +goog.addSingletonGetter = function(ctor) { + ctor.getInstance = function() { + return ctor.instance_ || (ctor.instance_ = new ctor) + } +}; +if(!COMPILED && goog.ENABLE_DEBUG_LOADER) { + goog.included_ = {}; + goog.dependencies_ = {pathToNames:{}, nameToPath:{}, requires:{}, visited:{}, written:{}}; + goog.inHtmlDocument_ = function() { + var doc = goog.global.document; + return typeof doc != "undefined" && "write" in doc + }; + goog.findBasePath_ = function() { + if(goog.global.CLOSURE_BASE_PATH) { + goog.basePath = goog.global.CLOSURE_BASE_PATH; + return + }else { + if(!goog.inHtmlDocument_()) { + return + } + } + var doc = goog.global.document; + var scripts = doc.getElementsByTagName("script"); + for(var i = scripts.length - 1;i >= 0;--i) { + var src = scripts[i].src; + var qmark = src.lastIndexOf("?"); + var l = qmark == -1 ? src.length : qmark; + if(src.substr(l - 7, 7) == "base.js") { + goog.basePath = src.substr(0, l - 7); + return + } + } + }; + goog.importScript_ = function(src) { + var importScript = goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; + if(!goog.dependencies_.written[src] && importScript(src)) { + goog.dependencies_.written[src] = true + } + }; + goog.writeScriptTag_ = function(src) { + if(goog.inHtmlDocument_()) { + var doc = goog.global.document; + doc.write('