Make table data collapsible - javascript

I am trying to make each heading part which is a table collapsible. The user should be able to click on the heading and view the table and click again to hide it. Something as simple as possible. I found something here: https://www.w3schools.com/howto/howto_js_collapsible.asp that seems like a lot of coding for such a simple thing. Is there a simple way to do it in HTML? I am using it in Thymeleaf as part of spring boot, so if it's done in HTML it should be easily doable in Thymeleaf too. Following is the sample HTML that I am using.
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h1> Concepts only in L1</h1>
<table>
<tr>
<th>missing-con</th>
<th>parent-con</th>
</tr>
<tr>
<td>{missing-con}</td>
<td>{parent-con}</td>
</tr>
</table>
<h1> Concepts only in M1</h1>
<table>
<tr>
<th>missing-con</th>
<th>parent-con</th>
</tr>
<td>{missing-con}</td>
<td>{parent-con}</td>
<td>{missing-con}</td>
<td>{parent-con}</td>
</table>
</body>
</html>
And I am looking to collapse each table. Any suggestions?

Here is one aproach you can addapt your way. I would advise you to add an ID/class name to the table element so you can reference it better. In this example I'm considering the first table on the document. You can also use getElementById or getelementByClassName
var table = document.getElementsByTagName("table")[0];
var th = table.getElementsByTagName("th");
for (var i = 0; i < th.length; i++) {
th[i].onclick = function() {
var td = this.parentNode.parentNode.getElementsByTagName("td");
for (var j = 0; j < td.length; j++) {
if (td[j].style.display == "none") {
td[j].style.display = "";
} else {
td[j].style.display = "none";
}
}
}
}

Related

Return Sheets data based on URL Parameter in Google Apps Script / Web App

This is a piece of a larger project. Essentially, I'm going to have a link with a parameter ("formid" in this case) that I need to use to retrieve the correct row number from a Google sheets table. The code below works the way I want it to with the exception of the parameters not being used and the rows being retrieved are hard coded. I'd like to change this so the getBody row corresponds to formid number (ie.: if formid=4 then the 4th row would be displayed. Column positions can be hardcoded, I only need the one variable to be used.
Index.html:
<!DOCTYPE html>
<html>
<head>
</head>
<style>
</style>
<body>
<form>
<h1><center>Sheet</center></h1>
</form>
<script>
google.script.url.getLocation(function(location) {
document.getElementById("formid").value = location.parameters.formid[0];
});
</script>
<div>
<table>
<thead>
<? const headers = getHeaders();
for (let i = 0; i < headers.length; i++) { ?>
<tr>
<? for (let j = 0; j < headers[0].length; j++) { ?>
<th><?= headers[i][j] ?></th>
<? } ?>
<? } ?>
</thead>
<tbody>
<? const body = getBody();
for (let k = 0; k < body.length; k++) { ?>
<tr>
<? for (let l = 0; l < body[0].length; l++) { ?>
<td><?= body[k][l] ?></td>
<? } ?>
<? } ?>
</tbody>
</table>
</div>
</body>
</html>
Code.gs:
function doGet() {
return HtmlService
.createTemplateFromFile('Index')
.evaluate();
}
function getHeaders() {
var url = "https://docs.google.com/spreadsheets/d/1NnG5lEKowlU6i2ZzkyCD1bjFtFGcgaODKZxvG179XfM/";
const sheet = SpreadsheetApp.openByUrl(url).getSheetByName("Sheet1");
return sheet.getRange(1, 1, 1, sheet.getLastColumn()).getDisplayValues();
}
function getBody() {
var url = "https://docs.google.com/spreadsheets/d/1NnG5lEKowlU6i2ZzkyCD1bjFtFGcgaODKZxvG179XfM/";
const sheet = SpreadsheetApp.openByUrl(url).getSheetByName("Sheet1");
const firstRow = 8;
const numRows = 1;
return sheet.getRange(firstRow, 1, numRows, sheet.getLastColumn()).getDisplayValues();
}
I've reviewed many related questions but either the solutions didn't seem to work or it wasn't clear what the full solution was. Perhaps I missed something.
I've tried inserting "formid" into the "return sheet.getRange()" but I keep getting an error that formid isn't an int.
I've made several attempts at this and the code above represents the closest and simplest script that has gotten me most of the way there.
I believe your goal is as follows.
You want to show only a row from the Spreadsheet by giving the row number using the query parameter like formid=5.
Modification points:
In your script, it seems that you are retrieving the query parameter on the HTML side. In this case, when the template is used, after the HTML is loaded, google.script.url.getLocation is run. So, in this answer, I would like to propose retrieving the query parameter on the Google Apps Script side.
In the current stage, when the loop process is included in the HTML template, the process cost becomes high. Ref (Author: me)
When these points are reflected in your script, how about the following modification?
HTML side:
In your HTML, document.getElementById("formid") is not found. So, in this modification, only HTML template without the script is used.
<!DOCTYPE html>
<html>
<head>
</head>
<style>
</style>
<body>
<form>
<h1>
<center>Sheet</center>
</h1>
</form>
<div>
<table>
<?!= table ?>
</table>
</div>
</body>
</html>
Google Apps Script side:
In this modification, only doGet function is used as follows.
function doGet(e) {
const url = "https://docs.google.com/spreadsheets/d/1NnG5lEKowlU6i2ZzkyCD1bjFtFGcgaODKZxvG179XfM/";
const { formid } = e.parameter;
const sheet = SpreadsheetApp.openByUrl(url).getSheetByName("Sheet1");
const [header, ...values] = sheet.getDataRange().getValues();
const h = "<thead><tr>" + header.map(e => `<th>${e}</th>`).join("") + "</tr></thead>";
const b = values[formid - 1] ? "<tbody><tr>" + values[formid - 1].map(e => `<td>${e}</td>`).join("") + "</tr></tbody>" : "";
const html = HtmlService.createTemplateFromFile('i16');
html.table = h + b;
return html.evaluate();
}
In this modification, for example, when a Web Apps URL like https://script.google.com/macros/s/###/dev?formid=5 including the query parameter is used, formid=5 is retrieved in the doGet function, and only the row of formid=5 is shown.
Note:
When you modified the Google Apps Script of Web Apps, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in my report "Redeploying Web Apps without Changing URL of Web Apps for new IDE (Author: me)".

Can't append table to div

I created a table using d3.js library,
but when I try to append the table to a div, it gives an error?
code:
<head>
<script src="../../d3.min.js"></script>
</head>
<body>
<div id="main">
Hi
</div>
<script>
const table = d3.create("table");
const tbody = table.append("tbody");
var i,j,row;
for(i=0;i<5;i++){
row =tbody.append("tr");
for(j=0;j<3;j++){
row.append("td").text(`${i},${j}`);
}
}
console.log(typeof(table));
console.log(table);
node =table.node();
console.log(typeof(node));
console.log(node);
d3.select("#main").append(node);
</script>
</body>
</html>
but I get an error:
although my code similar to what is in this tutorial
A tutorial on d3js
Observable tutorials are meant to create Observable notebooks. There are several small differences between Observable and a regular D3 running in a browser.
That being said, the only problem in your approach is that append requires either a string with the tag name or the element. If you have a string, just use it as append("foo"). However, if you have the element to be appended (in your case, table.node()), you have to return it from a function.
So, instead of:
d3.select("#main").append(node);
It has to be:
d3.select("#main").append(() => node);
Here is your code with that change only:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="main">
Hi
</div>
<script>
const table = d3.create("table");
const tbody = table.append("tbody");
var i, j, row;
for (i = 0; i < 5; i++) {
row = tbody.append("tr");
for (j = 0; j < 3; j++) {
row.append("td").text(`${i},${j}`);
}
}
node = table.node();
d3.select("#main").append(() => node);
</script>
Finally, if you are writing regular scripts for a browser, just ditch this d3.create() followed by append(() => selection.node()). Use a simple tag string instead.

Get all html tags with Javascript

Does anybody knows how can I get all the HTML tags that exist in a page?
I need to get only the tags without their IDs or other attributes, and create a kind of tree-structure of them.
Prefer to do that with Javascript or JQuery.
For example, This HTML code:
<html>
<head>
<title>
Example Page
</title>
</head>
< body>
<h1 style="somestyle">
Blabla
</h1>
<div id="id">
<table id="formid">
<tr>
<td>
</td>
</tr>
</table>
</div>
</body>
</html>
should return return:
html
head
title
body
h1
div
table
tr
td
You can pass a * to getElementsByTagName() so that it will return all elements in a page:
var all = document.getElementsByTagName("*");
for (var i=0, max=all.length; i < max; i++) {
// Do something with the element here
}
Its a very simple piece of Javascript
document.querySelectorAll('*')
Try it out in the console log and it will show you all the tags in the document.
Another example is to getElementsByTagName
These do print out into an array, so you can then loop through the elements and doing different things on different elements.
Example:
var items = document.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
//do stuff
}
I did it with getElementsByTagName and .tagName for every value in the return array.

Efficient way of finding index of row?

I am getting the index of a row by doing this:
row.parent().children("tr").index(row)
Is there a more efficient way to find the index? I have hundreds of rows so it is killing my performance that I have to select all rows just to find the index.
How about row.prevAll().length?
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" charset="utf-8" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
console.log($("tr").index($("#my")));
})
</script>
</head>
<body>
<table border="0" cellspacing="5" cellpadding="5" id="tbl">
<tr><th>Header</th></tr>
<tr><td>Data</td></tr>
<tr id="my"><th>Header</th></tr>
<tr><th>Header</th></tr>
<tr><td>Data</td></tr>
</table>
</body>
</html>
Hope that helps. Cheers.
Id attribute is the most fastest way to parse any html. You could provide all your rows with an Id.
Although the index method will determine the index among of the siblings elements, which could be faster
row.parent("tr").index();
see this example http://jsfiddle.net/shNrS/
If you are getting the reference to the row somehow (click handler etc) than there is no additional overhead in looking up that element, just .index() it and profit (although watch out for multiple tbody elements which are valid but would add complexity to your script)
If you are indexing all tr elements at runtime, might as well cache it in jquery data for future use!
The fastest way here is probably using plain javascript:
function getRowIndex(elem) {
var index = 0;
while (elem = elem.previousSibling) {
if (elem.tagName == "TR") {
++index;
}
}
return(index);
}
Working demo: http://jsfiddle.net/jfriend00/y4anN/
If you had to do this repeatedly on a large table that wasn't changing dynamically, then you could just pre-number the rows once with a custom attribute and from then on, all you'd have to do it retrieve the custom attribute from any row.
You would pre-number all the rows with a custom attribute like this:
function numberRows(table) {
var rows = table.getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
rows[i].dataIndex = i;
}
}
And, then you can just obtain the index number from any given row like this:
row.dataIndex
Working demo: http://jsfiddle.net/jfriend00/CR2Wk/

How to do page numbering in header/footer htmls with wkhtmltopdf?

I'm developing an electronic invoicing system, and one of our features is generating PDFs of the invoices, and mailing them. We have multiple templates for invoices, and will create more later, so we decided to use HTML templates, generate HTML document, and then convert it to PDF. But we're facing a problem with wkhtmltopdf, that as far as I know (I've been Googleing for days to find the solution) we cannot simply both use HTML as header/footer, and show page numbers in them.
In a bug report (or such) ( http://code.google.com/p/wkhtmltopdf/issues/detail?id=140 ) I read that with JavaScript it is achievable this combo. But no other information on how to do it can be found on this page, or elsewhere.
It is, of course not so important to force using JavaScript, if with wkhtmltopdf some CSS magic could work, it would be just as awesome, as any other hackish solutions.
Thanks!
Actually it's much simpler than with the code snippet. You can add the following argument on the command line: --footer-center [page]/[topage].
Like richard mentioned, further variables are in the Footers and Headers section of the documentation.
Among a few other parameters, the page number and total page number are passed to the footer HTML as query params, as outlined in the official docs:
... the [page number] arguments are sent to the header/footer html documents in GET fashion.
Source: http://wkhtmltopdf.org/usage/wkhtmltopdf.txt
So the solution is to retrieve these parameters using a bit of JS and rendering them into the HTML template. Here is a complete working example of a footer HTML:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script>
function substitutePdfVariables() {
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
function substitute(name) {
var value = getParameterByName(name);
var elements = document.getElementsByClassName(name);
for (var i = 0; elements && i < elements.length; i++) {
elements[i].textContent = value;
}
}
['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection']
.forEach(function(param) {
substitute(param);
});
}
</script>
</head>
<body onload="substitutePdfVariables()">
<p>Page <span class="page"></span> of <span class="topage"></span></p>
</body>
</html>
substitutePdfVariables() is called in body onload. We then get each supported variable from the query string and replace the content in all elements with a matching class name.
To show the page number and total pages you can use this javascript snippet in your footer or header code:
var pdfInfo = {};
var x = document.location.search.substring(1).split('&');
for (var i in x) { var z = x[i].split('=',2); pdfInfo[z[0]] = unescape(z[1]); }
function getPdfInfo() {
var page = pdfInfo.page || 1;
var pageCount = pdfInfo.topage || 1;
document.getElementById('pdfkit_page_current').textContent = page;
document.getElementById('pdfkit_page_count').textContent = pageCount;
}
And call getPdfInfo with page onload
Of course pdfkit_page_current and pdfkit_page_count will be the two elements that show the numbers.
Snippet taken from here
From the wkhtmltopdf documentation (http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf-0.9.9-doc.html) under the heading "Footers and Headers" there is a code snippet to achieve page numbering:
<html><head><script>
function subst() {
var vars={};
var x=document.location.search.substring(1).split('&');
for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
for(var i in x) {
var y = document.getElementsByClassName(x[i]);
for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
}
}
</script></head><body style="border:0; margin: 0;" onload="subst()">
<table style="border-bottom: 1px solid black; width: 100%">
<tr>
<td class="section"></td>
<td style="text-align:right">
Page <span class="page"></span> of <span class="topage"></span>
</td>
</tr>
</table>
</body></html>
There are also more available variables which can be substituted other than page numbers for use in Headers/Footers.
Safe approach, even if you are using XHTML (for example, with thymeleaf). The only difference with other's solution is the use of // tags.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<script>
/*<![CDATA[*/
function subst() {
var vars = {};
var query_strings_from_url = document.location.search.substring(1).split('&');
for (var query_string in query_strings_from_url) {
if (query_strings_from_url.hasOwnProperty(query_string)) {
var temp_var = query_strings_from_url[query_string].split('=', 2);
vars[temp_var[0]] = decodeURI(temp_var[1]);
}
}
var css_selector_classes = ['page', 'topage'];
for (var css_class in css_selector_classes) {
if (css_selector_classes.hasOwnProperty(css_class)) {
var element = document.getElementsByClassName(css_selector_classes[css_class]);
for (var j = 0; j < element.length; ++j) {
element[j].textContent = vars[css_selector_classes[css_class]];
}
}
}
}
/*]]>*/
</script>
</head>
<body onload="subst()">
<div class="page-counter">Page <span class="page"></span> of <span class="topage"></span></div>
</body>
Last note: if using thymeleaf, replace <script> with <script th:inline="javascript">.
My example shows how to hide some text on a particular page, for this case it shows the text from page 2 onwards
<span id='pageNumber'>{#pageNum}</span>
<span id='pageNumber2' style="float:right; font-size: 10pt; font-family: 'Myriad ProM', MyriadPro;"><strong>${siniestro.numeroReclamo}</strong></span>
<script>
var elem = document.getElementById('pageNumber');
document.getElementById("pageNumber").style.display = "none";
if (parseInt(elem.innerHTML) <= 1) {
elem.style.display = 'none';
document.getElementById("pageNumber2").style.display = "none";
}
</script>
Right From the wkhtmltopdf Docs
Updated for 0.12.6.
Footers And Headers:
Headers and footers can be added to the
document by the --header-* and --footer* arguments respectively. In
header and footer text string supplied to e.g. --header-left, the
following variables will be substituted.
[page] Replaced by the number of the pages currently being printed
[frompage] Replaced by the number of the first page to be printed
[topage] Replaced by the number of the last page to be printed
[webpage] Replaced by the URL of the page being printed
[section] Replaced by the name of the current section
[subsection] Replaced by the name of the current subsection
[date] Replaced by the current date in system local format
[isodate] Replaced by the current date in ISO 8601 extended format
[time] Replaced by the current time in system local format
[title] Replaced by the title of the of the current page object
[doctitle] Replaced by the title of the output document
[sitepage] Replaced by the number of the page in the current site being converted
[sitepages] Replaced by the number of pages in the current site being converted
As an example specifying --header-right "Page [page] of [topage]", will result in the text "Page x of y" where x is the
number of the current page and y is the number of the last page, to
appear in the upper left corner in the document.
Headers and footers can also be supplied with HTML documents. As an
example one could specify --header-html header.html, and use the
following content in header.html:
<!DOCTYPE html>
<html>
<head><script>
function subst() {
var vars = {};
var query_strings_from_url = document.location.search.substring(1).split('&');
for (var query_string in query_strings_from_url) {
if (query_strings_from_url.hasOwnProperty(query_string)) {
var temp_var = query_strings_from_url[query_string].split('=', 2);
vars[temp_var[0]] = decodeURI(temp_var[1]);
}
}
var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
for (var css_class in css_selector_classes) {
if (css_selector_classes.hasOwnProperty(css_class)) {
var element = document.getElementsByClassName(css_selector_classes[css_class]);
for (var j = 0; j < element.length; ++j) {
element[j].textContent = vars[css_selector_classes[css_class]];
}
}
}
}
</script></head>
<body style="border:0; margin: 0;" onload="subst()">
<table style="border-bottom: 1px solid black; width: 100%">
<tr>
<td class="section"></td>
<td style="text-align:right">
Page <span class="page"></span> of <span class="topage"></span>
</td>
</tr>
</table>
</body>
</html>
ProTip
If you are not using certain information like the webpage, section, subsection, subsubsection, then you should remove them. We are generating fairly large PDFs and were running into a segmentation fault at ~1,000 pages.
After a thorough investigation, it came down to removing those unused variables. No we can generate 7,000+ page PDFs without seeing the Segmentation Fault.
I have not understood the command line en finally I find the solution to put this information directly in the controller without any JS en command line.
In my controller when I call the format.pdf I just put the line footer:
format.pdf do
render :pdf => "show",
page_size: 'A4',
layouts: "pdf.html",
encoding: "UTF-8",
footer: {
right: "[page]/[topage]",
center: "Qmaker",
},
margin: { top:15,
bottom: 15,
left: 10,
right: 10}
end
The way it SHOULD be done (that is, if wkhtmltopdf supported it) would be using proper CSS Paged Media: http://www.w3.org/TR/css3-gcpm/
I'm looking into what it will take now.

Categories

Resources