How to use .innerHTML in template element - javascript

I'm making an app with a framework that uses templates in HTML pretty heavily. It was all going well until I bumped into this problem:
Uncaught TypeError: Cannot read property 'querySelector' of null
correct me if I'm wrong, but I think this has to do with my code itself using templates and JavaScipt not being able to read the inside of that template.
Here's a minature version of my code which I am experiencing errors with.
HTML
<template id="main.html">
<p id="paragraph">This text needs to be changed in
JS</p>
<button onclick=example()>Click ME!</button>
</template>
JavaScript
function example (){
document.getElementById("paragraph").innerHTML = "This text has been changed!"}
Error:
Uncaught TypeError: Cannot read property 'innerHTML'
of null
Is there any way of avoiding this error?

1) Template-element content is not rendered by default
You can re-use the content in a template-element as many times as you want, but you need to manually specify that using Javascript. If you don't use any Javascript to render your template-element it won't be visible in the DOM. Resulting in your code not finding the paragraph (which is in the template-element).
2) Make sure you call your Javascript after the DOM is loaded
A web-page gets loaded from top to bottom. If you don't place your Javascript-code on the bottom of your body tag, or manually say it needs to be loaded after the DOM is ready, your code tries to find an element before that element even exists.
3) Your code has small mistakes like missing paragraphs etc.
Fix that using a good editor.
This example code shows you how it can be done:
Javascript:
function example() {
document.getElementById("paragraph").innerHTML = "This text has been changed!";
}
function init() {
//Get the template-element by id
let mainTemplate = document.getElementById("main");
//Create a new div-element to hold template content
let parent = document.createElement("div");
//IMPORTANT!
//Cloning template content into HTML DOM so it's visible.
//This part - content.cloneNode - makes your template content visible after being appended to the body, or another legal element in the DOM.
parent.append(mainTemplate.content.cloneNode(true));
//Append to body
document.body.append(parent);
}
//After dom is ready: Launch init function
document.onload = init();
HTML:
<template id="main">
<p id="paragraph">This text needs to be changed in
JS</p>
<button onclick="example()">Click ME!</button>
</template>
Working JSFiddle: https://jsfiddle.net/ovc1mrs2/
You probably want to append the content of your template directly to the body, or to another element.

I'm thinking you just forgot to put "()" after "function example."
function example() {
document.getElementById("paragraph").innerHTML = "This text has been changed!"}
<p id="paragraph">This text needs to be changed in
JS</p>
<button onclick=example()>Click ME!</button>

add "()" after example in js.
Updated Javascript code :
function example () {
document.getElementById("paragraph").innerHTML = "This text has been changed!"
};

Related

how to render a JS variable into a html element

I am new to Javascript and want to be able to display a JS variable onto my page without the user going into the console as it is neater, and I find a lot of people don't know about the console, and I don't want to use the alert() code. Can anyone help?
The code below accesses the paragraph with id "test" and print the value of "testVariable" into it.
var testVariable = "hello";
document.getElementById("test").innerHTML = testVariable;
<p id="test"></p>
I think this is what you need but this example is very simple i recommend you to google more and to keep open the JavaScript Doc for any questions.
The definition for innerHTML.
The Element property innerHTML gets or sets the HTML or XML markup
contained within the element.
// Your variable
let name = "Jhon"
// Get the HTML tag by ID and set the innerHTML
document.getElementById('name').innerHTML = name;
<div class="container">
<p>Hello <span id="name"></span></p>
</div>

Add complexe Javascript array elements to HTML [duplicate]

I have a template:
function useIt() {
var content = document.querySelector('template').content;
// Update something in the template DOM.
var span = content.querySelector('span');
span.textContent = parseInt(span.textContent) + 1;
document.querySelector('#container').appendChild(
document.importNode(content, true));
}
<button onclick="useIt()">Use me</button>
<div id="container"></div>
<template>
<div>Template used: <span>0</span></div>
<script>alert('Thanks!')</script>
</template>
You can try the code here.
This code basically copies the template(html5 templates does not render on your screen) into another div. This allows you to reuse the DOM.
Problem: The line "span.textContent = parseInt(span.textContent) + 1;" changes the template code directly. I need to manipulate the content DOM and clone it into the container, without changing the template. This is very important since if I want to reuse the code, I need it to stay the same.
I have tried multiple ways to use jQuery to mimic the above javascript code, but I can't manage to figure it out. It would be better if there is a jQuery way.
If you NEED to use the new <template> tag, then you are mildly stuck . . . your cleanest alternative is to use importNode to bring in the content and then modify it after it's been appended.
Assuming that the templated code is realtively small, this should happen fast enough that you would never notice the difference in approach, though, in this specific example, the alert(), would delay the change of the content, so you would see "0", until you clicked "Okay", and then it would update to "1".
The code change for that would be:
function useIt() {
var content = document.querySelector('template').content;
var targetContainer = document.querySelector('#container');
targetContainer.appendChild(document.importNode(content, true));
var $span = $(targetContainer).find("div:last-of-type").find("span");
$span.text(parseInt($span.text() + 1));
}
If you are not married to the idea of <templates>, you could use jQuery's clone() method to do what you want to do, very easily . . . but, clone does not "see" the content of a <template>, due to the special nature of that particular element, so you would have to store the templated code some other way (JS variable, hidden div, etc.).
HOWEVER, this method will not work if you need to clone a script, the way that a <template> will. It will not trigger any script code in the "template container" element when the cloned version is created or appended. Additionally, if you store it in a hidden <div>, any script code in the "template container" element will trigger immediately on page load.
A simple version of the code for the clone() approach would look something like this:
function useIt() {
var $content = $("#template").clone();
var $span = $content.find("span");
$span.text(parseInt($span.text()) + 1);
$content.children().each(function() {
$("#container").append($(this));
});
}
Assuming that your template was:
<div id="template" style="display: none;">
<div>Template used: <span>0</span></div>
<script>alert('Thanks!')</script>
</div>
You could also move the <script>alert('Thanks!')</script> out of the template and into the script section (after you completed the "append loop"), to achive the desired alert functionality, if you wanted to.
It's an old question, but, did you try cloneNode(true)? It works on templates, as this:
var span = content.querySelector('span').cloneNode(true)
regards.

Need JS to modify the element following the script

I have a report authoring tool that lets me add descriptive text in the report header, prior to a table containing the data. The examples included with the tool show how include Javascript in the description for various special effects. I would like to change certain cells in the table into links to other reports. Here's the HTML produced by the reporting tool.
<div class="element-info">
<div class="description">My Description</div>
<div class="properties">
<table>...</table>
</div>
</div>
I have tried replacing "My Description" with the following, but (perhaps unsurprisingly) it's changing something other than the table.
<div>My Description
<script type="text/javascript">
// currentScript is supported in my version of Firefox.
var me = document.currentScript;
// go up two levels to get the enclosing div
var element_info = me.parentElement.parentElement;
// from there we want the properties div, then the table
var mytable = element_info.lastChild.firstChild;
mytable.style.color = "red";
</script>
</div>
I expect that the problem is that when the script runs, the HTML in the following div has not yet been parsed. Mozilla says that the defer attribute will be ignored in scripts without a src= attribute, and I've verified that it does nothing.
Although my example code is using plain Javascript, the authoring tool is based on jQuery, so it's full repertoire is available if needed.
If the problem involves the fact that the html hasn't yet been parsed, you can immediately gain a reference to the script, but only later utilize it, once the document is loaded. It would look like this:
<div>My Description
<script type="text/javascript">
// Get the reference immediately...
var script_of_interest = document.currentScript;
// And only use it once everything is loaded:
window.onload = function() {
var element_info = script_of_interest.parentElement.parentElement;
var mytable = element_info.lastChild.firstChild;
mytable.style.color = "red";
};
</script>
</div>
Make sure your the node your script operates on is loaded before the execution, otherwise your the node may be undefined or just nothing. You may try wrap your code with
$(document).ready(function(){
//your code
}).

Try It Yourself Editor JS

I have created a very simple editor that has been working great. However, I just tried to put JavaScript into it and I can't get it to work.
The code for the editor:
<div id="buttoncontainer">
<input id="button" onclick="update();" type="button" value="Update page">
</div>
<div id="tryitcontainer">
<textarea id="codebox"></textarea>
<iframe id="showpage"></iframe>
</div>
The JavaScript for the editor:
<script>
function update() {
var codeinput = document.getElementById('codebox').value;
window.frames[0].document.body.innerHTML = codeinput;
}
</script>
I just wanted to run some simple JavaScript that changes an image when it is clicked. This code works fine when I run it in a full browser, so I know its the editor thats the problem.
Is there a simple fix for this that I'm missing?
The button is not finding the update() method. You need that function to be globally available:
http://jsfiddle.net/t5swb7w9/1/
UPDATE: I understand now. Internally jQuery basically evals script tags. There's too much going on to be worth replicating yourself... either use a library to append, or eval the code yourself. Just a warning that eval'ing user input is rarely a good thing and is usually a welcome mat for hackers.
window.myScope = {
update: function() {
var div = document.createElement('div'),
codeinput = document.getElementById('codebox').value,
scriptcode = "";
div.innerHTML = codeinput;
Array.prototype.slice.apply(div.querySelectorAll("script")).forEach(function(script) {
scriptcode += ";" + script.innerHTML;
div.removeChild(script);
});
window.frames[0].document.body.appendChild(div);
// hackers love to see user input eval'd like this...
eval(scriptcode);
}
};
And then you would update your button like so:
<input id="button" onclick="myScope.update();" type="button" value="Update page">
Or, even better, use addEventListener and forget the onclick part altogether. I'll let you do that research on your own ;)
JavaScript inserted via innerHTML will not be executed due to security reasons:
HTML5 specifies that a <script> tag inserted via innerHTML should not execute.
from MDN: Element.innerHTML - Security considerations, see also: W3: The applied innerHTML algorithm.
A possible solution using the jQuery method .append() works around that, as it somehow evals the content. But this will still not solve your problem, as the JavaScript code is executed in the current scope.
Here's a test scenario:
function update() {
var codeinput = document.getElementById('codebox').value;
$(window.frames[0].document.body).append(codeinput);
}
Try it here
Try to insert this script:
<script>
alert( document.getElementById('tryitcontainer') );
</script>
and this one:
<p id="test">Test</p>
<script>
window.frames[0].document.getElementById('test').innerHTML = 'updated';
</script>
The first one will return a [object HTMLDivElement] or similar. Here you can see, that you're still in the same scope as the parent frame. The second one will correctly update the content within the iframe. Keep that in mind, when experimenting with those things.
Maybe Executing elements inserted with .innerHTML has some more infos for you.

Alternatives to document.write

I am in a situation where it seems that I must use document.write in a javascript library. The script must know the width of the area where the script is defined. However, the script does not have any explicit knowledge of any tags in that area. If there were explicit knowledge of a div then it would be as simple as this:
<div id="childAnchor"></div>
<script ref...
//inside of referenced script
var divWidth = $("#childAnchor").width();
</script>
So, inside of the referenced script, I am thinking of doing using document.write like this:
<script ref...
//inside of referenced script
var childAnchor = "z_87127XNA_2451ap";
document.write('<div id="' + childAnchor + '"></div>');
var divWidth = $("#" + childAnchor).width();
</script>
However, I do not really like the document.write implementation. Is there any alternative to using document.write here? The reason that I cannot simply use window is that this is inside of a view which is rendered inside of a master view page. Window would not properly get the nested area width.
The area is pretty much in here:
<body>
<div>
<div>
<div>
AREA
The AREA has no knowledge of any of the other divs.
This just occurred to me, and I remembered your question: the script code can find the script block it is in, so you can traverse the DOM from there. The current script block will be the last one in the DOM at the moment (the DOM still being parsed when the code runs).
By locating the current script block, you can find its parent element, and add new elements anywhere:
<!doctype html>
<html>
<head>
<style>
.parent .before { color: red; }
.parent .after { color: blue; }
</style>
</head>
<body>
<script></script>
<div class="parent">
<span>before script block</span>
<script>
var s = document.getElementsByTagName('script');
var here = s[s.length-1];
var red = document.createElement("p");
red.className = 'before';
red.innerHTML = "red text";
here.parentNode.insertBefore(red, here);
var blue = document.createElement("p");
blue.className = 'after';
blue.innerHTML = "blue text";
here.parentNode.appendChild(blue);
</script>
<span>after script block</span>
</div>
<script></script>
</body>
</html>​
http://jsfiddle.net/gFyK9/2/
Note the "blue text" span will be inserted before the "after script block" span. That's because the "after" span does not exist in the DOM at the moment appendChild is called.
However there's a very simple way to make this fail.
There is no alternative to this method. This is the only way that the script can find the width of the element that it is nested in without knowing anything about the DOM that it is loaded into.
Using document.write() when used appropriately is legitimate. Although, appending a new element to the DOM is preferable, it is not always an available option.
if you know which child element it is you can use the param nth child in jquery.
otherwise youll need to iterate through them with each()
Create and append the child like this:
var el = document.createElement('div');
el.id='id';
document.body.appendChild(el);
However I'm not really sure what you want to do with this to get the width of whatever, it will probably return 0.

Categories