The Pollen Cookbook

Tooltips

The book Beautiful Racket, which you should read and buy, and which is published with Pollen, includes “tooltips” like the one at the end of this sentence. + I asked Matthew for (and he very readily gave) permission to explain his tooltip design. Thanks Matthew!

This tooltip tag function takes one of the most common, basic forms of tag function: you take in some inputs and emit a single X-expression that translates directly into HTML.

We will apply the Pollen markup design recipe.

What are we aiming for?

We want to add tooltips to our Pollen documents using a simple ◊tooltip tag function, like this:

#lang pollen

This is my main text.◊tooltip{This is the text of a tooltip}.

Get it working in HTML and CSS:

Right-click a tooltip in Beautiful Racket and click Inspect Element and you will see the following HTML:

<span class="tooltip"
      onclick="this.classList.toggle('tooltip_visible')">
  &nbsp;+&nbsp;
  <span clas="tooltip-inner">
    The text of the tooltip
  </span>
</span>

The markup is simple; the magic is all in the CSS for the three named classes tooltip, tooltip-inner and tooltip_visible. You can find this styling by digging further into the source; I have isolated it into the tooltips.css.pp file for you.

Make an equivalent X-expression

Change the opening of each tag and the closing tag into a matched set of parentheses:

(span class="tooltip"
      onclick="this.classList.toggle('tooltip_visible')"
  &nbsp;+&nbsp;
  (span class="tooltip-inner"
    The text of the tooltip
  )
)

Using more parentheses, convert any attributes into a list of pairs:

(span ((class "tooltip")
       (onclick "this.classList.toggle('tooltip_visible')"))
  &nbsp;+&nbsp;
  (span ((class "tooltip-inner"))
    The text of the tooltip
  )
)

Finally mark any element text as strings, using quotations:

(span ((class "tooltip")
       (onclick "this.classList.toggle('tooltip_visible')"))
  "&nbsp;+&nbsp;"
  (span ((class "tooltip-inner"))
    "The text of the tooltip"
  )
)

Make it code

Open DrRacket and define your X-expression as a variable:

#lang racket

(require pollen/template txexpr)

(define test
  '(span ((class "tooltip")
          (onclick "this.classList.toggle('tooltip_visible')"))
         "&nbsp;+&nbsp;"
         (span ((class "tooltip-inner"))
               "The text of the tooltip")))

(The require line allows you to use the ->html and txexpr? functions.)

Press the Run button. Then in the bottom area, you can try manipulating your test variable:

> (txexpr? test)
#t
> (->html test)
"<span class=\"tooltip\" onclick=\"this.classList.toggle('tooltip_visible')\">
&amp;nbsp;+&amp;nbsp;<span class=\"tooltip-inner\">The text of the tooltip
</span></span>"
> (get-tag test)
'span
> (get-attrs test)
'((class "tooltip") (onclick "this.classList.toggle('tooltip_visible')"))
> (get-elements test)
'("&nbsp;+&nbsp;" (span ((class "tooltip-inner")) "The text of the tooltip"))

These kinds of tests simply prove that what you have so far is a valid tagged X-expression that you can convert to HTML and manipulate in various ways.

Functionize it

#lang racket

(require pollen/template txexpr)

(define (tooltip . elems)
  `(span ((class "tooltip")
          (onclick "this.classList.toggle('tooltip_visible')"))
         "&nbsp;+&nbsp;"
         (span ((class "tooltip-inner"))
               ,@elems)))

Notice the three highlighted changes:

  1. Instead of defining a variable test, we’re now defining a function, tooltip. The . elems means “however many arguments are passed to this function, gather them up into a list named elems.” It is important that our tooltip function be able to accept any number of arguments, because in the document, a tooltip tag might contain line breaks or further tags, each of which would become another argument to the tag function.
  2. Previously our span started with ', which means ”don’t execute this next bit as code right now; just treat it as a chunk of data” (that is, shorthand for quote). Using a backtick ` means the same thing, but allows you to insert bits of code inside the quoted expression (that is, quasiquote).
  3. The second span tag used to contain a string (“the text of the tooltip”). Obviously this is where we will want to substitute the actual text supplied in the document. So we do so using ,@elems. The ,@ is a bit of syntactic shorthand for unquote-splicing, which means “insert the following list, but drop its surrounding parentheses and splice it into the surrounding context.”

Press the Run button again:

> (tooltip "This is my tooltip")
'(span
  ((class "tooltip") (onclick "this.classList.toggle('tooltip_visible')"))
  "&nbsp;+&nbsp;"
  (span ((class "tooltip-inner")) "This is my tooltip"))
> (->html (tooltip "This is my tooltip"))
"<span class=\"tooltip\" onclick=\"this.classList.toggle('tooltip_visible')\">
&amp;nbsp;+&amp;nbsp;<span class=\"tooltip-inner\">this is my tooltip</span></span>"

Congratulations, you now have a working tooltip function.

Be a good provider

In order to allow your Pollen documents to use this tag function, add this line to your pollen.rkt:

#lang racket

(require pollen/template txexpr)

(provide (all-defined-out))

(define (tooltip . elems)
  `(span ((class "tooltip")
          (onclick "this.classList.toggle('tooltip_visible')"))
         "&nbsp;+&nbsp;"
         (span ((class "tooltip-inner"))
               ,@elems)))

You could use (provide tooltip) to provide just the tooltip function. But usually you have lots of tag functions in your pollen.rkt file, and it would be annoying to type them all out. Providing (all-defined-out) gives other modules (e.g., your Pollen documents) access to everything defined in that module.