How to runtime generate div through javascript? - javascript

While doing example of JSON, I found that values are overwritten. I want to generate a div for each value but how?
Javascript code:
<script>
var txt = '{"employees":[' +
'{"firstName":"John","lastName":"Doe","nationality":"Pakistani","Age":"24","Salary":"40000" },' +
'{"firstName":"Anna","lastName":"Smith","nationality":"USA","Age":"24","Salary":"40000" },' +
'{"firstName":"Peter","lastName":"Jones","nationality":"UK","Age":"24","Salary":"40000" }]}';
window.onload = function(){
for(var i = 0 ; i< 3 ; i++){
var obj = eval ("("+ txt +")");
document.getElementById("fname").innerHTML=obj.employees[i].firstName;
document.getElementById("lname").innerHTML=obj.employees[i].lastName;
document.getElementById("nationality").innerHTML=obj.employees[i].nationality;
document.getElementById("Age").innerHTML=obj.employees[i].Age;
document.getElementById("Salary").innerHTML=obj.employees[i].Salary;}
}
</script>
Html body:
<div>
<p>
First Name: <span id="fname"></span><br />
Last Name: <span id="lname"></span><br />
Nationality: <span id="nationality"></span><br />
Age: <span id="Age"></span><br />
Salary: <span id="Salary"></span><br />
</p>
</div>

What you are trying to do is typically done through templates. You should look into the new HTML5 template element, which makes the work of templating your data super easy.
Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
In your case, just JSON.parse the string to obtain the object array from the text, and then loop through it. With each iteration, clone (copy) the template into an element, replace the fields using an appropriate query selector and append to the document.
Demo: http://jsfiddle.net/abhitalks/VNtdU/1/
Relevant HTML:
<template id="mytemplate">
<div>
<p>
First Name: <span class="fname"></span><br />
Last Name: <span class="lname"></span><br />
...
</template>
Relevant Javascript:
var list = JSON.parse(txt);
list.employees.forEach(function(obj) {
var template = document.getElementById('mytemplate').content.cloneNode(true);
template.querySelector('.fname').innerText = obj.firstName;
template.querySelector('.lname').innerText = obj.lastName;
template.querySelector('.nationality').innerText = obj.nationality;
template.querySelector('.age').innerText = obj.Age;
template.querySelector('.salary').innerText = obj.Salary;
document.body.appendChild(template);
});
Where:
txt is the your json string (same as in your question)
It's preferable to use classes instead of id, otherwise your id gets duplicated and will no longer be unique.
You could also use document.importNode alternatively to content.cloneNode
The true parameter to cloneNode or importNode means deep copy, indicating whether the descendants of the imported node need to be imported.
Important:
template is an HTML5 addition (candidate recommendation) and currently is not supported in IE. Support in Safari is also flaky. Hence, use it carefully.
You may check the support at runtime by:
if ('content' in document.createElement('template')) {...
Aside:
Mostly, script element is also used in the similar way as above:
<script id="mytemplate" type="text/template">...

Related

How do I change more than one element?

EDIT: I changed the var to class but I might have some error in here.
Here it goes, I want to have this paragraph in which the user can change the name on the following paragraph. The code I'm using only changes one name but the rest remains the same.
<script type="text/javascript">
function changey(){
var userInput = document.getElementById('userInput').value;
var list = document.getElementByClassName('kiddo');
for (let item of list) {
item.innerHTML = userInput;
}
}
</script>
<input id="userInput" type="text" value="Name of kid" />
<input onclick="changey()" type="button" value="Change Name" /><br>
Welcome to the site <b class="kiddo">dude</b> This is how you create a document that changes the name of the <b class="kiddo">dude</b>. If you want to say <b class="kiddo">dude</b> more times, you can!
No error messages, the code only changes one name instead of all three.
Use class="kiddo" instead of id in the html.
You can then use var kiddos = document.getElementsByClassName('kiddo') which will return an array of all the elements of that class name stored in kiddos.
Then you just need to loop through the values and change what you want.
Example of loop below:
for (var i = 0; i < kiddos.length; i++) {
kiddos[i].innerHTML = userInput;
}
id should be unique on the page. Javascript assumes that there is only one element with any given id. Instead, you should use a class. Then you can use getElementsByClassName() which returns an entire array of elements that you can iterate over and change. See Select ALL getElementsByClassName on a page without specifying [0] etc for an example.
Hello You should not use id, instead use class.
Welcome to the site <b class="kiddo">dude</b> This is how you create a document that changes the name of the <b class="kiddo">dude</b>. If you want to say <b class="kiddo">dude</b> more times, you can!
After That on Js part :
<script type="text/javascript">
function changey(){
var userInput = document.getElementById('userInput').value;
var list = document.getElementByClassName('kiddo');
for (let item of list) {
item.innerHTML = userInput;
}
}
</script>
you should use class instated of id. if you use id then the id [kiddo] must be unique
In short, document.querySelectorAll('.kiddo') OR
document.getElementsByClassName('kiddo') will get you a list of elements to loop through. Take note of querySelectorAll, though - it uses a CSS selector (note the dot) and doesn't technically return an array (you can still loop through it, though).
See the code below for some full working examples (const and arrow functions are similar to var and function, so I'll put up a version using old JavaScript, too):
const formEl = document.querySelector('.js-name-change-form')
const getNameEls = () => document.querySelectorAll('.js-name')
const useNameFromForm = (formEl) => {
const formData = new FormData(formEl)
const nameValue = formData.get('name')
const nameEls = getNameEls()
// Set the text of each name element
// NOTE: use .textContent instead of .innerHTML - it doesn't get parsed, so it's faster and less work
nameEls.forEach(el => el.textContent = nameValue)
}
// Handle form submit
formEl.addEventListener('submit', (e) => {
useNameFromForm(e.target)
e.preventDefault() // Prevent the default HTTP request
})
// Run at the start, too
useNameFromForm(formEl)
.name {
font-weight: bold;
}
<!-- Using a <form> + <button> (submit) here instead -->
<form class="js-name-change-form">
<input name="name" value="dude" placeholder="Name of kid" />
<button>Change Name</button>
<form>
<!-- NOTE: Updated to use js- for js hooks -->
<!-- NOTE: Changed kiddo/js-name to spans + name class to remove design details from the HTML -->
<p>
Welcome to the site, <span class="js-name name"></span>! This is how you create a document that changes the name of the <span class="js-name name"></span>. If you want to say <span class="js-name name"></span> more times, you can!
</p>
var formEl = document.querySelector('.js-name-change-form');
var getNameEls = function getNameEls() {
return document.querySelectorAll('.js-name');
};
var useNameFromForm = function useNameFromForm(formEl) {
var formData = new FormData(formEl);
var nameValue = formData.get('name');
var nameEls = getNameEls(); // Set the text of each name element
// NOTE: use .textContent instead of .innerHTML - it doesn't get parsed, so it's faster and less work
nameEls.forEach(function (el) {
return el.textContent = nameValue;
});
};
// Handle form submit
formEl.addEventListener('submit', function (e) {
useNameFromForm(e.target);
e.preventDefault(); // Prevent the default HTTP request
});
// Run at the start, too
useNameFromForm(formEl);
<button class="js-get-quote-btn">Get Quote</button>
<div class="js-selected-quote"><!-- Initially Empty --></div>
<!-- Template to clone -->
<template class="js-quote-template">
<div class="js-quote-root quote">
<h2 class="js-quote"></h2>
<h3 class="js-author"></h3>
</div>
</template>
You have done almost everything right except you caught only first tag with class="kiddo".Looking at your question, as you need to update all the values inside tags which have class="kiddo" you need to catch all those tags which have class="kiddo" using document.getElementsByClassName("kiddo") and looping over the list while setting the innerHTML of each loop element to the userInput.
See this link for examples:https://www.w3schools.com/jsref/met_document_getelementsbyclassname.asp
try:
document.querySelectorAll('.kiddo')
with
<b class="kiddo">dude</b>

Find an element in a DOM given the exact html of the element

I needed an efficient way to search for a 13 digit numeric code (call it ncode) appearing in an arbitrary way in a html page e.g. it might be <p>ncode</p> or it could be <input type="hidden" value="ncode"> or <span content="ncode"></span>.
I created a regex that finds the html fragment I need and in a specific case it returns
<span itemprop="gtin13" content="0885913103914"></span>
Having done that, I thought it would be easy to use jQuery to find the actual DOM element but I can't seem to find anything that works. I tried $(body).filter(":contains()") and various similar things.
So the question is, what is the best way to located the element in the DOM whose html matches a known string?
If it can't be done, I guess I'll have to parse the found html string for different properties and then select elements using those properties. It just seems there should be a way of directly selecting the element so I can query and manipulate it in the DOM.
Try this simple approach
Find the name of the node which represent the html-to-find
Find the classes of the node which represent the html-to-find
Prepare a selector with the above two information and execute the same.
Demo
var htmlToFind = '<span class="clas31" attribute1="4">fdgdfg</span>';
var nodeName = $( htmlToFind )[0].nodeName;
var classList = [].slice.call($( htmlToFind )[0].classList).join(".");
var isFound = $( nodeName + "." + classList ).length > 0
console.log( isFound );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<span class="class1" attribute1="2">345345</span>
<span class="class2" attribute1="2"><a>a34rrt5345</a></span>
<span class="clas31" attribute1="4">fdgdfg</span>
<div asd="sd">rdfgdfg</div>
</div>
You can optimize this further by checking the other attributes of html-to-find as well.
If the number of nodes returned by nodename and classes are more than 1, then filter them further as
var htmlToFind = '<span class="clas31" attribute1="4">fdgdfg</span>';
var nodeName = $( htmlToFind )[0].nodeName;
var classList = [].slice.call($( htmlToFind )[0].classList).join(".");
var length = $( nodeName + "." + classList ).length
console.log( "total matches by classnames and node name " + length );
var exactMatches = [];
$( nodeName + "." + classList ).each( function(){
if ( this.outerHTML == htmlToFind )
{
exactMatches.push( this )
}
});
console.log( "exactMatches" , exactMatches )
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<span class="class1" attribute1="2">345345</span>
<span class="class2" attribute1="2"><a>a34rrt5345</a></span>
<span class="clas31" attribute1="4">fdgdfg</span>
<span class="clas31" attribute1="4">fdg2dfg</span>
<div asd="sd">rdfgdfg</div>
</div>
You can do what you're asking but I'd recommend not doing it, purely for performance reasons. If you take every element in the document body and parse them with some defined logic (dependent on how you want to identify the particular elements) then you can get a collection of just the matching elements.
This example shows finding the elements with the specific value anywhere in the element...
// this returns the element(s) that contain the given text
var elements = $("body span").filter(function() {
return this.outerHTML.match("0885913103914");
}).toArray();
elements.forEach(function(el) { console.log(el.innerText); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<span itemprop="gtin13" content="0885913103914">Span with correct content attribute.</span><br />
<span itemprop="gtin13" content="0885913103915">Span with incorrect content attribute.</span><br />
<span itemprop="gtin13" content="0885913103914">Span with correct content attribute.</span><br />
<span itemprop="gtin13" content="0885913103915">Span with 0885913103914 inside the contents.</span><br />
You would need to adjust the filter function to suit other criteria. Basically, if you return true then the element is included in the output.
If you can do this effectively with your original regex solution then I'd recommend that instead.

Using regex with javascript on nodejs find html attribute and prepend something to its value

I have some markup in JS as follows:
<div class="col-sm-4">
<span id="some-media" class="media">Text</span>
</div>
I would like to select the class attribute of the span and prepend its value with lets say the characters: "::". So after the regex replace i would end up with:
<div class="col-sm-4">
<span id="some-media" class="::media">Text</span>
</div>
EDIT: Note that the order of the attributes in the HTML element is variable so my span attributes could very well have different order like so:
<div class="col-sm-4">
<span class="::media" id="some-media" >Text</span>
</div>
You got a regex solution, this is a DOMmy one:
var html = `<div class="col-sm-4">
<span id="some-media" class="media">Text</span>
</div>`
var doc = (new DOMParser()).parseFromString(html, "text/html");
var el = doc.getElementsByTagName('span')[0];
el.setAttribute('class', '::' + el.className);
console.log(
doc.getElementsByClassName('::media').length > 0 // check if modification's done
);
Since you have no way except Regular Expressions this can be considered as a workaround:
(<span[^>]*class=.)([^'"]+)
JS:
var html = `<div class="col-sm-4">
<span id="some-media" class="media">Text</span>
</div>
<span class="media" id="some-media">Text</span>
`;
console.log(
html.replace(/(<span[^>]*class=.)([^'"]+)/g, `$1::$2`)
);
This isn't using regex, but you can do it like this in vanilla JavaScript:
const el = document.getElementsByClassName('media')[0];
el.className = '::' + el.className;
Or in jQuery:
const $el = $('div span.media');
$el.attr('class', '::' + $el.attr('class'));
Hope this helps.
Don't parse html with regex, use DocumentFragment (or DOMParser) object instead:
var html_str = '<div class="col-sm-4"><span class="media">Text</span></div>',
df = document.createRange().createContextualFragment(html_str),
span = df.querySelector('span');
span.setAttribute('class', '::' + span.getAttribute('class'));
console.log(df.querySelector('div').outerHTML);
I think this is what you're after:
var test = $("#some-media")[0].outerHTML();
var test2 = '<div id="some-media" class="media">Text</div>'
if(/span/.test(test)) //Valid as contains 'span'
alert(test.replace(/(class=")/g, "$1::"));
if(/span/.test(test2)) //Not valid
alert(test.replace(/(class=")/g, "$1::"));
Since the order differs, writing a regex that captures all possible combinations of syntax might be rather difficult.
So we'd need a full list of rules the span follows so we can identify that span?
Got some more info about if the span occurs in a longer HTML string? Or is the string this span and this span only?
An alternative would be to use one of the several node DOM modules available, so you can work with HTML nodes and be able to use any of the above solutions to make the problem simpler.
But since you're using node:
1) Are you using any templating engines? If so, why not rerender the entire template?
2) Why does the class name have to change on the server side? Isn't there a workaround on the clientside where you do have access to the DOM natively? Or if it's just to add styling, why not add another css file that overwrites the styling of spans with className 'media'?
3) If all of the above is not applicable and it;s a trivial problem like you say, what error di you get using a simple replace?
strHTML.replace( 'class="media"', 'class="::media"' )
or if it has to be regex:
strHTML.replace( /class=\"(.*)\"/, 'class=\"::$1\"' );

Regex match HTML attributes names

I have the following element stored as a String:
<div class="some-class" id="my-id" data-theme="black">
<strong data-animation="fade" disabled>Hello world!</strong>
</div>
I want to extract all the attributes names like this:
["class", "id", "data-theme", "data-animation", "disabled"]
This is what I tried to do, but I get also the values and dosent match the data-animation and disabled:
http://jsbin.com/hibebezibo/edit?js,console
EDIT:
Manged to get attributes using:
[\w-]+(?=\s*=\s*".*?")
But I still cant get the "disabled" prop.
Can someone explain me how to achieve this?
Thanks!
Using below regex which benefits from a positive lookahead you are able to match attributes' names:
[ ][\w-]+(?=[^<]*>)
Note: Adding - to character class is a must.
javascript code:
const HtmlElement = `<div class="some-class" id="my-id" data-theme="black">
<strong data-animation="fade" disabled>Hello world!</strong>
</div>`
console.log(HtmlElement.match(/ [\w-]+(?=[^<]*>)/g).map(function(element) {
return element.trimLeft();
}));
However it's not bulletproof as it can match words following a >. E.g:
<strong data-animation="fade" disabled>Hello world!></strong>
So it's recommended to accomplish such a task using DOM functionalities:
var html = document.createElement('div');
html.innerHTML = '<div class="some-class" id="my-id" xlink:href data-theme="black"><strong data-animation="fade" disabled>Hello world!</strong></div>';
var attrNodes = document.evaluate('//*/attribute::*', html, null, XPathResult.ANY_TYPE, null)
var nextAttrNode = attrNodes.iterateNext()
var arrAttrs = [];
while (nextAttrNode) {
arrAttrs.push(nextAttrNode.name)
nextAttrNode = attrNodes.iterateNext();
}
console.log(arrAttrs)
This works on even nested structure.
It returns element name and its attributes
\<([^\/\>"]+)\s{1,}([^"]+)=\"[^"]*\"
Test your own regex on https://regex101.com

Insert span in a dom element without overwrite child nodes?

I have an HTML article with some annotations that I retrieve with SPARQL queries. These annotations refer to some text in the document, and I have to highlight this text (wrapping it in a span).
I had already asked how to wrap text in a span, but now I have a more specific problem that I do not know how to solve.
The code I wrote was:
var currentText = $("#"+v[4]["element"]+"").text();
var newText = currentText.substring(0, v[5]["start"]) + "<span class=' annotation' >" + currentText.substring(v[5]["start"], v[6]["end"]) + "</span>" + currentText.substring(v[6]["end"], currentText.length);
$("#"+v[4]["element"]+"").html(newText);
Where:
v[4]["element"] is the id of the parent element of the annotation
v[5]["start"] is the position of the first character of the annotation
v[6]["end"] is the position of the last character of the annoation
Note that start and end don't consider html tags.
In fact my mistake consists in extracting data from the node with the text() method (to be able to go back to the correct position of the annotation) and put back with the html() method; but in this manner if parent node has children nodes, they will be lost and overwritten by simple text.
Example:
having an annotation on '2003'
<p class="metadata-entry" id="k673f4141ea127b">
<span class="generated" id="bcf5791f3bcca26">Publication date (<span class="data" id="caa7b9266191929">collection</span>): </span>
2003
</p>
It becomes:
<p class="metadata-entry" id="k673f4141ea127b">
Publication date (collection):
<span class="annotation">2003</span>
</p>
I think I should work with nodes instead of simply extract and rewrite the content, but I don't know how to identify the exact point where to insert the annotation without considering html tags and without eliminating child elements.
I read something about the jQuery .contents() method, but I didn't figure out how to use it in my code.
Can anyone help me with this issue? Thank you
EDIT: Added php code to extract body of the page.
function get_doc_body(){
if (isset ($_GET ["doc_url"])) {
$doc_url = $_GET ["doc_url"];
$doc_name = $_GET ["doc_name"];
$doc = new DOMDocument;
$mock_doc = new DOMDocument;
$doc->loadHTML(file_get_contents($doc_url.'/'.$doc_name));
$doc_body = $doc->getElementsByTagName('body')->item(0);
foreach ($doc_body->childNodes as $child){
$mock_doc->appendChild($mock_doc->importNode($child, true));
}
$doc_html = $mock_doc->saveHTML();
$doc_html = str_replace ('src="images','src="'.$doc_url.'/images',$doc_html);
echo($doc_html);
}
}
Instead of doing all these, you can either use $(el).append() or $(el).prepend() for inserting the <span> tag!
$("#k673f4141ea127b").append('<span class="annotation">2003</span>');
Or, If I understand correctly, you wanna wrap the final 2003 with a span.annotation right? If that's the case, you can do:
$("#k673f4141ea127b").contents().eq(1).wrap('<span class="annotation" />');
Fiddle:
$(document).ready(function() {
$("#k673f4141ea127b").contents().eq(1).wrap('<span class="annotation" />');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="metadata-entry" id="k673f4141ea127b">
<span class="generated" id="bcf5791f3bcca26">Publication date (<span class="data" id="caa7b9266191929">collection</span>): </span>
2003
</p>
At the end my solution is in this Fiddle.
Generalizing:
var element = document.getElementById(id);
var totalText = element.textContent;
var toFindText = totalText.substring(start,end);
var toReplaceText = "<span class='annotation'>"+toFindText+"</span>";
element.innerHTML = element.innerHTML.replace(toFindText, toReplaceText);
Hope it could help someone else.
Note: This don't check if two or more annotations refers to the same node, I'm working on it right now.

Categories

Resources