How can I target an element, held within a template, using JavaScript? - javascript

<script type="text/html" id="tmpl-wp-travel-itinerary-items">
<div class="panel-wrap panel-wrap-itinerary">
<label><?php esc_html_e( 'Etape', 'wp-travel' ); ?></label>
<select id="selectjs"></select>
</div>
</script>
getElementById() on selectjs is null; ow can I target this element, via its id, within a script tag?

There is a way to — ultimately — retrieve the element via its id property using the <script> template approach; however it is convoluted, and requires - so far as I can tell - the creation of a temporary element to hold the template content and then querying that element:
// retrieve the innerHTML of the template, using the id of the template itself:
let templateSource = document.getElementById('tmpl-wp-travel-itinerary-items').innerHTML,
// creating an element to hold, and from which to retrieve, that content:
template = document.createElement('div');
// assigning the content of the template as the innerHTML of the created-element:
template.innerHTML = templateSource;
// finally, retrieving the <select> element via its id, from the
// created-element and Element.querySelector():
let selectElement = template.querySelector('#selectjs');
// logging the selectElement to the console:
console.log(selectElement);
<script type="text/template" id="tmpl-wp-travel-itinerary-items">
<div class="panel-wrap panel-wrap-itinerary">
<label><?php esc_html_e( 'Etape', 'wp-travel' ); ?></label>
<select id="selectjs"></select>
</div>
</script>
JS Fiddle demo.
There is, however, a slightly easier approach, using the HTML <template> element:
// retrieving the <template> via its id:
let template = document.querySelector('#templateContent'),
// accessing the <select> from the content property
// of the <template>:
select = template.content.querySelector('select');
// logging the <select> element to the console:
console.log(select);
<template id="templateContent">
<div class="panel-wrap panel-wrap-itinerary">
<label></label>
<select id="selectjs"></select>
</div>
</template>
JS Fiddle demo
References:
document.createElement().
document.querySelector().
Element.innerHTML.
HTMLTemplateElement.
<template> element.

Related

How can you select code nodes that were added through templates?

So I am using the template method to dynamically add HTML content in a page, and I want to change the value of an input through an event listener.
Here's a completely random snippet of code as an example (it's nonsensical on purpose):
favoriteElement += `<div class="favorite__page JS-favoritePage">
<p id="JS-amountOfFavorites">Quantity of saved pages : ${amount}</p>
<input type="number" class="favoritesQuantity" name="amountOfFavorites" min="1" max="100" value="${value}">
</div>`
So let's say that I want to have access to the value of the input, I'll declare a variable and get it through their query selector :
let inputFavoritesQuantity = document.querySelector('input [class="favoritesQuantity"]');
Now I'll add an event listener:
inputFavoritesQuantity.addEventListener("input", function(e){
let valueOfInput = e.target.value;
//Other code
}
Though the problem is that I do not have access to the input because it's added with a template, so it gives an error Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
I could add everything by hand using the properties createElement,setAttribute,appendChild...
But it would make the code VERY long and difficult to maintain! (without even considering the fact on my code project I'd have to add 5 nested elements which have 5 attributes each!)
Is there another efficient method to have access to an element with templates?
The DOMParser compiles strings into a document. You need to access the
documentElement in order to add to the existing dom. Here's an example of use
let amount = 100
let value = 50
favoriteElement = `<div class="favorite__page JS-favoritePage">
<p id="JS-amountOfFavorites">Quantity of saved pages : ${amount}</p>
<input type="number" name="amountOfFavorites" min="1" max="100" value="${value}" />
</div>`
// This converts the string and gets the documentElement.
var node = new DOMParser().parseFromString(favoriteElement, "text/html").documentElement
//Now we are working with an actual element and not a string of text.
let inputFavoritesQuantity = node.querySelector('input [class="favoritesQuantity"]');
node.addEventListener("input", function(e){
let valueOfInput = e.target.value;
console.log('value changed', valueOfInput);
})
var outputDiv = document.getElementById('content')
outputDiv.appendChild(node);
<div id="content">
</div>

JavaScript evaluate XPATH within an element?

I am trying to get an element using document.evaluate() but also want to search only within a specific element. So for example:
const element = document.evaluate('.//p', ...); //I want this to return the Hello, World p element
<html>
<body>
<div id="someId">
<p>Hello, World!<p>
</div>
</body>
</html>
Is there any way I could pass in the div with id someId into the evaluate to only search within that scope?
I know I could write the whole XPATH like .//div[#id="someId"]/p (Wouldnt work for my case) or do string concatenation but would like to find a cleaner way of doing it like passing the DOM element (or some object it contains) somewhere.
This is precisely the purpose of the second argument of document.evaluate():
contextNode specifies the context node for the query (see the XPath specification). It's common to pass document as the context node.
const someId = document.getElementById('someId');
const result = document.evaluate('.//p', someId, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
console.log(result.snapshotItem(0)); // Hello, World!
console.log(result.snapshotItem(1)); // null
<div id="someId">
<p>Hello, World!</p>
</div>
<div>
<p>Goodbye, World!</p>
</div>
You can also use document for the contextNode, using the parent id within the query (like you asked). Like this:
const snapshotType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
const result1 = document.evaluate('.//div[#id="someId"]//p', document, null, snapshotType);
console.log(`result1 contains ${result1.snapshotLength} element, namely`, result1.snapshotItem(0))
//alternatively search for the actual text
const result2 = document.evaluate('.//p[contains(text(), "Hello")]', document, null, snapshotType);
console.log(`result2 contains ${result2.snapshotLength} element, namely`, result2.snapshotItem(0));
// finally, if the element order is fixed, you can take the first one
const result3 = document.evaluate('(.//p)[1]', document, null, snapshotType);
console.log(`result3 contains ${result3.snapshotLength} element, namely`, result3.snapshotItem(0));
<div id="someId">
<p>Hello, World!</p>
</div>
<div>
<p>Goodbye, World!</p>
</div>
<div>
<p>And I left</p>
</div>

HTML Button Appears as Text after $compile [duplicate]

This question already has answers here:
how can we use $compile outside a directive in Angularjs
(5 answers)
"Thinking in AngularJS" if I have a jQuery background? [closed]
(15 answers)
Closed 4 years ago.
I am trying to create a pop up dialog with two buttons created in JS code with angular. The following code that produces the buttons...
var html = $('<button ng-click = "cancelAlert()" > Cancel</button > <button ng-click="continueAlert()">Continue</button>');
var div = $compile(html);
var content = div($scope);
document.getElementById('dialogboxhead').innerHTML = "header";
document.getElementById('dialogboxbody').innerHTML = "body";
document.getElementById('dialogboxfoot').innerHTML = content;
Gives me the following html text instead of the actual buttons themselves...
[[object HTMLButtonElement], [object Text], [object HTMLButtonElement]]
Am I missing something here that I have forgotten to add in?
The HTML looks like the following...
<div id="dialogoverlay"></div>
<div id="dialogbox">
<div>
<div id="dialogboxhead"></div>
<div id="dialogboxbody"></div>
<div id="dialogboxfoot"></div>
</div>
</div>
The $compile method accepts a string argument if you want to provide markup in this way.
Avoid wrapping the input for $compile with anything (ie $(..)). Instead, just pass the html string directly to the $compile() method, and also attach the div via the DOM append() method, and you should find this will work as expected:
var html = '<button ng-click="cancelAlert()">Cancel</button><button ng-click="continueAlert()">Continue</button>';
var div = $compile(html);
...
document.getElementById('dialogboxfoot').append( div[0] );
For more infromation see the usage on the official docs.
Here's a link to a working jsFiddle
It is not wise to mix AngularJS and jQuery this way.
The major problem with this approach is that $compile adds watchers to the specified scope. Those watchers will remain after added elements are removed from the DOM. This will result in memory leaks. If this is a dialog box that is constantly being added and removed -- beware.
But if you must, don't use innerHTML to append compiled content:
̶d̶o̶c̶u̶m̶e̶n̶t̶.̶g̶e̶t̶E̶l̶e̶m̶e̶n̶t̶B̶y̶I̶d̶(̶'̶d̶i̶a̶l̶o̶g̶b̶o̶x̶f̶o̶o̶t̶'̶)̶.̶i̶n̶n̶e̶r̶H̶T̶M̶L̶ ̶=̶ ̶c̶o̶n̶t̶e̶n̶t̶;̶
var foot = document.getElementById('dialogboxfoot');
$(foot).append(content);
The DEMO
angular.module("app",[])
.controller("ctrl",function($scope, $compile) {
var html = $('<button ng-click = "cancelAlert()" > Cancel</button > <button ng-click="continueAlert()">Continue</button>');
var div = $compile(html);
var content = div($scope);
document.getElementById('dialogboxhead').innerHTML = "header";
document.getElementById('dialogboxbody').innerHTML = "body";
var foot = document.getElementById('dialogboxfoot');
$(foot).append(content);
})
<script src="//unpkg.com/jquery"></script>
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-controller="ctrl">
<div id="dialogoverlay"></div>
<div id="dialogbox">
<div id="dialogboxhead"></div>
<div id="dialogboxbody"></div>
<div id="dialogboxfoot"></div>
</div>
</body>

How to add Bootstrap class to element?

I'm getting this error from the console:
Uncaught DOMException: Failed to execute 'add' on 'DOMTokenList': The
token provided ('si col-md-4') contains HTML space characters, which
are not valid in tokens.
This is my HTML snippet, I want to append the div to the row:
<div id = 'data' class="container">
<div id = 'row1' class = 'row'>
</div>
</div>
This is my javascript code:
var row = document.getElementById('row1');
var div = document.createElement('div');
div.classList.add('si col-md-4');
row.append(div);
I should also note that I'm using a firebase database to get the information I want to append.
To add multiple class, separate class with ,(comma)
var row = document.getElementById('row1');
var div = document.createElement('div');
div.classList.add('si', 'col-md-4');
row.append(div);
<div id = 'data' class="container">
<div id = 'row1' class = 'row'>
test
</div>
</div>
Use , separator if you want to add/remove several classes.
div.classList.add('si','col-md-4');
If you only need to add the class col-md-4,
div.classList.add('col-md-4');
just add multiple classes with space seperated as shown below
div.className='si col-md-4';
Here you go with a solution
$("#row1").append("<div class='si col-md-4'>Test</div>");
.si {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id = 'data' class="container">
<div id = 'row1' class = 'row'>
</div>
</div>
Used jQuery append method.
Well you can use .setAttribute("class","si col-md-4").It will add the whole new class to it.
As you're adding a class so you should also .setAttribute("class","previos-class si col-md-4") do this trick.

How to select elements from a handelbars template?

We have a div within our webpage:
<div id="person-details">
<div></div>
...
</div>
We select the relevant elements from this div using the following jQuery code:
var person = $("#person-details");
var children = release.find("div");
var fullname = children.first();
We use a render function to perform an ajax request to get a handlebars template that we're storing in an external file:
function _render() {
var templateScript;
template.getTemplate(filename).done(function(template) {
templateScript = Handlebars.compile(template);
fullname.html(templateScript(context));
});
}
The template looks like the following:
<div>
<div>
<p>{{name}}</p>
</div>
<div>
<p>{{value}}</p>
</div>
</div>
After the render function has been called, we want to select any of the elements from within the template. For example we have tried using:
fullname.children('div');
but as the content has been dynamically generated we aren't able to get the nodes from within the DOM.
Is it even possible to select elements from within a generated handlebars template like this?
Thanks to #DanielShillcock for highlighting render() being asynchronous. Here is a solution:
function _render() {
var templateScript;
template.getTemplate(filename).done(function(template) {
templateScript = Handlebars.compile(template);
selector.html(templateScript(attribute));
value = selector.find("div");
});
}

Categories