Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature suggestion: More helper functions for rendering #85

Open
jrosell opened this issue Jan 4, 2025 · 8 comments
Open

Feature suggestion: More helper functions for rendering #85

jrosell opened this issue Jan 4, 2025 · 8 comments

Comments

@jrosell
Copy link

jrosell commented Jan 4, 2025

In my experimental project ambtmx, I end up doing some rendering helper functions that might make things easier for other prople too. We could include some of them if you want. https://github.com/jrosell/ambhtmx/blob/main/R/rendering.R

@jrosell jrosell mentioned this issue Jan 4, 2025
@jrosell jrosell changed the title Feature suggestion: More helper funtions for rendering Feature suggestion: More helper functions for rendering Jan 4, 2025
@kennedymwavu
Copy link
Contributor

thanks for this!

a most notable function in {ambhtmx} is render_html(): allows you to render without requiring an explicit html template.
we've been discussing this with @JohnCoene and we saw a need for such functionality in ambiorix. so we will definitely find a way to incorporate your findings.

main condition will be:

  • ensure correct placement of header tags (metatags, css, js). this will avoid issues like css being unintentionally included inline, causing "jumpy" behavior as it loads in the browser.

this will also ensure a smooth transition for those who are already familiar with {htmltools} ie. no overhead of having to learn about html templates when {htmltools} handles all the dynamic generation of html.

@kennedymwavu
Copy link
Contributor

turns out that we inadvertently (thanks @JohnCoene for this new vocabulary) fixed the issue i'm talking about in the above comment when tackling #78

this:

  • eliminates the need for explicit html templates. all you have to do is write using {htmltools}, and use res$send().
  • implies that you can now include js/css dependencies conditionally eg. only include htmx in pages where it's needed, instead of including it in every page.

say you have this directory structure:

.
├── index.R
└── public
    ├── about.js
    └── home.js

this will work well, and ensure correct placement of html head contents:

index.R:

library(ambiorix)
library(htmltools)

#' General page
#'
#' @param head_tags [htmltools::tagList()] containing
#' tags to include in the head of the html page.
#' @param ... [htmltools::tags]. Passed to the body.
#' @return [htmltools::tags$html]
#' @export
page <- \(head_tags = NULL, ...) {
  tags$html(
    tags$head(head_tags),
    tags$body(
      h1("Welcome!"),
      ...
    )
  )
}

#' Handle GET at '/'
#'
#' @export
home_get <- \(req, res) {
  html <- page(
    head_tags = tagList(
      tags$title("My Page"),
      tags$script(src = "/assets/home.js")
    ),
    tags$p("you're at the home page!")
  )

  res$send(html)
}

#' Handle GET at '/about'
#'
#' @export
about_get <- \(req, res) {
  html <- page(
    head_tags = tagList(
      tags$script(src = "/assets/about.js"),
      tags$title("About hehe")
    ),
    tags$p("you're are now at the about page"),
  )

  res$send(html)
}

Ambiorix$new(port = 5000L)$
  static("public", "assets")$
  get("/", home_get)$
  get("/about", about_get)$
  start()

about.js:

alert("in about!!")

home.js:

alert("in home!")

@jrosell
Copy link
Author

jrosell commented Jan 7, 2025

Awesome. What do you think about the hx_ to hx-* attributes replacement? Would be passing some filter/hook function or function list as argument be appropiate? Another option?

@kennedymwavu
Copy link
Contributor

i think that's more about what the user prefers. we do not know beforehand whether they're using htmx. they could be creating a JSON data API or just dealing with binary data. as such, we let the user handle how they treat the attributes.

also, since R (and hence {htmltools}) handles backticks/quotes around vector names well, there would be no need of using underscores in tag attributes and then converting back to dashes.

c(`hx-get` = "/this", `hx-post` = "/that")

#>  hx-get hx-post 
#> "/this" "/that"

library(htmltools)

tags$div(`hx-get` = "/there")

#> <div hx-get="/there"></div>

but if the user needs to do that it's fine.

@jrosell
Copy link
Author

jrosell commented Jan 7, 2025

Can middleware be fired on specific places?

I may want to use hx_* in my code, but I want to have some method (middleware in the rendering or hook system or whatever) to do the replacement to hx-* for me.

This system can be used for other purposes too.

@kennedymwavu
Copy link
Contributor

we currently have pre-render hooks, which will work with res$render() but not with res$send().

with res$send(), the user will have to handle any attribute replacements before passing the response to res$send(). with res$render(), the user can set the pre-render hooks to do the replacement.

@jrosell
Copy link
Author

jrosell commented Jan 8, 2025

Great. Is it possible to set the hook globally instead of in each method? If so, I could do a PR with some clean example using amriorix and htmx, if you want.

@kennedymwavu
Copy link
Contributor

kennedymwavu commented Jan 8, 2025

yes, it's possible to set a global hook via a middleware. here's an example:

index.R:

library(ambiorix)

#' A pre-render hook
#'
#' @param self The request class instance.
#' @param content String. [file] content of the template.
#' @param data Named list. Passed from the [render()] method.
#' @param ext String. File extension of the template file.
my_prh <- \(self, content, data, ext, ...) {
  data$title <- "Mansion"
  pre_hook(content, data)
}

#' Middleware to set a global pre-render hook
#'
#' @export
m1 <- \(req, res) {
  res$pre_render_hook(my_prh)
}

#' Handler for GET at '/'
#'
#' @details Renders the homepage
#' @export
home_get <- \(req, res) {
  res$render(
    file = "page.html",
    data = list(
      title = "Home",
      content = "<h3>hello, world</h3>"
    )
  )
}

error_handler <- \(req, res, error) {
  message(error)
  res$status <- 500L
  res$send("Internal Server Error")
}

Ambiorix$new(port = 5000L)$
  set_error(error_handler)$
  use(m1)$
  get("/", home_get)$
  start()

page.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <title>[% title %]</title>
</head>

<body>

  <div>
    [% content %]
  </div>

</body>

</html>

in this reprex, even though we have provided the title to render() as “Home”, it is changed by the global pre-render hook to “Mansion”.

adding this to the docs, thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants