vrac

Template-based web library [WIP] - https://github.com/green-coder/vrac Zulip archive: https://clojurians.zulipchat.com/#narrow/stream/180378-slack-archive/topic/vrac Clojureverse archive: https://clojurians-log.clojureverse.org/vrac
2020-08-27T02:01:36.135600Z

Good morning

2020-08-27T02:02:37.136500Z

Today I would like no do another session of my “open brainstorming” about another area of Vrac : the grammar of the template.

2020-08-27T02:19:23.144700Z

The goal of Vrac’s template are: 1. to be expressive enough to let the user represent his render function in an intuitive way, 2. to represent which data is read, how it is combined and where it is displayed, 3. to be easy to be read by the system (via an AST using Minimallist), 4. to be easy to be analyzed by the system (i.e. we don’t want to have deal with a general program termination problem .. Collatz, I am looking at you). For performances, we want to be able to do most of the analysis statically, while still allowing dynamic component composition.

2020-08-27T02:21:58.146300Z

Bonus goals: 5. to feel familiar to Clojure programmers 6. to be more convenient than the Clojure syntax, with some small shortcuts here and there.

2020-08-27T02:29:03.150500Z

My main problem is: The more expressive the template is, the hardest it is to be analyzed by the system. I have to choose which things should not be possible to be described in the template, so that its analyzing process stays doable and relatively easy. Those limitations are the main constraining lines which will shape the grammar of the template.

2020-08-27T02:32:34.153900Z

So I am in a catch-22 here, because I need to choose in advance what things are or are not easy to analyze on a template, and the result shapes the template’s grammar … but I need to try those grammars before knowing if that’s doable or not, therefore I need to choose it first. The solution is obviously to try things are iterate, but that’s a time consuming process. Instead, I will brainstorm about it without really implementing anything.

2020-08-27T03:08:03.166600Z

There are things I know for sure I can have in the template: 1. Representing html tags with properties and printable values inside. 2. Referring to another component for inclusion (and possibly using recursion via a cycle in the references), passing s-expressions as argument (possibly including variables). 3. Accepting input parameters in Vrac templates, use them as if they were described locally. 4. Use those params and s-expressions in html attributes, if conditions, source data in let and for, and printable values. 5. Destructuration in let and for expressions, as well as in the input param declaration.

yogthos 2020-08-31T12:46:38.371600Z

another thing to consider would be how the state of the component would be controlled, for example whether it's visible, disabled, etc. based on the state of the data in the db

yogthos 2020-08-31T12:46:46.371800Z

but otherwise seems comprehensive

2020-08-27T03:43:59.183800Z

Examples for 1.

[:div "hello, " nil "world!"] ;; nils values are ignored when used as argument on html tags

[:div true " implies truth, and " false " implies anything."] ;; true and false are printed

[:div :foobar] ;; displays ":foobar"

[:div {:style {:color "pink"}, :class [:color "button"], :id :theme-name} "Green"] ;; attributes as in hiccup, keywords used as strings

[:div "debug info: " {:id user-id, :user user-name}] ;; hash-maps can be used as printable values if not in second position in the hiccup vector

;; Ways to lift the ambiguity of the "type" of element inside a hiccup vector:
[:div.debug-info nil {:id user-id, :user user-name}]   ;; Using nil at the attribute position
[:div.debug-info (val {:id user-id, :user user-name})] ;; Using the val function
[:div.debug-info (val [:a :b :c :d])]                  ;; Here, to avoid confusion with hiccup vectors
[:div.debug-info (attrs my-attributes)]                ;; Using the attrs function

;; Those are equivalent:
[:div {:class ["foo-1" "foo-2" "foo-3" "foo-4"], :id "bar"}]
[:div.foo-1#bar.foo-2 {:class [:foo-3 "foo-4"]}]

[:div {:class "foobar"}] ;; Shortcut syntax when only 1 class, no vector needed.

2020-08-27T04:45:23.199100Z

Examples for 2.

;; Multiple ways to refer to vrac components for inclusion.
[:my-ns/my-component "hello"]                             ;; using a qualified keyword
[my-component "hello"]                                    ;; using a symbol which can be resolved in the current namespace
[(if my-cond ::my-component ::other-comp) "hello"]        ;; using a s-expression
[(if my-cond ::my-component my-alias/other-comp) "hello"] ;; using all of the above

;; A component can have any identifier except unqualified keywords which are reserved for html elements.
[true "This is not a pipe"]
[false "This is a pipe"]
[42 "This could be anything"]
['foobar "hello"]
[[:experiment 7] "hello"]

;; Arguments passing
[::my-component true false nil "foo"]           ;; any value can be passed, including nil
[::my-component (val [:a :b]) (val [c d])]      ;; vector literals should be marked as values
[::my-component (str "peace and " my-love-var)] ;; any s-expression can be passed
[::my-component :a :b ~@my-kw-sequence :z]      ;; ~@ slices an expression in the arg list

;; Attributes applied on components
[::my-component.foo {:class "bar"} arg1 arg2]    ;; my-component only has 2 args
[::my-component (attrs my-attributes) arg1 arg2] ;; my-component only has 2 args

;; Included components are always returning html. If you need to return a value, call a function instead.

2020-08-27T05:07:53.208500Z

Examples for 3.

;; Similar to a function
(defc my-component [myself friend]
  [:div (:name myself) " is friend with " (:name friend)
   [:img {:href (:picture-url friend)}]])

;; Supports variable arities
(defc my-component [& kws]
  [:div "List of keywords:"
   [:ul (for [kw kws] [:li kw])]]) ;; The for block is implicitly sliced into its parent

;; Supports multiple arities
(defc my-component
  ([user-name] [:div user-name])
  ([] [:div "Anonymous"]))

2020-08-27T05:43:04.212300Z

Example for 4.

;; Input params can be used in if conditions, let and for blocks
(defc my-component [person]
  (let [name (:name person)]
    (if (zero? (count (:friends person)))
      [:div name " has no friend."]
      [:div name "'s " (count (:friends person)) " friends:"
       [:ul (for [friend (:friends person)] [:li (:name friend)])]])))

;; Can also be used in place of a component, for dynamic component resolution.
;; The value has to evaluate to a component's identifier, usually a qualified keyword.
(defc my-component [user friend-comp]
  [:div "My friends:"
   [:ul (for [friend (:friends user)]
          [friend-comp friend])]])

2020-08-27T05:45:45.214600Z

Example for 5.

;; Destructuring in input parameters
(defc my-component [{my-name :name} {:key [name picture-url] :as friend}]
  [:div my-name " is friend with " name)
   [:img {:href picture-url}]])

;; In let blocks
(let [[first second & rest] my-list] ...)

;; In for blocks
(for [[first second & rest] my-lists] ...)

2020-08-27T06:49:18.221900Z

Now, the part where I am not sure how to put it in the template’s grammar or if it should be supported at all. Those parts require some planning and thinking with the whole system in head, for performance and difficulty of implementation: 1. passing hiccup vectors in argument of component inclusion. 2. getting hiccup vectors from (compute) functions. If yes, them will need to be included as (html my-hiccup) 3. passing functions in argument of component inclusion. Maybe it would work as well as passing values, will think about it. 4. passing some anonymous components fnc as argument of component inclusion.

2020-08-27T06:51:24.222200Z

— end of my daily “open brainstorming”, feedback welcome as always.