mbutterick / pollen-users

please use https://forums.matthewbutterick.com/c/typesetting/ instead
https://forums.matthewbutterick.com/c/typesetting/
52 stars 0 forks source link

Stacking or nesting templates #54

Open basus opened 4 years ago

basus commented 4 years ago

I'm wondering if there's some way to stack or nest templates? For example, I want a toplevel template.html.p that defines the general look and feel of my site, but then I want blog/template.html.p to define the look of blog posts. But the blog post template will share the <head> and navigation elements with the top-level templates, so it would be nice to have those in the toplevel, and just define an included smaller part in the blog template.

From my reading of the Pollen documentation on templates, it seems like there's only one template associated with a source file, and there isn't a way to nest them the way I'd like. Is there some way to do this?

odanoburu commented 4 years ago

would defining the header as a variable that you insert in every template work? it's not nesting, but…

basus commented 4 years ago

Hmm, I suppose I could define each component of the template separately (possibly as Pollen code) and then each template.html.p file combines the components I want.

otherjoel commented 4 years ago

One approach I’ve used was to create a separate module, and all the reusable HTML snippets I might want in a template are implemented there as functions. (Example) Then I require and re-provide this module from pollen.rkt so it is available for use inside templates.

My understanding is that the more code you put inside Racket modules as opposed to putting it inside the template itself, the better, because Racket modules can be precompiled and cached while templates are evaluated fresh every time they are used.

Note the use of #lang pollen/mode to allow using Pollen syntax inside a Racket module (more info in the Pollen docs). This is just a convenience that comes in handy for templatey stuff. Another way of representing HTML snippets as functions is to use scribble/html, as shown in @soegaard's web-tutorial project (see this file in particular).

mbutterick commented 4 years ago

At one point I thought about having templates for templates but it all got too ridiculous. In my experience most of what you describe is a matter of importing a common header and footer, which is easy enough to do with file->string, for instance:

;; test.html.pm
#lang pollen
hello world
;; template.html.p
◊(local-require racket/file)
◊(file->string "header.rktd")
◊(->html doc)
;; header.rktd
<h1>Included!</h1>
basus commented 4 years ago

I ended up addressing this by doing as much as possible with X-expressions in Racket. I have a bunch of snippets which look like:

;; Default code snippets, mostly for inclusion in templates
(define (default-head)
  `(head
    ,@(meta:defaults)
    ,(head:title)
    ,@(head:stylesheets "/css/fonts.css" "/css/style.css")))

(define (default-navigation)
  `(header ,(navigation "Colophon" "Posts" "Drafts" "Series")))

(define (body-with #:id [id #f]
                   #:class [cls #f]
                   #:navigation [nav (default-navigation)]
                   #:contents contents)
  (let ((attrs (match* (id cls)
                 [ [#f #f] '() ]
                 [ [id #f] `((id ,id)) ]
                 [ [#f cls] `((class ,cls)) ]
                 [ [id cls] `((class ,cls) (id ,id)) ]
                 )))
  `(body ,attrs ,nav ,contents)))

and then I can build templates like so:

<!DOCTYPE html>
<html lang="en">
  ◊(->html (default-head))
  ◊(->html (body-with
            #:class "theme-dark"
            #:contents `(div ((id "content"))
                             ,@(select* 'root doc))))
</html>