Tabulator rendering issue using responsive layout - javascript

ISSUE:
Everything pretty much works if I stick to layout: "fitColumns" anything else I try seems to result in rendering (if that is the correct term) issues. I am not using a framework (a bridge too far in the timeframe). When the table is displayed after the page is fully loaded, regardless of the layout I choose - it always starts off displaying as it would for "fitColumns". If I set it to "fitDataFill" for example, it loads and displays as for "fitColumns". When I click to another tab and back again
it then displays the data as it should for fitDataFill.
REVISED:
Rough order of steps:
Load a specific file which contains meta data about the rest of the files to load into tables, the names of those tables, the columns in each table and the text to display as float over help on the column headers
Load the rest of the data and build the table config object which contains the meta data
Add a table specific div for each table and append to the csv-tab-buttons div
Build the table on the newly created div
Add the button which will toggle which table gets display via the css active class and the setTab function
The setTab function includes two redraws (to be sure, to be sure).
If you see code that resembles something you wrote here then thanks to you, much of what I wrote I gleaned from others.
Tabulator version is 4.8
Code is created using Visual Studio Code using the Live Server extension to reload the page after every save
Browser is Chrome Version 85.0.4183.121 (Official Build) (64-bit)
I rewrote based on the suggestions of #mirza to ensure the data is read in entirely before the tables are built and each table is stored independently of the others so I don't pass the same table to setTab every time
fitColumns works but does not allow me to resize columns individually (which may be by design)
fitData seems to work ok
fitDataFill and fitDataStretch do not work on the first render, or subsequent renders of the same table until I click away and back again
I have attempted to follow the logic in the tabulator.js via the debugger and although I can see what is happening, there is way too much going on in there for me to grasp where the issue might be
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<!-- CSS -->
<link rel="stylesheet" href="./scripts/dist/css/tabulator.min.css">
<link rel="stylesheet" href="./styles/style.css">
<!-- Scripts -->
<script type="text/javascript" src="./scripts/dist/js/tabulator.min.js"></script>
<script type="text/javascript" src="./scripts/dist/js/papaparse.min.js"></script>
<title>My Team</title>
</head>
<body>
<!-- Wrapper -->
<div class="wrapper">
<section class="container">
<!-- Header -->
<header id="header" class="header">
<h1>Reporting</h1>
</header>
<!-- Tabs -->
<div id="csv-tab-buttons" class="tab">
</div>
<!-- Tables on each tab -->
<div id="csv-tabs">
</div>
<!-- Footer -->
<footer class="footer">
<p>My Team © 2020</p>
</footer>
</section>
</div><!-- Wrapper Ends-->
<script src="./scripts/dist/js/miscsv.min.js"></script>
</body>
</html>
REVISED Javascript:
//*******************************************************************************************************
// Global variables
//*******************************************************************************************************
var file = 'DS.PPTE.DB2.VIARACF.VARLEGND.CSV'
var tables = []
var tableDivs = []
var tabConfig = {}
//*******************************************************************************************************
// Global functions
//*******************************************************************************************************
let onlyUnique = (value, index, self) => {
return self.indexOf(value) === index
}
//*******************************************************************************************************
// Async functions
//*******************************************************************************************************
// Set the tab to whichever button was clicked
async function activateTab(target) {
// hides all tabs
document.querySelectorAll(".tabcontent").forEach(tabContent => tabContent.style.display = "none");
// Remove the active class from all tab links
document.querySelectorAll('.tablinks').forEach(tabLink => tabLink.className.replace("active", ""));
// Remove the active class from the active tab
document.querySelectorAll(".active").forEach(activeTab => activeTab.classList.remove("active"))
// Activate the selected tab
document.querySelector(`#${target.textContent}`).style.display = "block"
target.classList.add("active");
}
async function setTab(target) {
console.log("Activate the tab")
await activateTab(target)
console.log("Redraw the table")
// Redraw the table
tableDivs[`${target.textContent}`].redraw(true);
}
// Read a CSV file
const readCSV = async (file) => {
return new Promise(resolve => {
Papa.parse(`data/${file}`, {
header: true,
download: true,
skipEmptyLines: true,
complete: results => {
console.log(`${file} loaded - ${results.data.length} records.`)
resolve(results)
}
})
})
}
// Get all the data first
async function getData() {
// Read the meta data file with the data and table config
let parseMeta = await readCSV(file)
tabConfig = {
// Get the names of the tables to present
tabs: parseMeta.data.map((data) => data['TABLE-NAME']).filter(onlyUnique).sort(),
// Find the file name for each table
files: parseMeta.data.map((data) => `${data['TABLE-NAME']}-${data['CSV-FILE-NAME']}`).filter(onlyUnique)
.map((entry) => {
let tmpEntry = entry.split('-')
return { table: `${tmpEntry[0]}`, file: `${tmpEntry[1]}` }
}),
// Save the float over help for each column by table name
help: parseMeta.data.map((data) => {
return { key: `${data['TABLE-NAME']}-${data['VARIABLE']}`, helpText: data['VAR-DESCRIPTION'] != '' ? data['VAR-DESCRIPTION'] : data['VARIABLE'] }
}),
data: tables,
divs: tableDivs,
}
// Read in the files which contain the table data
for (const tabName of tabConfig.tabs) {
let file = tabConfig.files.filter(entry => entry.table == tabName)[0].file
tables[tabName] = await readCSV(file)
tableDivs[tabName] = `csv-table-${tabName}`
}
}
// Master function to do everything in the right order
async function doAll() {
// Get all the data and build the table config
await getData()
// Store the buttons and tabs anchor divs
let buttonsDiv = document.getElementById("csv-tab-buttons")
let tabsDiv = document.getElementById("csv-tabs")
// Add the buttons and tables
for ([idx, tabName] of tabConfig.tabs.entries()) {
// Add tabs to hold the tables to the page
const elemTabDiv = document.createElement('div')
const elemTableDiv = document.createElement('div')
elemTabDiv.id = tabName
elemTabDiv.className = "tabcontent"
elemTableDiv.id = `csv-table-${tabName}`
elemTableDiv.className = "table"
elemTabDiv.appendChild(elemTableDiv)
tabsDiv.appendChild(elemTabDiv)
// Define header context menu
let headerMenu = [
{
label:"Hide Column",
action:function(e, column){
column.hide()
},
},
]
// Create the table
tableDivs[tabName] = new Tabulator(`#csv-table-${tabName}`, {
data:tabConfig.data[tabName].data,
layout:"fitData",
responsiveLayout:"collapse",
tooltips:true,
pagination:"local",
paginationSize:20,
resizableColumns:true,
movableColumns:true,
resizableRows:true,
autoColumns: true,
autoColumnsDefinitions: function(definitions) {
definitions.forEach((column) => {
let helpText = tabConfig.help.find(key => key.key === `${tabName}-${column.field}`).help
// Add float over help based on column name
column.headerTooltip = helpText
column.headerMenu = headerMenu
column.headerFilter = true
column.headerSort = true
column.headerFilterLiveFilter = false
})
return definitions
},
renderStarted:function(){
console.log("Render started")
},
renderComplete:function(){
console.log("Render complete")
},
})
// Add tab buttons to page
const elemTabButton = document.createElement('button')
elemTabButton.id = `button-${tabName}`
if ( idx == 0 ) {
elemTabButton.className = "tablinks active"
} else {
elemTabButton.className = "tablinks"
}
elemTabButton.onclick = function() { setTab(this) }
elemTabButton.textContent = tabName
buttonsDiv.appendChild(elemTabButton)
}
document.querySelector(".active").click();
}
doAll()
CSS:
:root {
--shadow: 0 1px 5px rgba(104, 104, 104, 0.8);
--raisin: #262730;
--vermillion: #d33f49;
--cadet: #576c75;
--navyboy: #8db2c2;
--space-cadet: #363457;
--baby-powder: #f0f4ef;
--ice: rgb(245, 247, 253);
}
html {
box-sizing: border-box;
font-family: Arial, Helvetica, sans-serif;
color: var(--dark);
}
body {
background: var(--baby-powder);
margin: 10px 10px;
line-height: 1.4;
}
/* .wrapper {
display: grid;
grid-gap: 10px;
} */
.container {
display: grid;
grid-gap: 10px;
grid-template-areas:
'header'
'csv-tab-buttons'
'footer';
margin: auto;
width: 98%;
overflow: auto;
padding: 1rem 1rem;
}
header {
background: var(--raisin);
color: var(--vermillion);
font-size: 150%;
line-height: 1;
padding: 0.1rem;
text-align: center;
box-shadow: var(--shadow);
}
.tab {
background-color: var(--ice);
box-shadow: var(--shadow);
}
/* Style the buttons that are used to open the tab content */
.tab button {
background-color: inherit;
/* float: left; */
border: none;
outline: none;
cursor: pointer;
/* padding: 14px 16px; */
padding: 1rem 1.1rem;
transition: 0.3s;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: var(--cadet);
color: var(--ice);
}
/* Create an active/current tablink class */
.tab button.active {
background-color: var(--vermillion);
color: var(--ice);
}
/* Style the tab content */
.tabcontent {
/* overflow: hidden; */
display: none;
/* padding: 6px 12px; */
/* border-top: none; */
}
/* Override Tabulator header background */
.tabulator-col-content {
background-color: var(--ice);
}
.table {
overflow: hidden;
box-shadow: var(--shadow);
}
.footer {
background: var(--cadet);
color: white;
padding: 0rem 2rem;
line-height: 1;
box-shadow: var(--shadow);
}
EDIT:
I made some changes to the order of everything so I could keep the table object in scope when the setTab function is called and I could issue the redraw when the tab button is clicked. The first time I click through every tab it appears to be filling to the width of the data. When I click through each tab again it is properly wrapping columns that would be off screen to the next line. I put multiple table redraws in the setTab routine and it makes no difference to the rendering of the table. It does however change the attributes of the table in some way. I observed in the debugger that tableWidth changed from 0 prior to the first redraw to 2734, the to 2786 after the second redraw and it stayed at the value. If I click away and back again it wraps as expected.

Tabulator table does not properly render if the element is not visible when creating the table.
To properly render the table, you have to redraw the table when the element is visible.
See Here
From the website:
If the size of the element containing the Tabulator changes (and you are not able to use the in built auto-resize functionality) or you create a table before its containing element is visible, it will necessary to redraw the table to make sure the rows and columns render correctly.
You can redraw the table by
table.redraw();
Or
table.redraw(true); //trigger full rerender including all data and rows

Related

HTML\CSS\JavaScript: How can I build a menu dynamically?

I am using the W3.CSS animated drop-down (https://www.w3schools.com/w3css/w3css_dropdowns.asp).
But I cannot figure out how can I populate the menu items dynamically according to a list of item names.
I guess some Javascript should be involved here, so I try in this forum 🙂
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<div class="w3-dropdown-click">
<button onclick="showMenu()" class="w3-button">Team1</button>
<div id="Demo" class="w3-dropdown-content w3-bar-block w3-animate-zoom">
Link 1
Link 2
Link 3
</div>
</div>
<script>
async function getTeams() {
try{
(async () => {
const response = await fetch('http://localhost:8088/teams')
var teamsArrObj = await response.json()
console.log(teamsArrObj);
return teamsArrObj;
})()
}catch{
console.log("error");
}
}
function showMenu() {
var x = document.getElementById("Demo");
if (x.className.indexOf("w3-show") == -1) {
x.className += " w3-show";
} else {
x.className = x.className.replace(" w3-show", "");
}
}
</script>
Thanks!
Assuming that you're using a regular js array of text, you can loop over that array and insertAdjacentHTML into the dropdown.
for (let text of /*insert array name here*/) {
document.getElementById('Demo').insertAdjacentHTML('beforeend', `${text}`
Here we use the modern for...of loop, and do insertAdjacentHTML. Check the MDN docs if you are not familiar with the function. We also use the ES6 template strings to insert.
Because W3 Schools has so much wrong in their demo that you've linked to, I've recreated their example (the right way) and shown how the CSS would be done (instead of relying on their pre-made CSS that you don't get to see).
You'll note that the actual hiding/showing of the menu is accomplished with a single line of JavaScript instead of all that outdated string related stuff that W3 Schools uses.
And, you'll see how you can dynamically load the menu items from an array of object data.
<!DOCTYPE html>
<html>
<head>
<title>W3.CSS</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
button {
background-color:black;
color:white;
padding:5px;
border:none;
font-size:1.1em; /* 10% bigger than normal */
}
#Demo {
position:relative;
box-shadow:0 0 20px #808080;
padding:6px;
margin-top:3px;
height:auto;
width:75px;
transform-origin: center; /* Make effect work from center outward */
transition:all .5s; /* Make changes to all CSS happen over 1 second */
}
/* The style of the menu when it's hidden */
#Demo.hidden {
transform:scale(0,0); /* Scale it to 0 by 0 pixels */
}
.menuItem {
display:block; /* Each item on its own line */
text-decoration:none; /* no underline on the links */
padding:3px;
font-family:Arial;
}
.menuItem:hover {
background-color:#e0e0e0;
}
</style>
</head>
<body>
<div>
<h1>Animated Dropdown</h1>
<div class="w3-dropdown-click">
<button>Click me</button>
<div id="Demo" class="hidden"></div>
</div>
</div>
<script>
let items = [
{ text: "Link 1", path: "https://example.com"},
{ text: "Link 2", path: "https://hbo.com"},
{ text: "Link 3", path: "https://cbs.com"},
];
const demo = document.getElementById("Demo");
// Loop over the items array
items.forEach(function(item){
// Create a new anchor element and configure it:
let link = document.createElement("a");
link.classList.add("menuItem");
link.href = item.path;
link.textContent = item.text;
// Append the new link to the menu
demo.appendChild(link);
});
document.querySelector("button").addEventListener("click", function(){
demo.classList.toggle("hidden"); // Toggle the display of the menu
});
</script>
</body>
</html>

Webcomponents is re-initializing every time while working with Vue js

I have created a webcomponent for a generic input boxes that i needed across multiple projects.
the design functionality remains same only i have to use switch themes on each projects.so i have decided to go on with webcomponents.One of the projects is based on Vue Js.In Vue js the DOM content is re-rendered while each update for enabling reactivity. That re-rendering of vue template is reinitializing my custom webcomponent which will result in loosing all my configurations i have assigned to the component using setters.
I know the below solutions. but i wanted to use a setter method.
pass data as Attributes
Event based passing of configurations.
Using Vue-directives.
using v-show instead of v-if
-- Above three solutions doesn't really match with what i am trying to create.
I have created a sample project in jsfiddle to display my issue.
Each time i an unchecking and checking the checkbox new instances of my component is creating. which causes loosing the theme i have selected. (please check he active boxes count)
For this particular example i want blue theme to be displayed. but it keep changing to red
JSFiddle direct Link
class InputBox extends HTMLElement {
constructor() {
super();
window.activeBoxes ? window.activeBoxes++ : window.activeBoxes = 1;
var shadow = this.attachShadow({
mode: 'open'
});
var template = `
<style>
.blue#iElem {
background: #00f !important;
color: #fff !important;
}
.green#iElem {
background: #0f0 !important;
color: #f00 !important;
}
#iElem {
background: #f00;
padding: 13px;
border-radius: 10px;
color: yellow;
border: 0;
outline: 0;
box-shadow: 0px 0px 14px -3px #000;
}
</style>
<input id="iElem" autocomplete="off" autocorrect="off" spellcheck="false" type="text" />
`;
shadow.innerHTML = template;
this._theme = 'red';
this.changeTheme = function(){
this.shadowRoot.querySelector('#iElem').className = '';
this.shadowRoot.querySelector('#iElem').classList.add(this._theme);
}
}
connectedCallback() {
this.changeTheme();
}
set theme(val){
this._theme = val;
this.changeTheme();
}
}
window.customElements.define('search-bar', InputBox);
<!DOCTYPE html>
<html>
<head>
<title>Wrapper Component</title>
<script src="https://unpkg.com/vue"></script>
<style>
html,
body {
font: 13px/18px sans-serif;
}
select {
min-width: 300px;
}
search-bar {
top: 100px;
position: absolute;
left: 300px;
}
input {
min-width: 20px;
padding: 25px;
top: 100px;
position: absolute;
}
</style>
</head>
<body>
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<div class='parent' contentEditable='true' v-if='visible'>
<search-bar ref='iBox'></search-bar>
</div>
<input type='checkbox' v-model='visible'>
</div>
</script>
<script type="text/x-template" id="select2-template">
<select>
<slot></slot>
</select>
</script>
<script>
var vm = new Vue({
el: "#el",
template: "#demo-template",
data: {
visible: true,
},
mounted(){
let self = this
setTimeout(()=>{
self.$refs.iBox.theme = 'blue';
} , 0)
}
});
</script>
</body>
</html>
<div class='parent' contentEditable='true' v-if='visible'>
<search-bar ref='iBox'></search-bar>
</div>
<input type='checkbox' v-model='visible'>
Vue's v-if will add/remove the whole DIV from the DOM
So <search-bar> is also added/removed on every checkbox click
If you want a state for <search-bar> you have to save it someplace outside the <search-bar> component:
JavaScript variable
localStorage
.getRootnode().host
CSS Properties I would go with this one, as they trickle into shadowDOM
...
...
Or change your checkbox code to not use v-if but hide the <div> with any CSS:
display: none
visibility: hidden
opacity: 0
move to off screen location
height: 0
...
and/or...
Managing multiple screen elements with Stylesheets
You can easily toggle styling using <style> elements:
<style id="SearchBox" onload="this.disabled=true">
... lots of CSS
... even more CSS
... and more CSS
</style>
The onload event makes sure the <style> is not applied on page load.
activate all CSS styles:
(this.shadowRoot || document).getElementById("SearchBox").disabled = false
remove all CSS styles:
(this.shadowRoot || document).getElementById("SearchBox").disabled = true
You do need CSS Properties for this to work in combo with shadowDOM Elements.
I prefer native over Frameworks. <style v-if='visible'/> will work.. by brutally removing/adding the stylesheet.

Place a button and its div with one command

Currently, I have a button class which lets me place a clickable button inside a sentence, and a div class which lets me add content to the button which I placed at the end of the paragraph containing the sentence.
This is an example of how I use them
Try to click <button class="col">THIS</button> and see what happens.
<div class="con">nice!</div>
Did you try?
When this text is displayed on the page, the two sentences are placed inside two different paragraphs, so the div object is placed between them.
Here is a snippet with the css classes and the javascript.
( function() {
coll = document.getElementsByClassName("col");
conn = document.getElementsByClassName("con");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].setAttribute('data-id', 'con' + i);
conn[i].setAttribute('id', 'con' + i);
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = document.getElementById(this.getAttribute('data-id'));
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
} )();
.col {
cursor: help;
border-radius: 0;
border: none;
outline: none;
background: none;
padding: 0;
font-size: 1em;
color: red;
}
.con {
padding: 0 1em;
max-height: 0;
overflow: hidden;
transition: .3s ease;
background-color: yellow;
}
Try to click <button class="col">THIS</button> and see what happens.
<div class="con">nice!</div>
Did you try?
I wonder if it is possible to implement a shortcut to place the two objects with one command, that is to obtain the previous example by using something like this
Try to click [[THIS|nice!]] and see what happens.
Did you try?
What I mean is that the command [[THIS|nice!]] should place the object <button class="col">THIS</button> in the same position and the object <div class="con">nice!</div> at the end of the paragraph containing the command.
Is it possible to implement such a command (or a similar one)?
EDIT
I forgot to say that the content of the button, ie what is written inside the div, should also be possible to be a wordpress shortcode, which is a shortcut/macro for a longer piece of code or text.
Using jQuery, closest() find the nearest <p> element and add <div class="con">nice!</div> after <p> element. To toggle you can use class active and add or remove .con element.
$('.col').click(function(){
let traget = $(this).closest('p');
if(traget.hasClass('active')) {
traget.removeClass('active');
traget.next('.con').remove();
} else {
traget.addClass('active');
traget.after(`<div class="con">${$(this).data('message')}</div>`);
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>Try to click <button class="col" data-message="Hello">THIS</button> and see what happens.</p>
<p>Did you try?</p>
You usually dont use div to type text. you use it to define areas or group items. you could obtain what youre asking for in a 1 sentence like this:
html
<h1> some random text <a class="btnID">button</> some more text<h1>
css
.btnID {
color: red;
}

How to change one HTML file to another without changing link?

I have an html file(a webpage). I want when I press a button on it, the page should be replaced by another html file (with its own css, javascript functions etc) without being redirected to some other link.
For example, if link in first case is abc.com/def it should be same after too.
Using this code, I am able to change webpage look, but not getting how to change look (and also manage to load css and js functions) from another file.
<script type="text/javascript">
document.body.addEventListener('click',function(){
document.write("THIS IS NEW TEXT")
},
false);
</script>
You need to look into frameworks like AngularJS, Specially Routing of Angular. They provide such features built-in for web applications. However, you can do it the hard way, using javascript, like you are doing it right now. Add CSS and change whole body HTML using javascript if you don't want to learn any new framework or libraries.
You want to use PJAX,
Here's a link for an example.
As discuss by others, you should use a Framework to do this..
But this is a complete solution you can inspire of:
let layouts = {}
let current = null
// Display the new page by deleting current, and replacing by the good one
let displayLayout = (layout_id) => {
let parentNode = current.parentNode
parentNode.removeChild(current)
current = layouts[layout_id]
parentNode.appendChild(current)
loadEvents(current)
}
// Load event for HTML DOM you just created
let loadEvents = (layout_el) => {
Array.from(layout_el.getElementsByClassName('go-to-layout')).forEach(el => {
el.addEventListener('click', e => {
e.preventDefault()
displayLayout(e.currentTarget.dataset.layout)
})
})
}
// On init I get all the existing layout, but you can build you own dictionary an other way.
Array.from(document.getElementsByClassName('layout')).forEach(l => {
layouts[l.id] = l
if (l.classList.contains('active')) {
loadEvents(l)
current = l
}
else {
l.parentNode.removeChild(l);
}
})
/* Global CSS */
body, html, .layout {
height: 100%;
margin: 0;
}
* {
color: #FFF
}
.layout {
display: flex;
}
.nav, .page {
}
.nav {
width: 150px;
background: #555;
}
/* Special CSS for one layout */
#layout1 {
background: red;
}
#layout2 {
background: blue;
}
<div id="layout1" class="layout active">
<div class="nav">
Page 2
</div>
<div class="page">
This is page 1
</div>
</div>
<div id="layout2" class="layout">
<div class="nav">
Page 1
</div>
<div class="page">
This is page 2
</div>
<style>.page { font-size: 2em }</style>
</div>

Code folding in bookdown

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.
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://stackoverflow.com/questions/45360998/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:
Create a js folder in the same directory than your Rmd file
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
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');
});
});
}
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://stackoverflow.com/questions/45360998/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.
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!
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"]

Categories