reagent icon indicating copy to clipboard operation
reagent copied to clipboard

Ability to register custom Hiccup tags

Open mfikes opened this issue 7 years ago • 3 comments

A useful feature would be an ability to register custom Hiccup tags, essentially associating keywords with React classes.

This would be particularly useful use with React Native and re-frame, where common practice today is to write Hiccup containing vars resolving to (adapted) native React classes in re-natal projects.

Consider the way things are typically done today:

(def ReactNative (js/require "react-native"))
(def text (reagent.core/adapt-react-class (.-Text ReactNative)))

(defn greeting []
  [text "Hello"])

Note that the text var is referenced directly in the Hiccup.

It would be nice to use :text instead, because view data could then more easily be treated as edn, round-tripped between printing and reading, more easily unit-tested in a pure fashion, etc.:

(defn greeting []
  [:text "Hello"])

It appears to be possible to do this today by abusing private API (and perhaps abusing internal data structures) via a registration function:

(defn register-tag [k v]
  (goog.object/set reagent.impl.template/tag-name-cache k v))

Supporting this enhancement request would likely involve some sort of public API that allows and blesses something along the lines of this function (not necessarily polluting the tag-name-cache, but—in some way—achieving the same end-effect of causing keywords to be resolved to registered values.)

I'm not familiar with the internals of Reagent, but this this appears to lead to a working solution when used with re-frame and re-natal.

As a more extensive example illustrating how something like this could be used in downstream projects, consider the namespace below, derived by making some slight modifications to the namespace that you would get when doing a new re-natal init <project-name>.

The salient (and desired) change in the code is that the view generated in app-root involves "regular" looking Hiccup with tags like [:view ..., [:text .., etc. instead of referencing vars in the first elements of these vectors as in [view ..., [text .., etc.:

(ns hiccup-tags.ios.core
  (:require 
    [goog.object :as gobj]
    [reagent.core :as r :refer [atom]]
    [reagent.impl.template]
    [re-frame.core :refer [subscribe dispatch dispatch-sync]]
    [hiccup-tags.events]
    [hiccup-tags.subs]))

(def ReactNative (js/require "react-native"))

(def app-registry (.-AppRegistry ReactNative))

(defn register-tag [k v]
  (gobj/set reagent.impl.template/tag-name-cache k v))

(def tag->class
  {:text                (.-Text ReactNative)
   :view                (.-View ReactNative)
   :image               (.-Image ReactNative)
   :touchable-highlight (.-TouchableHighlight ReactNative)})

(defn register-tags []
  (doseq [[kw c] tag->class]
    (register-tag (name kw) (r/adapt-react-class c))))

(def logo-img (js/require "./images/cljs.png"))

(defn alert [title]
  (.alert (.-Alert ReactNative) title))

(defn app-root []
  (let [greeting (subscribe [:get-greeting])]
    (fn []
      [:view {:style {:flex-direction "column" :margin 40 :align-items "center"}}
       [:text {:style {:font-size 30 :font-weight "100" :margin-bottom 20 :text-align "center"}} @greeting]
       [:image {:source logo-img
               :style  {:width 80 :height 80 :margin-bottom 30}}]
       [:touchable-highlight {:style {:background-color "#999" :padding 10 :border-radius 5}
                             :on-press #(alert "HELLO!")}
        [:text {:style {:color "white" :text-align "center" :font-weight "bold"}} "press me"]]])))

(defn init []
  (register-tags)
  (dispatch-sync [:initialize-db])
  (.registerComponent app-registry "HiccupTags" #(r/reactify-component app-root)))

mfikes avatar Apr 08 '18 04:04 mfikes

Related: #48 #198

Deraen avatar Apr 08 '18 07:04 Deraen

I also would highly welcome such a feature. Mt usecase is that we want to render values from a clojure repl in a reagent frontend. I tried to achieve a similar functionality with unevaluated symbols; however it seems to be impossible to convert from symbol to function reference in clojurescript in a dynamic way. So a keyword registry would make a lot of sense. I can do the replacement via a clojure Walk function myself. But registering additional keywords would be a much cleaner way.

awb99 avatar Dec 05 '19 06:12 awb99

Compiler protocol will allow customizing how Hiccup keywords are converted to elements, that can be useful in this case: https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/template.cljs#L307

Deraen avatar Jun 05 '21 11:06 Deraen