Code folding in bookdown
Asked Answered
B

3

54

The Code folding option in RMarkdown for html documents is awesome. That option makes the programmatic methodology transparent for those who are interested, without forcing the audience to scroll through miles of code. The tight placement of code with prose and interactive graphical output makes the whole project more accessible to a wider audience, and furthermore it reduces the need for additional documentation.

For a larger project, I'm using bookdown, and it works great. The only problem is that there is no code-folding option. Code folding is not currently enabled in bookdown. (see Enable code folding in bookdown )

I know I don't need an option to make it happen. I just need to paste the right code in the right place or places. But what code and where?

A viable alternative would be to put the code chunk below the chunk's outputs in the page. Or, finally, to put them as an appendix. I could do that with html but not reproducible like rbookdown.

Bifrost answered 27/7, 2017 at 21:5 Comment(2)
Here is my hint: github.com/yihui/knitr/blob/master/inst/misc/toggleR.js Use the includes option to include it. You have to understand JavaScript and HTML.Florez
Thanks for the hint!Bifrost
S
41

Global Hide/Show button for the entire page

To use @Yihui's hint for a button that fold all code in the html output, you need to paste the following code in an external file (I named it header.html here):

Edit: I modified function toggle_R so that the button shows Hide Global or Show Global when clicking on it.

<script type="text/javascript">

// toggle visibility of R source blocks in R Markdown output
function toggle_R() {
  var x = document.getElementsByClassName('r');
  if (x.length == 0) return;
  function toggle_vis(o) {
    var d = o.style.display;
    o.style.display = (d == 'block' || d == '') ? 'none':'block';
  }

  for (i = 0; i < x.length; i++) {
    var y = x[i];
    if (y.tagName.toLowerCase() === 'pre') toggle_vis(y);
  }

    var elem = document.getElementById("myButton1");
    if (elem.value === "Hide Global") elem.value = "Show Global";
    else elem.value = "Hide Global";
}

document.write('<input onclick="toggle_R();" type="button" value="Hide Global" id="myButton1" style="position: absolute; top: 10%; right: 2%; z-index: 200"></input>')

</script>

In this script, you are able to modify the position and css code associated to the button directly with the style options or add it in your css file. I had to set the z-index at a high value to be sure it appears over other divisions.
Note that this javascript code only fold R code called with echo=TRUE, which is attributed a class="r" in the html. This is defined by command var x = document.getElementsByClassName('r');

Then, you call this file in the YAML header of your rmarkdown script, as in the example below:

---
title: "Toggle R code"
author: "StatnMap"
date: '`r format(Sys.time(), "%d %B, %Y")`'
output:
  bookdown::html_document2:
    includes:
      in_header: header.html
  bookdown::gitbook:
    includes:
      in_header: header.html
---

Stackoverflow question
<https://mcmap.net/q/336678/-code-folding-in-bookdown>

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

## R Markdown

This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see <http://rmarkdown.rstudio.com>.

When you click the **Knit** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:

```{r cars}
summary(cars)
```

New Edit: Local Hide/show button for each chunk

I finally found the solution !
While looking at the code folding behavior for normal html output (no bookdown), I was able to add it to bookdown. The main javascript function needs to find .sourceCode class divisions to work with bookdown. However, this also requires complementary javascript functions of bootstrap, but not all. This works with gitbook and html_document2.
Here are the steps:

  1. Create a js folder in the same directory than your Rmd file
  2. Download javascript functions transition.js and collapse.js here for instance: https://github.com/twbs/bootstrap/tree/v3.3.7/js and store them in your js folder
  3. Create a new file in the js folder called codefolding.js with the following code. This is the same as for rmarkdown code_folding option but with pre.sourceCode added to find R code chunks:

codefolding.js code:

window.initializeCodeFolding = function(show) {

  // handlers for show-all and hide all
  $("#rmd-show-all-code").click(function() {
    $('div.r-code-collapse').each(function() {
      $(this).collapse('show');
    });
  });
  $("#rmd-hide-all-code").click(function() {
    $('div.r-code-collapse').each(function() {
      $(this).collapse('hide');
    });
  });

  // index for unique code element ids
  var currentIndex = 1;

  // select all R code blocks
  var rCodeBlocks = $('pre.sourceCode, pre.r, pre.python, pre.bash, pre.sql, pre.cpp, pre.stan');
  rCodeBlocks.each(function() {

    // create a collapsable div to wrap the code in
    var div = $('<div class="collapse r-code-collapse"></div>');
    if (show)
      div.addClass('in');
    var id = 'rcode-643E0F36' + currentIndex++;
    div.attr('id', id);
    $(this).before(div);
    $(this).detach().appendTo(div);

    // add a show code button right above
    var showCodeText = $('<span>' + (show ? 'Hide' : 'Code') + '</span>');
    var showCodeButton = $('<button type="button" class="btn btn-default btn-xs code-folding-btn pull-right"></button>');
    showCodeButton.append(showCodeText);
    showCodeButton
        .attr('data-toggle', 'collapse')
        .attr('data-target', '#' + id)
        .attr('aria-expanded', show)
        .attr('aria-controls', id);

    var buttonRow = $('<div class="row"></div>');
    var buttonCol = $('<div class="col-md-12"></div>');

    buttonCol.append(showCodeButton);
    buttonRow.append(buttonCol);

    div.before(buttonRow);

    // update state of button on show/hide
    div.on('hidden.bs.collapse', function () {
      showCodeText.text('Code');
    });
    div.on('show.bs.collapse', function () {
      showCodeText.text('Hide');
    });
  });

}
  1. In the following rmarkdown script, all three functions are read and included as is in the header, so that the js folder in not useful for the final document itself. When reading the js functions, I also added the option to show code blocks by default, but you can choose to hide them with hide.

rmarkdown code:

---
title: "Toggle R code"
author: "StatnMap"
date: '`r format(Sys.time(), "%d %B, %Y")`'
output:
  bookdown::html_document2:
    includes:
      in_header: header.html
  bookdown::gitbook:
    includes:
      in_header: header.html
---

Stackoverflow question
<https://mcmap.net/q/336678/-code-folding-in-bookdown>


```{r setup, include=FALSE}
# Add a common class name for every chunks
knitr::opts_chunk$set(
  echo = TRUE)
```
```{r htmlTemp3, echo=FALSE, eval=TRUE}
codejs <- readr::read_lines("js/codefolding.js")
collapsejs <- readr::read_lines("js/collapse.js")
transitionjs <- readr::read_lines("js/transition.js")

htmlhead <- 
  paste('
<script>',
paste(transitionjs, collapse = "\n"),
'</script>
<script>',
paste(collapsejs, collapse = "\n"),
'</script>
<script>',
paste(codejs, collapse = "\n"),
'</script>
<style type="text/css">
.code-folding-btn { margin-bottom: 4px; }
.row { display: flex; }
.collapse { display: none; }
.in { display:block }
</style>
<script>
$(document).ready(function () {
  window.initializeCodeFolding("show" === "show");
});
</script>
', sep = "\n")

readr::write_lines(htmlhead, path = "header.html")
```

## R Markdown

This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see <http://rmarkdown.rstudio.com>.

When you click the **Knit** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:

```{r cars}
summary(cars)
```

```{r plot}
plot(cars)
```

This script shows the buttons in the Rstudio browser but does not work well. However, this is ok with firefox.
You'll see that there is a little css in this code, but of course you can modify the position and color and whatever you want on these buttons with some more css.

Edit: Combine Global and local buttons

Edit 2017-11-13: Global code-folding button well integrated with individual bloc buttons. Function toggle_R is finally not necessary, but you need to get function dropdown.js in bootstrap.

Global button is called directly in the code chunk when calling js files:

```{r htmlTemp3, echo=FALSE, eval=TRUE}
codejs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/codefolding.js")
collapsejs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/collapse.js")
transitionjs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/transition.js")
dropdownjs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/dropdown.js")

htmlhead <- c(
  paste('
<script>',
paste(transitionjs, collapse = "\n"),
'</script>
<script>',
paste(collapsejs, collapse = "\n"),
'</script>
<script>',
paste(codejs, collapse = "\n"),
'</script>
<script>',
paste(dropdownjs, collapse = "\n"),
'</script>
<style type="text/css">
.code-folding-btn { margin-bottom: 4px; }
.row { display: flex; }
.collapse { display: none; }
.in { display:block }
.pull-right > .dropdown-menu {
    right: 0;
    left: auto;
}
.open > .dropdown-menu {
    display: block;
}
.dropdown-menu {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 1000;
    display: none;
    float: left;
    min-width: 160px;
    padding: 5px 0;
    margin: 2px 0 0;
    font-size: 14px;
    text-align: left;
    list-style: none;
    background-color: #fff;
    -webkit-background-clip: padding-box;
    background-clip: padding-box;
    border: 1px solid #ccc;
    border: 1px solid rgba(0,0,0,.15);
    border-radius: 4px;
    -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
    box-shadow: 0 6px 12px rgba(0,0,0,.175);
}
</style>
<script>
$(document).ready(function () {
  window.initializeCodeFolding("show" === "show");
});
</script>
', sep = "\n"),
  paste0('
<script>
document.write(\'<div class="btn-group pull-right" style="position: absolute; top: 20%; right: 2%; z-index: 200"><button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-_extension-text-contrast=""><span>Code</span> <span class="caret"></span></button><ul class="dropdown-menu" style="min-width: 50px;"><li><a id="rmd-show-all-code" href="#">Show All Code</a></li><li><a id="rmd-hide-all-code" href="#">Hide All Code</a></li></ul></div>\')
</script>
')
)

readr::write_lines(htmlhead, path = "/mnt/Data/autoentrepreneur/header.html")
```

The new global button shows a dropdown menu to choose between "show all code" or "hide all code". Using window.initializeCodeFolding("show" === "show") all codes are shown by default, whereas using window.initializeCodeFolding("show" === "hide"), all codes are hidden by default.

Smukler answered 4/8, 2017 at 8:13 Comment(13)
Thanks! This adds a button on each page, how would I add a button for each chunk, like in rmarkdown?Bifrost
Sorry, I am currently trying to use this answer but without success for the moment: #37944697. This may be combined with this one for including css class in the chunks: #37944697. If I succeed, I will update my answer.Developing
I added the script in the current "not working" stage, maybe somebody can help see what is the problem.Developing
Ok, now I found the solution to show/hide each chunk of code. You can then use your own css to show nice buttons.Developing
I've tried the show/hide for each chunk of code solution, but it seems to create a new button every time there is a blank line within a code chunk. Also, when I click to hide it, the chunk (or portion of chunk) is reduced to a single line with a vertical scroll bar, not hidden.Bifrost
The vertical scroll bar was because the div was not really hidden but just reduced to 0. I modified the css for .collapse and .in for that. However, I do not see problems with blank lines. I tried with gitbook and html_document2. What do you mean ?Developing
Thanks @yihui for the bonus. To celebrate that, I updated the code for the same global button as in classical rmarkdown html. Much nicer. I also implemented it with blogdown on my website...Developing
Is there a way to modify the javascript for the Global option to initially have all the code folded (i.e. not showing)?Quartan
Yes it is possible, you can use window.initializeCodeFolding("show" === "hide"). Also I recommend reading the complete blog post about it : statnmap.com/…Developing
Hi @SébastienRochette, I've been trying to get the hide-all button to stay fixed next to the h1 title (so when I scroll it does stay in one place like when using html_document). I've tried playing with the CSS of the button itself but it always moves with the scroll. Then I tried to add it manually after the window.initializeCodeFolding is run, putting it into a string var str = <div class="btn-group... and adding it like this: $( '[class^="section level1"]' ).prepend( str ); but the button loses all extra CSS+functionality I have put before! Do you maybe know any quick fix?Squeaky
position: fixed with top: some_value and right: some_value for class code-folding-btn should be enough in cssDeveloping
for bs4_book I had to remove the general - multi - buttons. Hope someone comes with a solution for that.More
Anyone got this to work in a bs4_book? It just reopens the code chunks when a button is clicked.Teofilateosinte
S
5

I made the R package rtemps which includes a ready-to-use bookdown template with code-folding buttons among others (largely based on Sébastien Rochette's answer/post). Check it here!

Squeaky answered 9/3, 2020 at 13:6 Comment(1)
This was really great, thanks. I love way that the code panels open and close smoothly.Aldebaran
C
2

I wrote a filter for pandoc that:

  • wraps all code blocks in HTML5 <details> tags
  • adds a local button to fold/unfold code
  • button text toggles between "Show code" and "Hide code" (feel free to customize) via onclick javascript event

Filter can be found here. Needs python distribution with panflute installed to run.

Add to bookdown via pandoc_args: ["-F", "path/to/collapse_code.py"]

Contemplation answered 23/10, 2019 at 21:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.