rstudio / htmltools

Tools for HTML generation and output
https://rstudio.github.io/htmltools/
215 stars 69 forks source link

`head()` does not render under `html()` #322

Closed mpettis closed 2 years ago

mpettis commented 2 years ago

version 0.5.2.9000 From example at: https://rstudio.github.io/htmltools/reference/builder.html#examples

No need for reprex -- the example there puts a head with a title in the list structure:

tags$html(
  tags$head(
    tags$title('My first page')
  ),
  tags$body(
    h1('My first heading'),

but it is not rendered, as shown in the output in at the same link:

#> <html>
#>   <body>
#>     <h1>My first heading</h1>

I confirmed that that is indeed the case with other ways of trying it.

wch commented 2 years ago

At the R console, the print method for HTML tag objects is used by default. It does not show the head, but the head content is present. This behavior can be a bit confusing. If you use renderTags(), you can see that the head content there. And shiny:::renderPage() will render all the content in full HTML document, if you want to see that.

library(htmltools)

x <- tags$html(
  tags$head(
    tags$title('My first page')
  ),
  tags$body(
    h1('My first heading')
  )
)

renderTags(x)
#> $head
#>   <title>My first page</title>
#> 
#> $singletons
#> character(0)
#> 
#> $dependencies
#> list()
#> 
#> $html
#> <html>
#>   <body>
#>     <h1>My first heading</h1>
#>   </body>
#> </html>

cat(shiny:::renderPage(x))
#> <!DOCTYPE html>
#> <html>
#> <head>
#>   <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
#>   <script type="application/shiny-singletons"></script>
#>   <script type="application/html-dependencies">jquery[3.6.0];shiny-css[1.7.1];shiny-javascript[1.7.1]</script>
#> <script src="shared/jquery.min.js"></script>
#> <link href="shared/shiny.min.css" rel="stylesheet" />
#> <script src="shared/shiny.min.js"></script>  <title>My first page</title>
#> </head>
#> <body>
#>   <html>
#>     <body>
#>       <h1>My first heading</h1>
#>     </body>
#>   </html>
#> </body>
#> </html>
wch commented 2 years ago

Related: #271

mpettis commented 2 years ago

EDIT: Never mind, answered my own question in next comment.

I am hoping to use {htmltools} as a standalone for a mini-webserver in conjunction with {plumber}, and I am not sure how to render the created structure back for plumber to serve it. Your example uses shiny:::renderPage(), but that adds a lot of header information that seems specific to {shiny}, which I'm not using. What is the way to create a html document with htmltools and then return the html that can be streamed back in the request (like in plumber?). The following is close, but again, the header is separated from the rest, and the html section has only the body rendered under it. Now, I can hack it to take this structure and stick the header right after the opening html tag, but I suspect I am missing something more elegant.

library(htmltools)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
withTags(
    html(
        head(
            title("First Page")
        ),
        body(
            h1("First Heading")
        )
    )
) %>% renderTags()
#> $head
#>   <title>First Page</title>
#> 
#> $singletons
#> character(0)
#> 
#> $dependencies
#> list()
#> 
#> $html
#> <html>
#>   <body>
#>     <h1>First Heading</h1>
#>   </body>
#> </html>

Created on 2022-04-27 by the reprex package (v2.0.1)

mpettis commented 2 years ago

Never mind... answered my own question:

library(htmltools)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
withTags(
    html(
        head(
            title("First Page")
        ),
        body(
            h1("First Heading")
        )
    )
) %>% doRenderTags()
<html>
  <head>
    <title>First Page</title>
  </head>
  <body>
    <h1>First Heading</h1>
  </body>
</html>

Created on 2022-04-27 by the reprex package (v2.0.1)

cpsievert commented 2 years ago

What is the way to create a html document with htmltools and then return the html that can be streamed back in the request (like in plumber?).

I think the "official" way would be to use save_html(), but that requires writing (and probably reading?) from disk. If you wan't to avoid I/O, renderTags() is probably the best abstraction we have right now, but you'll need to think more carefully about how to handle HTMLDependency()s (i.e., renderDependencies() in the head and also copyDependencyToDir() to a relevant location) with that approach.

Either way, you'll have to somehow make the directory that holds the HTMLDependency() files (in the case of save_html(), this defaults to "lib") available through a {plumber} route/mount (so that the resulting HTML can make requests to external JS/CSS files)

wch commented 2 years ago

Oh, actually, with save_html(), you could use stdout():

save_html(x, stdout())
#> <!DOCTYPE html>
#> <html lang="en">
#> <head>
#> <meta charset="utf-8"/>
#> <style>body{background-color:white;}</style>
#> 
#>   <title>My first page</title>
#> </head>
#> <html>
#>   <body>
#>     <h1>My first heading</h1>
#>   </body>
#> </html>
#> </html>

But now that I look at that, there are two issues:

mpettis commented 2 years ago

@wch @cpsievert I think the doRenderTags() call, in my last comment above, does what I want, and sticks to what I explicitly constructed, and I've been running with that. Unless I'm missing something. But thanks!