~benaiah/fennel-online

a4aa9fae28dbbd9390283471a21de57a3369fe4a — Benaiah Mischenko 2 years ago c542c37 master
Small fixes and alternative sequence-based c! syntax
2 files changed, 64 insertions(+), 28 deletions(-)

M src/app.fnl
M src/react-macros.fnl
M src/app.fnl => src/app.fnl +30 -18
@@ 25,6 25,9 @@
(fn use-effect [effect dependencies]
  (React.useEffect nil effect dependencies))

(fn use-onload-effect [effect]
  (React.useEffect nil effect (js! [])))

(fn fetch [url options] (: js.global :fetch url options))
(fn then [p f c] (: p :then (fn [_ a] (f a)) (fn [_ a] (c a))))



@@ 36,33 39,42 @@
    t))

(component! Greet [{:id id :color color :children children}]
   (c! :h1 {:id id
  (c! :h1 {:id id
           :style {:color color}}
      (.. "Hello " (or children "World") "!")))

(component! Paragraph [{:children child}]
   (c! :p {:style {:fontSize 18 :fontFamily :serif}} child))
  (c! :p {:style {:fontSize 18 :fontFamily :serif}} child))

(component! Paragraphs [{:children maybe-children}]
   (let [children (if (: js.global.Array :isArray maybe-children) maybe-children
                      (js! [maybe-children]))]
     (: children :map
        (fn [_ child i]
          (c! Paragraph {:key i} child)))))
  (let [children (if (: js.global.Array :isArray maybe-children) maybe-children
                     (js! [maybe-children]))]
    (: children :map
       (fn [_ child i]
         (c! Paragraph {:key i} child)))))

(fn load-httpbin-response [text-callback]
(fn get-user-agent-from-httpbin []
  (-> (fetch "https://httpbin.org/get?paramOne=valueOne&paramTwo=anotherValue")
      (then (fn [res] (: res :text)))
      (then (fn [json-text] (text-callback json-text))
            (fn [err] (text-callback (: err :toString))))))
      (then (fn [res] (: res :json)))
      (then (fn [json] json.headers.User-Agent)
            (fn [err]
              (: js.console :error err)
              (: err :toString)))))

(component! App [{}]
   (let [[httpbin-state set-httpbin-state] (use-state "")]
     (use-effect (fn [] (load-httpbin-response set-httpbin-state) nil) (js! []))
     (c! :div {}
        (c! Greet {:color :blue} "Benaiah")
        (c! Paragraphs {} "This is a test of props.children.")
        (c! :pre {} (or httpbin-state "")))))
(component! App []
  (let [[user-agent set-user-agent] (use-state "(loading...)")
        [header-color set-header-color] (use-state :blue)]
    (use-onload-effect (fn [] (-> (get-user-agent-from-httpbin) (then set-user-agent)) nil))
    (c! [:div {}
         [Greet {:color header-color} "Benaiah"]
         [Paragraphs {}
          "This is a test of props.children."
          (.. "Your user-agent is " user-agent)]
         [:select {:value header-color
                   :onChange (=> event (set-header-color event.target.value))}
          [:option {:value :blue} :Blue]
          [:option {:value :red} :Red]
          [:option {:value :yellow} :Yellow]]])))

(: ReactDOM :render
   (c! App {})

M src/react-macros.fnl => src/react-macros.fnl +34 -10
@@ 1,9 1,10 @@
(local unpack (or unpack table.unpack))
(fn push [tab v]
  (let [len (# tab)
        i (+ len 1)]
    (tset tab i v)))

(fn jsliteral [val]
(fn js! [val]
  (if (table? val)
    (let [RETURN (gensym)
          dolist `(do)]


@@ 11,13 12,13 @@
          (do
            (push dolist `(local @RETURN (js.new js.global.Array)))
            (each [i v (ipairs val)]
              (push dolist `(tset @RETURN @(- i 1) @(jsliteral v))))
              (push dolist `(tset @RETURN @(- i 1) @(js! v))))
            (push dolist RETURN)
            dolist)

          (do (push dolist `(local @RETURN (js.new js.global.Object)))
              (each [k v (pairs val)]
                (push dolist `(tset @RETURN @(tostring k) @(jsliteral v))))
                (push dolist `(tset @RETURN @(tostring k) @(js! v))))
              (push dolist RETURN)
              dolist)))
    val))


@@ 28,15 29,38 @@
        children [...]
        child-list []]
    (each [i v (ipairs children)]
      (push child-list `@(jsliteral v)))
    `(do (local @ATTRS @(jsliteral (or attrs {})))
      (push child-list `@(js! v)))
    `(do (local @ATTRS @(js! (or attrs {})))
         (: React :createElement @el @ATTRS
            @(table.unpack child-list)))))
            @(unpack child-list)))))

(fn component [name args ...]
(fn c! [arg1 ...]
  (if (sequence? arg1)
      (let [[name attrs] arg1
            child-forms []
            evaluated-child-forms []]
        (for [i 3 (# arg1)] ;; skip name and attrs
          (push child-forms (. arg1 i)))
        (each [_ child-form (ipairs child-forms)]
          ;; FIXME: this will eventually blow the stack
          (push evaluated-child-forms
                (if (sequence? child-form) (c! child-form)
                    child-form)))
        (create-component name attrs (unpack evaluated-child-forms)))

      ;; else
      (create-component arg1 ...)))

(fn component! [name args ...]
  (let [padded-args `[_ @(unpack args)]]
    `(fn @name @padded-args @...)))

{:js! jsliteral
 :c! create-component
 :component! component}
(fn => [arg-or-args ...]
  (let [args (if (sym? arg-or-args) `[@arg-or-args] arg-or-args)
        padded-args `[_ @(unpack args)]]
    `(fn @padded-args @...)))

{:js! js!
 :c! c!
 :component! component!
 :=> =>}