rstudio / htmltools

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

Can't find sibling via htmltools::tagQuery #318

Closed ismirsehregal closed 2 years ago

ismirsehregal commented 2 years ago

I've posted this here a few days ago but didn't get an answer. I'm not sure if this is a bug or if I'm doing something wrong.

I'm trying to find the span: <span class="input-group-text">per month</span>

in the following HTML snippet via htmltools::tagQuery:

<div class="form-group shiny-input-container" style="width:160px;">
  <label class="control-label" for="numericInputIconId">numericInputIcon</label>
  <div class="input-group">
    <input id="numericInputIconId" type="number" class="form-control numeric-input-icon" value="5" min="0" max="10"/>
    <div class="input-group-addon sw-input-icon input-group-append">
      <span class="input-group-text">per month</span>
    </div>
  </div>
  <span class="help-block invalid-feedback hidden d-none"></span>
</div>

However $selectedTags() remains empty:

tagQ <- htmltools::tagQuery(
  shinyWidgets::numericInputIcon(
    inputId = "numericInputIconId",
    label = "numericInputIcon",
    value = 5,
    min = 0,
    max = 10,
    width = "160px",
    icon = list(NULL, "per month")
  )
)
tagQ$find("span")$selectedTags() # only finds the help-block span: [[1]] <span class="help-block invalid-feedback hidden d-none"></span>
tagQ$find(".input-group-text") # `$selectedTags()`: (Empty selection)
tagQ$find(".input-group")$find("div") # `$selectedTags()`: (Empty selection)
tagQ$find("#numericInputIconId")$siblings("div") # `$selectedTags()`: (Empty selection)

What am I missing here?

Related articles:

https://rstudio.github.io/htmltools/articles/tagQuery.html#children https://rstudio.github.io/htmltools/articles/tagQuery.html#siblings

wch commented 2 years ago

It looks like the shinyWidgets::numericInputIcon() returns an object with a tagFunction(), and that tagFunction() generates one of the spans in the resulting output. That's something that the regular printed output doesn't make clear, but if you use str(), you can see the function with class shiny.tag.function, and only one span:

> str(x)
List of 3
 $ name    : chr "div"
 $ attribs :List of 2
  ..$ class: chr "form-group shiny-input-container"
  ..$ style: chr "width:160px;"
 $ children:List of 3
  ..$ :List of 3
  .. ..$ name    : chr "label"
  .. ..$ attribs :List of 2
  .. .. ..$ class: chr "control-label"
  .. .. ..$ for  : chr "numericInputIconId"
  .. ..$ children:List of 1
  .. .. ..$ : chr "numericInputIcon"
  .. ..- attr(*, "class")= chr "shiny.tag"
  ..$ :List of 3
  .. ..$ name    : chr "div"
  .. ..$ attribs :List of 1
  .. .. ..$ class: chr "input-group"
  .. ..$ children:List of 3
  .. .. ..$ : NULL
  .. .. ..$ :List of 3
  .. .. .. ..$ name    : chr "input"
  .. .. .. ..$ attribs :List of 6
  .. .. .. .. ..$ id   : chr "numericInputIconId"
  .. .. .. .. ..$ type : chr "number"
  .. .. .. .. ..$ class: chr "form-control numeric-input-icon"
  .. .. .. .. ..$ value: num 5
  .. .. .. .. ..$ min  : num 0
  .. .. .. .. ..$ max  : num 10
  .. .. .. ..$ children: list()
  .. .. .. ..- attr(*, "class")= chr "shiny.tag"
  .. .. ..$ :List of 1
  .. .. .. ..$ :function ()  
  .. .. .. .. ..- attr(*, "class")= chr "shiny.tag.function"
  .. .. .. ..- attr(*, "class")= chr [1:2] "shiny.tag.list" "list"
  .. ..- attr(*, "class")= chr "shiny.tag"
  ..$ :List of 3
  .. ..$ name    : chr "span"
  .. ..$ attribs :List of 1
  .. .. ..$ class: chr "help-block invalid-feedback hidden d-none"
  .. ..$ children:List of 1
  .. .. ..$ : NULL
  .. ..- attr(*, "class")= chr "shiny.tag"
 - attr(*, "class")= chr "shiny.tag"
 - attr(*, "html_dependencies")=List of 1
  ..$ :List of 10
  .. ..$ name      : chr "shinyWidgets"
  .. ..$ version   : chr "0.6.4"
  .. ..$ src       :List of 2
  .. .. ..$ href: chr "shinyWidgets"
  .. .. ..$ file: chr "assets"
  .. ..$ meta      : NULL
  .. ..$ script    : chr "shinyWidgets-bindings.min.js"
  .. ..$ stylesheet: chr "shinyWidgets.min.css"
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : chr "shinyWidgets"
  .. ..$ all_files : logi FALSE
  .. ..- attr(*, "class")= chr "html_dependency"

The numericInputIcon() function calls markup_input_group(), which calls tagfunction() here: https://github.com/dreamRs/shinyWidgets/blob/83e059f84ab474c9af89383a24739ae940eecea8/R/input-icon.R#L293-L303

tagQuery can't go into the contents of tagFunctions, because those functions are executed and generate their content later, at render time (just before the HTML text is generated), and the content can change depending on whatever the author of the tagFunction decided to do. So that's just something that tagQuery won't be able to do.

What could be better is for the documentation to make clear that tagQuery can't go into a tagFunction, and make it clearer when a tagFunction is part of the hierarchy for a tag/tagList.

ismirsehregal commented 2 years ago

@wch thanks for looking into this!

Now it makes sense that I was able to solve this only via JS (aftershinyWidgets::numericInputIcon() is rendered) and not via tagQuery which was my initial approach.

Please feel free to close this issue (Not sure if you still need it regarding the documentation).

Cheers