javascripthtmlrr-markdownbookdown

Insert nested tabset in an R Bookdown document


I found this question R Bookdown: Tabbed headings and the awesome answer from @linog which was the basis for my approach.

To show you what's the purpose for my bookdown document, I have done this in r markdown too. For the R markdown document you need library(rmarkdown), library(pandoc) and pandoc_activate(). You render it with: rmarkdown::render("C:/YourPath/tabset.Rmd")

The file tabset.Rmd

---
title: "Test Tabset"
date: "Last edited: `r format(Sys.time(), '%d.%m.%Y')`"
output: html_document
---

# City Trips {.tabset}

These are some sightseeing tips for the cities.

## London {.tabset}

Start your sightseeing in **London** now with these popular spots

### Buckingham Palace

Enjoy a glimpse into the life of royalty with...

### Tower of London

Explore the Tower of London and discover...

## Paris {.tabset}

Start your sightseeing in **Paris** now with these popular spots

### Eiffel Tower

The Eiffel Tower is the most famous landmark in the French capital...

```{r , echo=FALSE, eval=TRUE, fig.cap='Eiffel Tower', out.width='50%'}
knitr::include_graphics('images/Eiffel_Tower.png')
```

### Louvre Museum

Housing a vast collection of art spanning centuries and continents...

## Tokyo {.tabset}

Start your sightseeing in **Tokyo** now with these popular spots

### Shinjuku

Shinjuku is one of the 23 city wards of Tokyo, but the name...

### Edo-Tokyo Museum

The Edo-Tokyo Museum is housed in a unique looking building in the Ryogoku district...

In the single nested tabs I want to insert images like done with the Eiffel Tower but for simplicity I omitted these code snippets...

See the rendered result:

enter image description here

And now it's my purpose to adopt that in my bookdown document.

The packages you need (and hopefully I have all of them listed): library(bookdown), library(knitr), library(markdown), library(rmarkdown), library(tinytex), library(htmltools) and library(pandoc) (with pandoc_activate() afterwards)

You render it with: bookdown::render_book("C:/YourPath/index.Rmd", output_format = "bookdown::gitbook")

The file index.Rmd

---
title: "Tabset in Bookdown"
date: "Last edited: `r format(Sys.time(), '%d.%m.%Y')`"
output:
  bookdown::gitbook:
   includes:
    in_header: header.html
   css: style.css
   number_sections: true
   anchor_sections: true
   split_by: "chapter"
   config:
     toc:
       collapse: section
       before: |
         <li><a href="./">Table of content</a></li>
---


# Test Tabset

<p>Click the Buttons:</p>

<div class="tab">
  <button class="tablinks" id="defaultOpen" data-target="London" onclick="openCity(event, 'London')">London</button>
  <button class="tablinks" data-target="Paris"  onclick="openCity(event, 'Paris')">Paris</button>
  <button class="tablinks" data-target="Tokyo"  onclick="openCity(event, 'Tokyo')">Tokyo</button>
</div>

<div id="London" class="tabcontent">
    Start your sightseeing in **London** now with these popular spots
</div>


<div id="Paris" class="tabcontent">
    Start your sightseeing in **Paris** now with these popular spots
    
    <div class="tab">
        <button class="tablinks" id="defaultOpenInner" data-target="Paris1"  onclick="openCity(event, 'Paris1')">Paris1</button>
        <button class="tablinks" data-target="Paris2"  onclick="openCity(event, 'Paris2')">Paris2</button>
    </div>

    <div id="Paris1" class="tabcontent">
        The Eiffel Tower is the most famous landmark in the French capital...
        
        ```{r, echo=FALSE, fig.cap='Eiffel Tower', out.width='70%'}
        knitr::include_graphics('images/Eiffel_Tower.png', dpi = NA)
        ```
        
    </div>

    <div id="Paris2" class="tabcontent">
        Housing a vast collection of art spanning centuries and continents...
        
        ```{r, echo=FALSE, fig.cap='Louvre Museum', out.width='70%'}
        knitr::include_graphics('images/Louvre_Museum.png', dpi = NA)
        ```
        
    </div>
  
  
</div>

<div id="Tokyo" class="tabcontent">
  Start your sightseeing in **Tokyo** now with these popular spots
</div>

# More Sections

With the CSS file (style.css)

/* CSS code just for the styling of the tabs */
/* Style the tab */
.tab {
  overflow: hidden;
  border: 1px solid #ccc;
  background-color: #f1f1f1;
}

/* Style the buttons inside the tab */
.tab button {
  background-color: inherit;
  float: left;
  border: none;
  outline: none;
  cursor: pointer;
  padding: 14px 16px;
  transition: 0.3s;
  font-size: 17px;
}

/* Change background color of buttons on hover */
.tab button:hover {
  background-color: #ddd;
}

/* Create an active/current tablink class */
.tab button.active {
  background-color: #ccc;
}

/* Style the tab content */
.tabcontent {
  display: none;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-top: none;
}

The header file (header.html)

<script>
window.openCity = function(evt, tabId) {
  var container = evt.currentTarget.closest(".tab").parentNode;

  // Hide content only within this container
  var tabcontent = container.querySelectorAll(".tabcontent");
  for (var i = 0; i < tabcontent.length; i++) {
    tabcontent[i].style.display = "none";
  }

  // Reset buttons only within this container
  var tablinks = container.querySelectorAll(".tablinks");
  for (var i = 0; i < tablinks.length; i++) {
    tablinks[i].classList.remove("active");
  }

  // Show the selected tab and set the corresponding button as active
  var el = document.getElementById(tabId);
  if (el) el.style.display = "block";
  if (evt && evt.currentTarget) evt.currentTarget.classList.add("active");
};

// Open the default tab on page load
document.addEventListener("DOMContentLoaded", function() {
  var defaultTabBtn = document.getElementById("defaultOpen");
  if (defaultTabBtn) {
    window.openCity({ currentTarget: defaultTabBtn }, defaultTabBtn.getAttribute("onclick").match(/'([^']+)'/)[1]);
  }
  // Optionally: also open the inner default tab
  var defaultInnerBtn = document.getElementById("defaultOpenInner");
  if (defaultInnerBtn) {
    window.openCity({ currentTarget: defaultInnerBtn }, defaultInnerBtn.getAttribute("onclick").match(/'([^']+)'/)[1]);
  }
});
</script>

And also the _bookdown.yml file

new_session: no
output_dir: "docs"
rmd_files: ["index.Rmd"]
language:
  label:
    fig: "Figure "

See the rendered result (separated by horizontal lines):

enter image description here

As you can see, it's not that what I expect:

Can you please help me to fix this issue with the aim, that the tabsets are nested, the images are shown and the London tab is shown correctly.

Many thanks in advance!


Solution

  • Here are the main problems:

    1. You used tabspaces in your html structure. but tabs are interpreted as code so your HTML gets wrapped in <code>. To fix this, just don't use tabs within your html structure.
    2. Your nested default sections were not showing because the section "// Hide content only within this container" queries over all tabcontent class elements below tab including your nested tabs and hides them; thus even the tabcontent Paris_1 is hidden even if it should be shown per default. To fix this, I gave the inner tabs, tabcontents and tablinks their own class and added the parameter inner to window.openCity which then changes the query selector within the function.

    index.rmd

    ---
    title: "Tabset in Bookdown"
    date: "Last edited: `r format(Sys.time(), '%d.%m.%Y')`"
    output:
      bookdown::gitbook:
       includes:
        in_header: header.html
       number_sections: true
       anchor_sections: true
       split_by: "chapter"
       config:
         toc:
           collapse: section
           before: |
             <li><a href="./">Table of content</a></li>
    ---
    
    ```{r, echo=FALSE, include=FALSE}
    
    fileConn<-file("header.html")
    writeLines('
    <script>
    window.openCity = function(evt, tabId, inner) {
      var container = evt.currentTarget.closest(inner ? (".i_tab") : (".tab")).parentNode;
    
      // Hide content only within this container
      var tabcontent = container.querySelectorAll(inner ? (".i_tabcontent") : (".tabcontent"));
      for (var i = 0; i < tabcontent.length; i++) {
        tabcontent[i].style.display = "none";    
      }
    
      // Reset buttons only within this container
      var tablinks = container.querySelectorAll(inner ? (".i_tablinks") : (".tablinks"));
      for (var i = 0; i < tablinks.length; i++) {
        tablinks[i].classList.remove("active");
      }
    
      // Show the selected tab and set the corresponding button as active
      var el = document.getElementById(tabId);
      if (el) el.style.display = "block";
      if (evt && evt.currentTarget) evt.currentTarget.classList.add("active");
    };
    
    // Open the default tab on page load
    document.addEventListener("DOMContentLoaded", function() {
      var defaultTabBtn = document.getElementById("defaultOpen");
      if (defaultTabBtn) {
        window.openCity({ currentTarget: defaultTabBtn }, defaultTabBtn.getAttribute("data-target"));
      }
      var defaultInnerBtn = document.getElementById("defaultOpenInner");
      if (defaultInnerBtn) {
         window.openCity({ currentTarget: defaultInnerBtn }, defaultInnerBtn.getAttribute("data-target"), true);
      }
    });
    </script>
    <style>
    /* Shared tab container styles */
    .tab, .i_tab {
      overflow: hidden;
      border: 1px solid #ccc;
      background-color: #f1f1f1;
    }
    
    /* Shared button styles for both outer and inner tabs */
    .tab button, .i_tab button {
      background-color: inherit;
      float: left;
      border: none;
      outline: none;
      cursor: pointer;
      padding: 14px 16px;
      transition: 0.3s;
      font-size: 17px;
    }
    
    /* Hover effect for all tab buttons */
    .tab button:hover, .i_tab button:hover {
      background-color: #ddd;
    }
    
    /* Active state for all tab buttons */
    .tab button.active, .i_tab button.active {
      background-color: #ccc;
    }
    
    /* Shared content styles for both outer and inner tabs */
    .tabcontent, .i_tabcontent {
      display: none;
      padding: 6px 12px;
      border: 1px solid #ccc;
      border-top: none;
    }
    </style>
    ', fileConn)
    close(fileConn)
    
    fileConn<-file("_bookdown.yaml")
    writeLines('
    new_session: no
    output_dir: "docs"
    rmd_files: ["index.Rmd"]
    language:
      label:
        fig: "Figure "
    
    ', fileConn)
    close(fileConn)
    
    # download images
    if (!dir.exists("images")) dir.create("images")
    download.file("https://static.vecteezy.com/system/resources/previews/055/514/860/non_2x/a-3d-eiffel-tower-free-png.png", destfile = "images/Eiffel_Tower.png", mode = 'wb')
    download.file("https://images.rawpixel.com/image_png_800/cHJpdmF0ZS9sci9pbWFnZXMvd2Vic2l0ZS8yMDI0LTA5L3Jhd3BpeGVsb2ZmaWNlOV9waG90b19vZl9sb3V2cmVfbXVzZXVtX3dob2xlX2J1aWxkaW5nX2FnYWluc3RfY19kYTFmODg5Zi0xOWI4LTQzMmMtYTAyZS00OWZiOTA0MGU0NjIucG5n.png", destfile = "images/Louvre_Museum.png", mode = 'wb')
    
    ```
    
    # Test Tabset
    
    <p>Click the Buttons:</p>
    
    <div class="tab">
    <button class="tablinks" id="defaultOpen" data-target="London" onclick="openCity(event, 'London', false)">London</button>
    <button class="tablinks" data-target="Paris"  onclick="openCity(event, 'Paris', false)">Paris</button>
    <button class="tablinks" data-target="Tokyo"  onclick="openCity(event, 'Tokyo', false)">Tokyo</button>
    </div>
    
    <div id="London" class="tabcontent">
    Start your sightseeing in **London** now with these popular spots
    </div>
    
    <div id="Paris" class="tabcontent">
    Start your sightseeing in **Paris** now with these popular spots
    
    <div class="i_tab">
    <button class="i_tablinks" id="defaultOpenInner" data-target="Paris1"  onclick="openCity(event, 'Paris1', true)">Paris1</button>
    <button class="i_tablinks" data-target="Paris2"  onclick="openCity(event, 'Paris2', true)">Paris2</button>
    </div>
    
    <div id="Paris1" class="i_tabcontent">
    The Eiffel Tower is the most famous landmark in the French capital...
    
    ```{r, echo=FALSE, fig.cap='Eiffel Tower', out.width='70%'}
    knitr::include_graphics('images/Eiffel_Tower.png', dpi = NA)
    ```
    </div>
    
    <div id="Paris2" class="i_tabcontent">
    Housing a vast collection of art spanning centuries and continents...
    
    ```{r, echo=FALSE, fig.cap='Louvre Museum', out.width='70%'}
    knitr::include_graphics('images/Louvre_Museum.png', dpi = NA)
    ```
    </div>
    
    </div>
    
    <div id="Tokyo" class="tabcontent">
    Start your sightseeing in **Tokyo** now with these popular spots
    </div>
    
    # More Sections
    

    Run with

    bookdown::render_book("index.Rmd", output_format = "bookdown::gitbook")
    

    giving your desired output

    out