jakub holý

building the right thing, building it right, fast

Kioo: How To Replace The Whole Body

2014-04-08 11:14:47Languages

This whole post in unnecessary since it is simply possible to use a snippet directly without a template, as kindly explained by Creighton Kirkendall.

Kioo, the enlive-inspired templating library for React.js and derived libs such as Om, normally works by matching selectors against elements inside <body> and transforming the matched elements while also keeping all the other ones. But what if you want to keep just the single matched element and drop all the others? You need some tricks and I will demonstrate one possible way.

Dislaimer: This is a result of my experimentation, not deep knowledge if Kioo.

This template HTML:

<!-- kiootest.html -->
<html lang="en">
    <div id="contentOne">Cool content modified by <span id="name">???</span</div>                                           
    <div id="contentTwo">place holder 2</div>

turned into an Om component with this code:

(ns experiment.core
  (:require-macros [kioo.om :refer [defsnippet deftemplate]])
  (:require [om.core :as om :include-macros true]
            [kioo.om :as kioo]))
(deftemplate kiootest "kiootest.html" [_]
  {[:#contentOne :#name] (kioo/content "#inserted by Kioo#")})

(om/root #(om/component (kiootest %)) (atom {}) {:target (. js/document (getElementById "om-shop-list2"))})

would produce this HTML (the wrapping om-shop-list2 is not produced by the template):

<div id="om-shop-list2">
  <div id="contentOne" data-reactid=".5.1">
    <span data-reactid=".5.1.0">Cool content modified by</span>
    <span id="name" data-reactid=".5.1.1">#inserted byKioo#</span>
  <div id="contentTwo" data-reactid=".5.3">place holder 2</div>

This is not what we want - we want to use contentOne but ignore the rest of the template, i.e. contentTwo. You might think of using [:body] to match and substitute the full content of the template but that will not work. The reason is that Kioo normally matches your selectors only against tags inside <body>. If you want to match body itself, you need to use slightly different syntax where you explicitely define the root element selector (either :root, which would match the full html, I suppose, or f.ex. :body).

So we will use a snippet to extract and modify the part of the HTML we want to keep and specify an explicit root selector (instead of the default [:body :> any-node]) on the template to match and replace the whole body:

(defsnippet kiootest-one "kiootest.html" [:#contentOne] [_]
  {[:#name] (content "Kioo")})

(deftemplate kiootest "kiootest.html" [_] [:body] {[:body] (substitute (kiootest-one _))}) ;; ^- custom root selector ;; ^- inside the selected <body..>..</body>, match the body element itself

(om/root #(om/component (kiootest %)) (atom {}) {:target (. js/document (getElementById "om-shop-list2"))})

which will produce the desired

<div id="om-shop-list2">
  <div id="contentOne" data-reactid=".4">
    <span data-reactid=".4.0">Cool content modified by</span>
    <span id="name" data-reactid=".4.1">Kioo</span>


Kioo by default matches your selectors against [:body :> any-node] so you cannot match :body itself. You should rarely need it - especially if you control the HTML and can wrap the content of body into a div that you can then normally match against with Kioo. However, if necessary, you can also provide a custom root selector using the syntax [<your selector> {<selectors and transforms>}], f.ex. (deftemplate ... [:body] {[:body] (substitute (your-snippet-name _))}).