Hello I have a <strong></strong> Tag nested in a paragraph <p></p>, I'm trying to remove the <strong> tag but keep the text or the value. Something similar to unwrapping in jquery but in javascript.
I tried this code on a dummy HTML page and it works fine
<html>
<body>
<p>aaa <Strong>bbbbb</Strong></p>
<p>acccaa <Strong>ddddd</Strong></p>
<p>eeee <Strong>ffff</Strong></p>
<script>
var p = document.getElementsByTagName("p");
for(var i=0;i<p.length;i++){
var strongs = p[i].getElementsByTagName("strong");
for(var j=0;j<strongs.length;j++){
p[i].replaceChild(document.createTextNode(strongs[j].innerText),strongs[j]);
}
}
</script>
</body>
</html>
But as soon as I try the same code on a real page example: https://www.bustle.com/privacy
I get this error:
Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node.
Any idea on how to get this to work on the example or any other example?
getElementsByTagName() returns a live NodeList. So when you replace a tag, the indexes of all the following elements shift down and the code fails when you have more than one <strong> tag in the same paragraph. As a result, it will skip some tags.
The solution is to convert the NodeList to an array so it doesn't change while you're looping.
Another problem in your real page that isn't in the snippet is that the <strong> tags can be nested deeply within the <p>. You should use strongs[j].parentElement to get its direct parent, rather than assuming that the p[i] is the parent.
var p = document.getElementsByTagName("p");
for (var i = 0; i < p.length; i++) {
var strongs = Array.from(p[i].getElementsByTagName("strong"));
for (var j = 0; j < strongs.length; j++) {
strongs[j].parentElement.replaceChild(document.createTextNode(strongs[j].innerText), strongs[j]);
}
}
<html>
<body>
<p>aaa
<Strong>bbbbb</Strong> - <strong>12345</strong></p>
<p>acccaa <span><Strong>ddddd</Strong> x</span></p>
<p>eeee
<Strong>ffff</Strong>
</p>
</body>
</html>
You can also avoid the nested loops by using a query selector.
var strongs = document.querySelectorAll("p strong");
strongs.forEach(strong => strong.parentElement.replaceChild(document.createTextNode(strong.innerText), strong));
<html>
<body>
<p>aaa
<Strong>bbbbb</Strong> - <strong>12345</strong></p>
<p>acccaa <span><Strong>ddddd</Strong> x</span></p>
<p>eeee
<Strong>ffff</Strong>
</p>
</body>
</html>
No need to loop through paragraphs to remove <strong>. Simply removing all 'strongs' in place works fine.
function removeStrongs() {
let strongs = document.querySelectorAll('strong');
strongs.forEach(strong => {
strong.insertAdjacentText('afterend', strong.innerText);
strong.remove();
});
}
<h4>This is a <strong>Title</strong></h4>
<p>
Now is the time for all <strong>good</strong> men to come to the <strong>aid</strong> of the party.
</p>
<p>A <strong>quick brown</strong> fox jumps over the lazy dog.</p>
<button onclick="removeStrongs();">Remove Strongs</button>
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.
I want to make a html which will auto get the information for a *.txt file
something1
something2
something3
and output into this html file
<!DOCTYPE html>
<html>
<head>
<title>List</title>
</head>
<body>
<ul>
#here to output#
</ul>
</body>
</html>
I prefer to use JavaScript. Some help is appreciated.
You have to request the file using AJAX call. Then you need to iterate through each line of response and generate DOM element (li in this case) and input line inside of it. After that insert each li element into your ul list.
You can achieve it using jQuery as you are probably new to JavaScript it's probably the easiest way.
What you need to do is request the file first:
$.ajax('url/to/your/file', {
success: fileRetrieved
});
Now after the file is retrieved jQuery will call fileRetrieved method, so we have to create it:
function fileRetrieved(contents) {
var lines = contents.split('\n');
for(var i = 0; i < lines.length; i += 1) {
createListElement(lines[i]);
}
}
Now for each line from the file function fileRetrieved will call createListElement function passing line of text to it. Now we just need to generate the list element and inject it into DOM.
function createListElement(text) {
var into = $('ul');
var el = $('<li></li>').html(text);
el.appendTo(into);
}
Of course you don't want to retrieve into element each time createListElement is called so just store it somewhere outside the function, it's your call, I'm just giving you the general idea.
Here is an example of the script (without AJAX call of course as we can't simulate it):
var into = $('#result');
function fileRetrieved(contents) {
var lines = contents.split('\n');
for (var i = 0; i < lines.length; i += 1) {
createListElement(lines[i]);
}
}
function createListElement(text) {
var el = $('<li></li>').html(text);
el.appendTo(into);
}
var text = $('#text').html();
fileRetrieved(text);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- This element simulates file contents-->
<pre id="text">
fdsafdsafdsa
fdsafd
safdsaf
dsafdsaf
dsafdsafds
afdsa
</pre>
<div id="result"></div>
Try this
<html>
<head>
<title>List</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<ul id="renderTxt_list">
</ul>
<input type="button" id="lesen" value="Read Data" />
</body>
<script>
$(document).ready(function(){
$("#lesen").click(function() {
$.ajax({
url : "testTxt.txt",
dataType: "text",
success : function (data) {
$html = "";
var lines = data.split("\n");
for (var i = 0, len = lines.length; i < len; i++) {
$html += '<li>'+lines[i]+'</li>';
}
$("body ul").append($html);
}
});
});
});
</script>
</html>
You need to request the file first, and then append it to your chosen place in the document.
You can for example use jQuery's get (or any other function like the native fetch), and then inject it into the ul element:
$.get("*.txt").then(x => $("ul").html("<li>" + x.split('\n').join('</li><li>') + "</li>"))
Let's break this solution by steps:
First, we need to request the external file:
$.get("*.txt")
Read about jQuery's get here. Basicly it will request the file you asked for using network request, and return a promise.
In the Promise's then, we can do stuff with the request's result after it is resolved. In our case we want to first break it by lines:
x.split('\n')
split will return an array that will look like this: ["line 1, "line 2", "line 3"].
JS arrays have the join method, which concat them to string while putting the string you want between the items. So after we do this:
x.split('\n').join('</li><li>')
We only need to add the <li> element to the start and end of the string like this:
"<li>" + x.split('\n').join('</li><li>') + "</li>"
Finally we appent it to your chosen element using jQuery's html.
I am learning JavaScript.
I am trying toggle the text on a page using the replaceChild() method. I came up with the code below. I don't understand why it will not work. Pls help.
<html>
<head>
<script>
function toggleText() {
var be= document.getElementById("main");
var b4= be.getElementsByTagName("h1");
var l8 = document.createElement("h1").innerHTML="After";
var l88 = document.createElement("h1").innerHTML="Before";
if (b4[0].innerHTML=="Before"){
be.replaceChild(l8,b4[0])
}
if (b4[0].innerHTML=="After") {
be.replaceChild(l88,b4[0]);
}
}
</script>
</head>
<body>
<div id="main" onclick="toggleText()">
<h1>Before</h1>
</div>
</body>
</html>
As CBrone wrote, you have to create h1 instance first, store it to variable and then call innerHML on the variable.
Another problem is if structure. First you replace the element and then test the same element for another condition and do another operation. In this case is better to use if ... else if ... statement instead of if ... if ..., which is the root of your problem.
Here is working toggleText function
function toggleText() {
var be= document.getElementById("main");
var b4= be.getElementsByTagName("h1");
var l8 = document.createElement("h1");
l8.innerHTML="After";
var l88 = document.createElement("h1");
l88.innerHTML="Before";
if (b4[0].innerHTML == "Before")
{
be.replaceChild(l8, b4[0]);
}
else if (b4[0].innerHTML=="After")
{
be.replaceChild(l88, b4[0]);
}
}
Here is working fiddle
In addition to what’s been said in comments already:
var l8 = document.createElement("h1").innerHTML="After";
var l88 = document.createElement("h1").innerHTML="Before";
After this your variables do not contain references to the created elements, but the string values that you assigned to their innterHTML. (The result of an assignment operation is the assigned value.) And trying to pass text values instead of element references to replaceChild afterwards must fail for that reason.
Do this in two steps – create the elements first and save their reference into the variables – and then manipulate their innerHTML afterwards.
var l8 = document.createElement("h1");
l8.innerHTML="After";
var l88 = document.createElement("h1");
var l88 = .innerHTML="Before";
(And maybe use better suited variable names, because if you keep your current “naming scene” up you’ll get confused sooner or later.)
May I suggest the following, for better readability:
<html>
<head>
<script>
function toggleText() {
var be= document.getElementById("main");
var b4= be.getElementsByTagName("h1")[0];
if (b4.innerHTML=="Before") {
b4.innerHTML = "After";
}
else if (b4.innerHTML=="After") {
b4.innerHTML = "Before";
}
}
</script>
</head>
<body>
<div id="main" onclick="toggleText()">
<h1>Before</h1>
</div>
</body>
</html>
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.