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

Need to clarify how plain lists are supposed to be treated by as.tags #181

Closed
wch opened this issue Sep 23, 2020 · 1 comment
Closed

Need to clarify how plain lists are supposed to be treated by as.tags #181

wch opened this issue Sep 23, 2020 · 1 comment

Comments

@wch
Copy link
Collaborator

wch commented Sep 23, 2020

The handling of plain lists in htmltools is inconsistent and kind of confusing. If there's a list at the top level, it causes problems. But if it's inside of another tag/tagList object, it seems to behave almost like a tagList.

We should clarify where a plain list is acceptable and where it's not.

library(htmltools)

# Here's a tagList and a list with HTML inside
tl <- tagList(div("ab"), "cd")
tl
#> <div>ab</div>
#> cd

l <- list(div("ab"), "cd")
l
#> [[1]]
#> <div>ab</div>
#> 
#> [[2]]
#> [1] "cd"


# as.tags() works on the tagList, but errors on the list. The error is actually
# due to the printing.
as.tags(tl)
#> <div>ab</div>
#> cd
as.tags(l)
#> Error in writeImpl(text): Text to be written must be a length-one character vector


# We can looks at the str() of the output of as.tags(). For the plain list, it
# doesn't look right -- it's basically the deparsed code.
str(as.tags(tl))
#> List of 2
#>  $ :List of 3
#>   ..$ name    : chr "div"
#>   ..$ attribs : Named list()
#>   ..$ children:List of 1
#>   .. ..$ : chr "ab"
#>   ..- attr(*, "class")= chr "shiny.tag"
#>  $ : chr "cd"
#>  - attr(*, "class")= chr [1:2] "shiny.tag.list" "list"
str(as.tags(l))
#> List of 1
#>  $ : chr [1:2] "list(name = \"div\", attribs = list(), children = list(\"ab\"))" "cd"
#>  - attr(*, "class")= chr [1:2] "shiny.tag.list" "list"


# Now, if we wrap either of them in another tag, they produce the same text
# output from as.tags().
as.tags(div(tl))
ab
cd
#> <div>
#>   <div>ab</div>
#>   cd
#> </div>
as.tags(div(l))
ab
cd
#> <div>
#>   <div>ab</div>
#>   cd
#> </div>

# Structure of as.tags is almost identical, except for shiny.tag.list attribute
str(as.tags(div(tl)))
#> List of 3
#>  $ name    : chr "div"
#>  $ attribs : Named list()
#>  $ children:List of 1
#>   ..$ :List of 2
#>   .. ..$ :List of 3
#>   .. .. ..$ name    : chr "div"
#>   .. .. ..$ attribs : Named list()
#>   .. .. ..$ children:List of 1
#>   .. .. .. ..$ : chr "ab"
#>   .. .. ..- attr(*, "class")= chr "shiny.tag"
#>   .. ..$ : chr "cd"
#>   .. ..- attr(*, "class")= chr [1:2] "shiny.tag.list" "list"
#>  - attr(*, "class")= chr "shiny.tag"
str(as.tags(div(l)))
#> List of 3
#>  $ name    : chr "div"
#>  $ attribs : Named list()
#>  $ children:List of 1
#>   ..$ :List of 2
#>   .. ..$ :List of 3
#>   .. .. ..$ name    : chr "div"
#>   .. .. ..$ attribs : Named list()
#>   .. .. ..$ children:List of 1
#>   .. .. .. ..$ : chr "ab"
#>   .. .. ..- attr(*, "class")= chr "shiny.tag"
#>   .. ..$ : chr "cd"
#>  - attr(*, "class")= chr "shiny.tag"


# Note that the structure of both above adds a layer of nesting in the children.
# Compare to case where there's no list or tagList, just a div wrapping the
# inner content.
str(as.tags(div(div("ab"), "cd")))
#> List of 3
#>  $ name    : chr "div"
#>  $ attribs : Named list()
#>  $ children:List of 2
#>   ..$ :List of 3
#>   .. ..$ name    : chr "div"
#>   .. ..$ attribs : Named list()
#>   .. ..$ children:List of 1
#>   .. .. ..$ : chr "ab"
#>   .. ..- attr(*, "class")= chr "shiny.tag"
#>   ..$ : chr "cd"
#>  - attr(*, "class")= chr "shiny.tag"
@cpsievert
Copy link
Collaborator

The weird result of str(as.tags(l)) is because as.character() gets called on the list before turning into a tag list here:

htmltools/R/tags.R

Lines 909 to 914 in f589c77

as.tags.default <- function(x, ...) {
if (is.list(x) && !isTagList(x))
unclass(x)
else
tagList(as.character(x))
}

Seems like that method should instead be:

as.tags.default <- function(x, ...) {
  if (is.list(x) && !isTagList(x))  return(unclass(x))
  if (is.list(x)) return(tagList(x))
  tagList(as.character(x))
}

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