Is it okay to append a template element's content to DocumentFragment? - javascript

This code is working fine as expected.
index.html
<div id="sliderContent">
<div class="item" id="item-1">
<img src="images/image1.jpg">
</div>
</div>
<template id="sliderItem">
<div class="item">
<img src="dummy_image.jpg">
</div>
</template>
scripts.js
const next = document.querySelector("#next");
const sliderContent = document.querySelector("#sliderContent");
next.addEventListener('click', () => {
const sliderItem = document.querySelector("#sliderItem");
const moreImages = ['image2.jpg', 'image3.jpg', 'image4.jpg'];
// Create a DocumentFragment first
const moreImagesHTML = document.createDocumentFragment();
moreImages.forEach((item) => {
const sliderItemClone = sliderItem.content.cloneNode(true);
const sliderImage = sliderItemClone.querySelector('img');
sliderImage.src = `images/${item}`;
// Append each item to the DocumentFragment
moreImagesHTML.append(sliderItemClone);
});
// Finally append DocumentFragment to
sliderContent.append(moreImagesHTML);
I am not sure if this is valid and standard way in JS. I am appending the content of the template which is already a DocumentFragment to another DocumentFragment i.e. moreImagesHTML. Is Valid in JS?

Related

JavaScript Error when submitting HTML form: Form is NULL [duplicate]

This question already has answers here:
Why does jQuery or a DOM method such as getElementById not find the element?
(6 answers)
Closed 1 year ago.
My Issue:
Please help me run this code as it should. I am getting a null form error when typing a City name in the place holder and I'm not sure why I am practicing this code from here: https://webdesign.tutsplus.com/tutorials/build-a-simple-weather-app-with-vanilla-javascript--cms-33893
/*SEARCH BY USING A CITY NAME (e.g. athens) OR A COMMA-SEPARATED CITY NAME ALONG WITH THE COUNTRY CODE (e.g. athens,gr)*/
const form = document.querySelector(".top-banner form");
const input = document.querySelector(".top-banner input");
const msg = document.querySelector(".top-banner .msg");
const list = document.querySelector(".ajax-section .cities");
/*SUBSCRIBE HERE FOR API KEY: https://home.openweathermap.org/users/sign_up*/
const apiKey = "f077e7d6167270fa866a36699ab528fe"; /*REPLACE THIS WITH YOUR API KEY FROM OPENWEATHERMAP.ORG*/
form.addEventListener("submit", e => {
e.preventDefault();
let inputVal = input.value;
//check if there's already a city
const listItems = list.querySelectorAll(".ajax-section .city");
const listItemsArray = Array.from(listItems);
if (listItemsArray.length > 0) {
const filteredArray = listItemsArray.filter(el => {
let content = "";
//athens,gr
if (inputVal.includes(",")) {
//athens,grrrrrr->invalid country code, so we keep only the first part of inputVal
if (inputVal.split(",")[1].length > 2) {
inputVal = inputVal.split(",")[0];
content = el
.querySelector(".city-name span")
.textContent.toLowerCase();
} else {
content = el.querySelector(".city-name").dataset.name.toLowerCase();
}
} else {
//athens
content = el.querySelector(".city-name span").textContent.toLowerCase();
}
return content == inputVal.toLowerCase();
});
if (filteredArray.length > 0) {
msg.textContent = `You already know the weather for ${
filteredArray[0].querySelector(".city-name span").textContent
} ...otherwise be more specific by providing the country code as well 😉`;
form.reset();
input.focus();
return;
}
}
//ajax here
const url = `https://api.openweathermap.org/data/2.5/weather?q=${inputVal}&appid=${apiKey}&units=metric`;
fetch(url)
.then(response => response.json())
.then(data => {
const {
main,
name,
sys,
weather
} = data;
const icon = `https://s3-us-west-2.amazonaws.com/s.cdpn.io/162656/${
weather[0]["icon"]
}.svg`;
const li = document.createElement("li");
li.classList.add("city");
const markup = `
<h2 class="city-name" data-name="${name},${sys.country}">
<span>${name}</span>
<sup>${sys.country}</sup>
</h2>
<div class="city-temp">${Math.round(main.temp)}<sup>°C</sup></div>
<figure>
<img class="city-icon" src="${icon}" alt="${
weather[0]["description"]
}">
<figcaption>${weather[0]["description"]}</figcaption>
</figure>
`;
li.innerHTML = markup;
list.appendChild(li);
})
.catch(() => {
msg.textContent = "Please search for a valid city 😩";
});
msg.textContent = "";
form.reset();
input.focus();
});
<!DOCTYPE html>
<html>
<head>
<script src="main.js"></script>
</head>
<body>
<div class="api">
<div class="container">🌞 This demo needs an OpenWeather API key to work. <a target="_blank" href="https://home.openweathermap.org/users/sign_up">Get yours here for free!</a>
</div>
</div>
<section class="top-banner">
<div class="container">
<h1 class="heading">Simple Weather App</h1>
<form>
<input type="text" placeholder="Search for a city" autofocus>
<button type="submit">SUBMIT</button>
<span class="msg"></span>
</form>
</div>
</section>
<section class="ajax-section">
<div class="container">
<ul class="cities"></ul>
</div>
</section>
<footer class="page-footer">
<div class="container">
</div>
<small>Made with <span>❤</span> by George Martsoukos
</small>
<li class="city">
<h2 class="city-name" data-name="...">
<span>...</span>
<sup>...</sup>
</h2>
<span class="city-temp">...<sup>°C</sup></span>
<figure>
<img class="city-icon" src="..." alt="...">
<figcaption>...</figcaption>
</figure>
</li>
</footer>
</body>
</html>
It's because your javascript code is executed before DOM is fully loaded.
So you have two choices, either move
<script src="main.js"></script> as the last item inside body (before </body>)
or place all your javascript code inside:
document.addEventListener("DOMContentLoaded", e =>
{
// your code here
});

Is there a way to read html title from a string

I have some javascript variable in which it has html script something like below:
<div style="display: inline-block;" class="ms-fontColor"><i title="Sample Text"></i></div>
I want to read just the text that is inside the title tag using javascript or JQuery and get rid of everything else. Is there any way that I can achieve this?
const el = document.createElement( 'div' );
el.innerHTML = `<div style="display: inline-block;" class="ms-fontColor"><i title="Sample Text"></i></div>`;
// If there are more than one <i> elements with title attribute you might wanna do this
const innerEls = el.querySelectorAll('i[title]')
innerEls.forEach(el => {
const value = el.getAttribute('title') // or el.title
})
// If you just want to get the only or the first <i> with title attribute
const innerEl = el.querySelector('i[title]')
//Here's the attribute value
const value = innerEl.getAttribute('title') // or innerEl.title
You can use a DOMParser.
let str = '<div style="display: inline-block;" class="ms-fontColor"><i title="Sample Text"></i></div>';
let doc = new DOMParser().parseFromString(str, "text/html");
console.log(doc.querySelector('i[title]').title);
It took me a second to realize you meant get the title from an HTML String instead of from actual DOM elements. This will convert the string to an html element for jquery:
var $elem = $(document.createElement(htmlString));
var title = $elem.is("[title]")
? $elem.prop("[title]")
: $elem.find("[title]").prop("title");
A simple regex can help with this.
const str = '<div style="display: inline-block;" class="ms-fontColor"><i title="Sample \"Text"></i></div>';
const reg = /title=('|")(.*)\1/;
console.log(str.match(reg)[2]);
There are a few methods you can employ to achieve this some of which depend on the structure of your HTML string. The second approach below works for irrespective of what the structure of your HTML string is.
const str = '<div style="display: inline-block;" class="ms-fontColor"><i title="Sample Text"></i></div><div style="display: inline-block;" class="ms-fontColor"><i title="Sample Text 2"></i></div><div style="display: inline-block;" class="ms-fontColor"><i title="Sample Text 3"></i></div>';
const titles = $(str).find('i[title]').map((index, elem) => elem.title).get();
console.log( titles );
//When the i element is at the top of the hierarchy
const str1 = '<i title="Sample Text"></i><i title="Sample Text 2"></i><i title="Sample Text 3"></i><i title="Sample Text 4"></i><div title="This other sample too">watch this</div>'
//approach 1
const t1 = $(str1).filter('i[title]').map((index, elem) => elem.title).get();
console.log( t1 );
//this approach would not work for `str`
//approach 2
const t21 = $('<div/>').append(str1).find('i[title]').map((index, elem) => elem.title).get();
console.log( t21 );
//this would work for `str` as well
const t22 = $('<div/>').append(str).find('i[title]').map((index, elem) => elem.title).get();
console.log( t22 );
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Get the element using querySeletor and on that object just fetch the title using iElement.title
const iElement = document.querySelector("i");
const title = iElement.title;
console.log( title );
<div style="display: inline-block;" class="ms-fontColor">
<i title="Sample Text"></i>
</div>

How to create nested elements in JavaScript?

Here's my HTML. I target to create elements in Javascript and insert it into div with the class "wrap" but didn't succeed.
<div class="wrap">
<div class="shelf">
<div class="shelf-background">
<div class="base">
</div>
</div>
</div>
</div>
var base = document.createElement('div');
var shelfBackground = document.createElement('div');
var wrap = document.querySelector(".wrap");
var shelf = document.createElement('div');
shelfBackground.className = "shelf-background";
base.className = "base";
shelf.className = "shelf";
shelfBackground.appendChild(base);
wrap.append(shelf, shelfBackground, shelfBackground.appendChild(base));
I get
<div class="wrap">
<div class="shelf"></div>
<div class="shelf-background"></div>
<div class="base"></div>
</div>
Right now, you are appending base to the background, and then appending all of the elements to the wrap element at the top level. Also note, when you call shelfBackground.appendChild(base), it returns the appended child base which is why it is the last element in your output structure.
What you need to do is instead append the elements based to their respective parents, i.e.:
...
// Build the structure from the bottom up
shelfBackground.appendChild(base); // shelf-background > base
shelf.appendChild(shelfBackground); // shelf > shelf-background > base
wrap.appendChild(shelf); // wrap > shelf > shelf-background > base
Try this:
var wrap = document.querySelector(".wrap");
var base = document.createElement('div');
var shelfBackground = document.createElement('div');
var shelf = document.createElement('div');
base.className = "base";
shelfBackground.className = "shelf-background";
shelf.className = "shelf";
shelfBackground.appendChild(base);
shelf.appendChild(shelfBackground);
wrap.appendChild(shelf);
document.appendChild(wrap);

Create multiple div using data from array using for loop

I want to assign array elements to multiple divs which also have image tag in them using for loop.
Array consists of image paths.
var img_list = ["one.png", "two.png", "three.png", "four.png"];
By using above array I have to create below HTML Structure. All divs should be inside "outer" div with a data-slide attribute which is without ".png".
<div id="outer">
<div class="slide" data-slide="one"><img src="Images/one.png" /></div>
<div class="slide" data-slide="two"><img src="Images/two.png" /></div>
<div class="slide" data-slide="three"><img src="Images/three.png" /></div>
<div class="slide" data-slide="four"><img src="Images/four.png" /></div>
</div>
This is what I wrote:
for (var i=0; i < img_list.length; i++){
var container = document.getElementById("outer").innerHTML;
var new_card = "<div class=\"slide\" data-slide=\'" + img_list[i] + "\'><img src=\'Images/" + img_list[i] + "\' /></div>";
document.getElementById("outer").innerHTML = new_card;
}
But it is only showing the last image.
Please help.
Each time your for loop runs, it is replacing the html element within the "outer" div with the current img html.
In order to have it append you can simply change
document.getElementById("outer").innerHTML = new_card;
to
document.getElementById("outer").innerHTML += new_card; so that the element is appended rather than overwritten.
The code at the question overwrites the .innerHTML within the for loop by setting .innerHTML to new_card at every iteration of the array. You can substitute .insertAdjacentHTML() for setting .innerHTML. Also, substitute const for var to prevent new_card from being defined globally. Include alt attribute at <img> element. You can .split() img_list[0] at dot character ., .shift() the resulting array to get word before . in the string img_list[i] to set data-* attribute value.
const img_list = ["one.png", "two.png", "three.png", "four.png"];
for (let i = 0, container = document.getElementById("outer"); i < img_list.length; i++) {
const src = img_list[i];
const data = src.split(".").shift();
const new_card = `<div class="slide" data-slide="${data}"><img src="Images/${src}" alt="${data}"/></div>`;
container.insertAdjacentHTML("beforeend", new_card);
}
<div id="outer"></div>
You are changing the innerHTML you need to add to it. And use Template literals for creating html strings
var img_list = ["one.png", "two.png", "three.png", "four.png"];
const outer = document.getElementById('outer')
img_list.forEach(img => {
outer.innerHTML += `<div class="slider" data-slide="${img.split('.')[0]}"><img src="Images/${img}" /></div>`
})
console.log(outer.innerHTML)
<div id="outer">
</div>
#Tony7931, please replace the last line of for loop with below code:
document.getElementById("outer").innerHTML += new_card;
You are almost there but, every time you are overriding the slider div.
You just have to add + at assignments section. like below.
document.getElementById("outer").innerHTML += new_card;
Here is the full example:
var img_list = ["one.png", "two.png", "three.png", "four.png"];
for (var i=0; i < img_list.length; i++){
var container = document.getElementById("outer").innerHTML;
var new_card = "<div class=\"slide\" data-slide=\'" + img_list[i].split('.')[0] + "\'><img src=\'Images/" + img_list[i] + "\' /></div>";
document.getElementById("outer").innerHTML += new_card;
}
<div id="outer"></div>
You could also use map method of array and Element.innerHTML to get the required result.
The map() method creates a new array with the results of calling a provided function on every element in the calling array.
Demo
const img_list = ["one.png", "two.png", "three.png", "four.png"];
let result = img_list.map((v, i) => `<div class="slide" data-slide="${v.split(".")[0]}"><img src="Images/${v}"/>${i+1}</div>`).join('');
document.getElementById('outer').innerHTML = result;
<div id="outer"></div>
You can declare your variables outside of the loop and reuse them. This is more efficient.
const and let are preferable to var. You only need to set container once so it can be const. new_card should be let since you need to assign it more than once.
You can also use template string so you don't need all the back slashes.
using forEach will make the code cleaner:
const img_list = ["one.png", "two.png", "three.png", "four.png"];
const container = document.getElementById("outer")
let new_card;
img_list.forEach(i => {
new_card = `<div class=slide data-slide='${i.split(".")[0]}'><img src='Images/${i}'></div>`
container.innerHTML += new_card;
})
<div id="outer">
</div>
Alternately, using reduce:
const img_list = ["one.png", "two.png", "three.png", "four.png"];
const container = document.getElementById("outer")
const reducer = (a, i) => a + `<div class=slide data-slide='${i.split(".")[0]}'><img src='Images/${i}'></div>`
container.innerHTML = img_list.reduce(reducer, '')
<div id="outer">
</div>

document.querySelector returns null

I use the querySelector in JS to select html markup that I filled in with a JS script. However, anytime I try to store the divs with a class of .card in the const questionCard, I get null.
Why can't I select the cards?
HTML:
<div class='card-container'></div>
JS:
const questionBlock = document.querySelector('.card-container');
const questionCard = document.querySelector('.card');
function build() {
let output = [];
...some unimportant code here...
output.push(
`
<div class='card'>
<div class='question'>${current.question}</div>
<div class='answers'>${answers.join('')}</div>
</div>
`
);
})
questionBlock.innerHTML = output;
}
build();
You need to call document.querySelector('.card') after calling build(). It cannot find HTML elements that do not exist yet.
const questionBlock = document.querySelector('.card-container');
function build() {
let output = [];
...some unimportant code here...
output.push(
`
<div class='card'>
<div class='question'>${current.question}</div>
<div class='answers'>${answers.join('')}</div>
</div>
`
);
})
questionBlock.innerHTML = output;
}
build();
const questionCard = document.querySelector('.card');
An alternative to the more correct answers is:
const questionCard = document.getElementsByClassName('card');
now: questionCard is a live HTMLCollection, and questionCard[0] will be the first element with class including card

Categories

Resources