Why isn't my appendChild working with createDocumentFragment? - javascript

I am trying to append newly created elements to a div, then append the div to a document fragment, but it isn't working as expected. Please help me identify what I am missing.
//array of values to look up
let channels = ["channel1", "channel2", "channel3","channel4","channel5","channel6","channel7","channel8","channel9","channel10","channel11","channel12","channel13","channel14","channel15","channel16"];
//jsonp request function defined
function streamRequest () {
//identify DOM elements for final appendChild
let docFrag = document.createDocumentFragment();
let container = document.getElementById("main-container");
//create container and elements for the acquired information
let div = document.createElement("div");
let image = document.createElement("img");
let p = document.createElement("p");
let p1 = document.createElement("p");
let p2 = document.createElement("p");
//variables for request responses
let logo;
let name;
let status;
let game;
channels.forEach(function channelsRequest(channel){
$.getJSON("https://api.twitch.tv/kraken/streams/" + channel + "?callback=?", function callback(data) {
if(data.stream === null){
status = "Offline"
game = "Offline"
} else if (data.stream != null) {
status = "Online"
game = data.stream.game
}
console.log(channel, status, game);
$.getJSON("https://api.twitch.tv/kraken/channels/" + channel + "?callback=?",function logoRequest(data) {
name = data.display_name;
if(data.logo === null) {
logo = "http://www.logowik.com/uploads/images/379_twitch.jpg"
} else if (data.logo != null) {
logo = data.logo
}
//set attributes and inner HTML for new elements
image.setAttribute("src",logo);
image.setAttribute("alt","photo of channel's image");
image.className = "image";
p.innerHTML = name;
p.className = "name";
p1.innerHTML = status;
p1.className = "status";
p2.innerHTML = game;
p2.className = "game";
//append elements to the div
div.appendChild(image)
div.appendChild(p)
div.appendChild(p1)
if(status === "Online"){
div.appendChild(p2)
}
div.className = "tv-block";
docFrag.appendChild(div);
console.log(data, name, logo, docFrag);
});
});
});
//append final document fragment to the DOM
container.appendChild(docFrag);
};
`
From what I understand, you should be able to append everything to the a div, then append the div to the fragment. When I run the code, nothing is amended to the DOM. I think it may be because of scoping, or the second json request isn't set up properly

I know this answer comes a year later but as I've just completed the same challenge I thought it might make sense to share my solution as I also had the same problem. It looks like creating a new DOM element and appending all generated divs to it and then appending this element to an existing DOM element is faster than using document fragment. So my structure looks like this:
<body>
<main>
<header>
</header>
</main>
<footer>
</footer>
</body>
And then I append to the main a div#container created in js and containing all generated divs.
Here is a link to the completed project.

Related

How to append multiple elements to a div?

So, I am accessing a to-do list api to make a very basic to-do list app. I am able to display what I call from the api, I have the title, description, and price appended to an h1, h3, and h4 respectively and it then gets displayed to the document when the user fills out a form.
How can I append those three into a div, so that I can apply CSS to each individual to-do or so that i can add a button for when I want to delete or edit the todo?
And I want to do this in plain vanilla JavaScript, if possible.
Here is my JavaScript code and also if there is anything you see in the existing code that you feel can be improved or changed or if there is anything I am doing completely wrong, please tell me. I am still very much a beginner at this, so I can use all the help I can get.
function Todo(title, description, price){
this.title = title;
this.description = description;
this.price = price;
}
document.todo.addEventListener("submit", function(e){
e.preventDefault();
var titleForm = document.todo.title.value;
var descriptionForm = document.todo.description.value;
var priceForm = document.todo.price.value;
var newTodo = new Todo(titleForm, descriptionForm, priceForm);
axios.post("<todo api url>", newTodo).then(function(response){
console.log(response.data);
})
})
axios.get("<todo api url>").then(function(response){
for(var i = 0; i < response.data.length; i++){
var h1 = document.createElement("h1");
var h3 = document.createElement("h3");
var h4 = document.createElement("h4");
var displaytitle = document.createTextNode(response.data[i].title);
var displayDescription = document.createTextNode(response.data[i].description);
var displayPrice = document.createTextNode(response.data[i].price);
h1.appendChild(displaytitle);
h3.appendChild(displayDescription);
h4.appendChild(displayPrice);
document.body.appendChild(h1);
document.body.appendChild(h3);
document.body.appendChild(h4);
}
})
Instead of appending them to document.body, create a DIV and append them to the DIV, then append the DIV to the body.
axios.get("<todo api url>").then(function(response){
for(var i = 0; i < response.data.length; i++){
var h1 = document.createElement("h1");
var h3 = document.createElement("h3");
var h4 = document.createElement("h4");
var div = document.createElement("div");
var displaytitle = document.createTextNode(response.data[i].title);
var displayDescription = document.createTextNode(response.data[i].description);
var displayPrice = document.createTextNode(response.data[i].price);
h1.appendChild(displaytitle);
h3.appendChild(displayDescription);
h4.appendChild(displayPrice);
div.appendChild(h1);
div.appendChild(h3);
div.appendChild(h4);
document.body.appendChild(div);
}
});
If you want to style them with CSS, you probably should give them distinct classes, or give a class to the DIV, e.g.
div.classList.add("todoItem");

create anchors dynamically with in para

I'm writing a javascript program that gets the input in any of the below forms.
"Here is the result \"google\", and \"yahoo\""
or
"Here is a plain result"
and this is stored in a variable say X. and I want to create an anchor tag when ever I come across an anchor tag. I know that a href will by default create an anchor tag but in my case the result is rendered as a text, here is my code that I've tried so far.
var newLink = document.createElement('a');
newLink.href = 'http://google.com';
newLink.innerHTML = 'My anchor';
if (message) {
var x = message;
console.log(x.includes("href"));
if (!x.includes("href")) {
responsePara.appendChild(document.createTextNode(message));
responsePara.appendChild(document.createElement('br'));
} else {
//responsePara.appendChild(document.createTextNode(message));
responsePara.appendChild(newLink);
responsePara.appendChild(document.createElement('br'));
}
}
the output that I'm expecting is in case 1
<p> Here is the result "google", and "yahoo"</p>
in case 2
<p>Here is a plain result</p>
please let me know on how can I do this.
Note I'm using only js, no jquery
I can't see your problem, it should be really easy to implement this, right?
All you need to parse is the input that is coming to HTML. within another element (in your case p element)
UPDATE
I have updated this question, so you can modify (or create if there is not ref) an existing element with not parsed a element or with plain text.
function createOrUpdateCompositeLink(input, ref) {
if (ref) {
var isLinkText = input.match(/href/g);
var elementChild;
if (isLinkText) {
elementChild = document.createElement('span');
elementChild.innerHTML = input;
} else {
elementChild = document.createTextNode(input);
}
ref.appendChild(elementChild);
return ref;
} else {
var element = document.createElement('p');
element.innerHTML = input;
return element;
}
}
/* USAGE */
var message;
var element;
message = "Here is the result ";
message1 = "google\"";
message2 = " something plain text ";
message3 = ", and \"yahoo\"";
var reference = document.querySelector('.ref');
var el;
createOrUpdateCompositeLink(message, reference);
createOrUpdateCompositeLink(message1, reference);
createOrUpdateCompositeLink(message2, reference);
createOrUpdateCompositeLink(message3, reference);
<div class="ref"></div>
I would suggest you consider using jQuery and what you are trying to do becomes:
jQuery(".response").append(message);
I assume that your responsePara variable is defined from an existing <div> somewhere. In my example, that <div> would have a class named response.
<div class="response"></div>
Once you get a message, it gets added to the response div but that one line jQuery() command.

Shortest way to create a DIV

What would be the shortest way to do the following :
var div = document.createElement('div');
div.className = 'divClass';
div.innerHTML = 'Div Content';
... without any external libraries
class Div {
constructor(className, innerHTML) {
let div = document.createElement("div");
div.className = className;
div.innerHTML = innerHTML;
return div;
}
}
let innerHTML = "LOL"
new Div(divClass, innerHTML);
This would be the shortest way to doing it again and again while still having some order inside your code, IMO.
Write a function to do it in one line:
function tag(tagNameAndClass, innerHTML) {
var parts = (tagNameAndClass || 'div').split(/\./g);
var elem = document.createElement(parts.shift());
elem.className = parts.join(' ');
if (innerHTML) elem.innerHTML = innerHTML;
return elem;
}
Examples of uses:
tag('div.divClass', 'Div Content') // <div class="divClass">Div Content</div>
tag('.class-one.class-two', 'Content') // <div class="class-one class-two">Content</div>
tag('h1.super', 'My Super Heading') // <h1 class="super">My Super Heading</h1>
What would be the shortest way to do the following [...]
We can imagine a situation in which the div already exists in the DOM while the CSS style rule display:none ensures it remains absent from the visible document flow.
The following single line in javascript will make the element reappear into the visible document flow:
document.getElementsByClassName('divClass')[0].style.display = 'block';
Probably the best solution I have came up with so far :
var el = function(type,props,appends){
var el = document.createElement(type);
if(props) for(var x in props) el[x] = props[x];
if(appends) for(var x in appends) el.appendChild(appends[x]);
return el;
}
and then when using it (creating a popup with header and body example) :
$title = el('div',{className:'title',innerHTML:'Item Title'});
$remove = el('div',{className:'remove',innerHTML:'X'});
$header = el('div',{className:'header'},[$title,$remove,el('div',{className:'clear'})]);
$body = el('div',{className:'body',innerHTML:'body'});
$el = el('div',{className:'item'},[$header,$body]);

Split SPAN when Inserting HTML Inside of it

I have a setup where I have a large amount of text that is formatted HTML (containing headings and paragraphs)–all inside of a contenteditable div. I've setup a function to insert a custom html entity: <newnote> into the editable div upon clicking a button. This works great except that when inserting the note inside of a span I need to split the span in two and place the <newnote> in between them.
I've looked at lots of functions around the web for inserting text into a DIV, and since it is a requirement that I be inserting HTML it seems like document.createTextNode() is my only choice.
So here is what I've setup thus far, it checks if the parent is a SPAN and then places different content based on that if statement.
function insertNodeAtRange() {
var newRange = rangy.getSelection().getRangeAt(0);
var parentElement = newRange.commonAncestorContainer;
if (parentElement.nodeType == 3) {
parentElement = parentElement.parentNode;
}
var el = document.createElement('newnote');
el.className = "bold";
if (parentElement.tagName == 'SPAN') {
el.appendChild(document.createTextNode(" </span>Test<span> "));
} else {
el.appendChild(document.createTextNode(" Test "));
}
newRange.insertNode(el);
}
Here is what I have sofar
Thanks in advance for any help...
The main problem with your code is that you were trying to make/modify html by creating a text node. Text nodes do not get parsed as dom.
This should work for you:
JSFiddle
function insertNodeAtRange() {
var newRange = rangy.getSelection().getRangeAt(0);
var parentElement = newRange.commonAncestorContainer;
if (parentElement.nodeType == 3) {
parentElement = parentElement.parentNode;
}
var el = document.createElement('newnote');
el.className = "bold";
el.appendChild(document.createTextNode(" Test "));
newRange.insertNode(el);
if (parentElement.tagName == 'SPAN') {
var $newnote = $(el);
var $span1 = $("<span>");
var $span2 = $("<span>");
var left = true;
$parentElement = $(parentElement);
$parentElement.contents().each(function (i, node) {
if (node.isSameNode(el)) {
left = false;
} else if (left) {
$span1.append(node);
} else {
$span2.append(node);
}
});
$parentElement.replaceWith($newnote);
$span1.insertBefore($newnote);
$span2.insertAfter($newnote);
}
}
I just went ahead and inserted the newnote element right away, then got the stuff before that and put it into span1, got the stuff after it and put it into span2, replaced the existing span with newnote and positioned the new spans around newnote.

Manipulating DOM data without affecting the view

<!DOCTYPE html>
<html><body>
<p id="intro">Hello <em id="abcd">intro</em> World!</p>
<script type="text/javascript">
var txt=document.getElementById("intro").innerHTML;
var el = document.createElement("span");
el.innerHTML = txt;
var aa = el.getElementById("abcd").innerHTML;
alert( aa );
</script>
</body></html>
The above is a simple snippet. Actually I have an HTML editor and when the user saves the data I should save only the required content. Here I am getting the content of an element and manipulating it with DOM and pass the details to the server. This way I will not change the page content (user view remains the same) and he/she will continue editing the document.
The above is a simple example but in the real case I have to remove, change and move certain elements. The above code fails el.getElementById("abcd").innerHTML. Appreciate any pointers.
You can create a hidden iframe to manipulate all your changes, thus creating a separate DOM, then simply pull back the results you want.
var iframe;
if (document.createElement && (iframe = document.createElement('iframe'))) {
iframe.name = iframe.id = "externalDocument";
iframe.className = "hidden";
document.body.appendChild(iframe);
var externalDocument;
if (iframe.contentDocument) {
externalDocument = iframe.contentDocument;
} else if (iframe.contentWindow) {
externalDocument = iframe.contentWindow.document;
}
else if (window.frames[iframe.name]) {
externalDocument = window.frames[iframe.name].document;
}
if (externalDocument) {
externalDocument.open();
externalDocument.write('<html><body><\/body><\/html>');
externalDocument.close();
/* Run your manipulations here */
var txt = document.getElementById("intro").innerHTML;
var el = document.createElement("span");
el.innerHTML = txt;
/* Attach your objects to the externalDocument */
externalDocument.body.appendChild(el);
/* Reference the externalDocument to manipulate */
var aa = externalDocument.getElementById("abcd").innerHTML;
alert(aa);
}
/* Completed manipulation - Remove iFrame */
document.removeChild(iframe);
}
I have it working here:
http://jsfiddle.net/ucpvP/
Try using jQuery like given below.
function SaveData() //Your add function
{
var txt=$("#intro").html();
$(document).append("<span id='abcd'>" + txt+ "</span>");
var aa = $("#abcd").hmtl();
alert(aa);
}
You can use a DOM Element that is never appended to the DOM.
I use this 'cleanup' function:
function cleanup(str){
var tester = document.createElement('div'),
invalid, result;
tester.innerHTML = str;
//elements I don't allow
invalid = tester.querySelectorAll('script,object,iframe,style,hr,canvas');
// the cleanup (remove unwanted elements)
for (var i=0;i<invalid.length;(i+=1)){
invalid[i].parentNode.removeChild(invalid[i]);
}
result = tester.innerHTML;
tester = invalid = null;
//diacritics to html-encoded
return result.replace(/[\u0080-\u024F]/g,
function(a) {return '&#'+a.charCodeAt(0)+';';}
)
.replace(/%/g,'%25');
}
//usage:
cleanup(document.getElementById("intro").innerHTML);
You can extend the function with your own code to remove, change and move certain elements.

Categories