Most efficient way to create a div with several children - javascript

I'm trying to create a function which takes an object with a few parameters and returns a newly created div.
From what i can see, there seem to be two main ways to accomplish this:
creating each element by itself and appending it
creating a template literal and set the divs innerHTML
the inputs of the functions are not user generated, so i don't think using template literals will create a security issue (please educate me if i'm wrong)
So now my questions are the following:
is one more efficient than the other?
is one preferred?
are there any other concerns?
is there an even more efficient/better way?
below you can see the two solutions i've come up with.
function createDiv (entry) {
const div = document.createElement('div')
div.classList.add('exchange')
div.id = entry.exchange
const img = document.createElement('img')
img.src = `/static/img/${entry.img}.png`
img.alt = entry.name
img.classList.add('logo-image')
div.appendChild(img)
const link = document.createElement('a')
link.href = entry.url
link.classList.add('name')
link.innerText = entry.name
div.appendChild(link)
const routing = document.createElement('span')
routing.innerText = entry.routing ? entry.routing : ''
div.appendChild(routing)
const price = document.createElement('span')
price.innerText = entry.price
price.classList.add('price')
div.appendChild(price)
return div
}
function createDiv (entry) {
const div = document.createElement('div')
div.classList.add('exchange')
div.id = entry.exchange
let text = `
<img class="logo-image" src="/static/img/${entry.img}.png" alt="${entry.name}">
<a class="exchange-name" href="${entry.url}">${entry.name}</a>
<span>${routing.innerText = entry.routing ? entry.routing : ''}</span>
<span class="price">${entry.price}</span>
`
div.innerHTML = text
return div
}
Thank you in advance!

What about doing something like the following?
const createDiv = ({ exchange, img, name, url, routing: entryRouting, price }) => {
return `
<div class="exchange" id="${exchange}">
<img class="logo-image" src="/static/img/${img}.png" alt="${name}">
<a class="exchange-name" href="${url}">${name}</a>
<span>${routing.innerText = entryRouting || ''}</span>
<span class="price">${price}</span>
</div>
`;
}
In this case you are getting the full power of the template literals and of the object destructing.
About the values, you should validate them in some way before storing in the database and sanitize the HTML before getting it back. Some sort of easy validation with regex could be enough for validation. For sanitizing you can choose one of the many libraries like the this https://www.npmjs.com/package/sanitize-html.
About performances, I wouldn't take it too seriously until you do many iterations. As far as I see it is a onetime function call. So I would go for the cleaner way: template strings. But if you are curious, the template string is the fastest. The first approach is almost 100% slower. You can check the results of the test I did over 100 iterations here https://jsbench.me/7gkw1t31rs/2.
Remember that the approach I am telling you will need an innerHTML once the createDiv function returns its value.

Related

How do I delete the element that I clicked?

I have a form with input fields whose values are to be stored in an array in the form of an object. At the same time, these values are displayed in the browser. Now I want to allow the user to delete the displayed values, i.e. the created object. Unfortunately, the function I have set up so far "deletes" very randomly (that's how it seems to me). What do I have to change so that the function deletes the element that is clicked?
HTML:
<div class="readContainer">
<label for="radio">read?</label>
<div class="checkReadContainer">
<input type="checkbox" id="read" name="read" value="yes">
</div>
</div>
<button id="btn" onclick="createBook();">submit</button>
</div>
<div class="displayMessageContainer">
<p id="displayBook">๐Ÿ“š store up to 10 books ๐Ÿ“š</p>
<p id="infoTestVersion"> or sign up for our free trial and get 30 days of unlimited
storage for your books*</p>
</div>
<div class="Display">
<p class="display"> </p>
</div>
<footer>
<div class="footerContainer">
<div class="madeBy">
<h1> made by anitakath <i class="fa-brands fa-github"></i> </h1>
</div>
</div>
</footer>
JavaScript:
const labels = document.querySelectorAll("label");
const inputs = document.querySelectorAll("input");
const title = document.getElementById("title");
const author = document.getElementById("author");
const pages = document.getElementById("pages");
const button = document.getElementById("btn");
const displayArea = document.querySelector(".display");
let myLibrary = [];
function Book(title, author, pages) {
this.title = title.value;
this.author = author.value;
this.pages = pages.value;
}
Book.prototype.introduction = function () {
return `${this.title} by ${this.author}, `;
};
Book.prototype.introductionII = function () {
return `with ${this.pages} pages`;
};
let errorMessage = document.getElementById("displayBook");
function createBook() {
if (title.value && author.value && pages.value) {
let bookX = new Book(title, author, pages);
let card = document.createElement("div");
card.classList.add("cardStyle");
displayArea.appendChild(card);
let cardSectionLeft = document.createElement("div");
let cardSectionRight = document.createElement("div");
cardSectionLeft.classList.add("cardLeft");
cardSectionRight.classList.add("cardRight");
card.appendChild(cardSectionLeft);
card.appendChild(cardSectionRight);
let deleteButton = document.createElement("div");
let deleteContent = document.createElement("p");
deleteContent.innerText = "X";
deleteButton.classList.add("deleteButton");
deleteButton.appendChild(deleteContent);
cardSectionRight.appendChild(deleteButton);
deleteButton.setAttribute("onclick", "deleteBook();");
let cardIntro = document.createElement("h3");
let cardOutro = document.createElement("p");
cardIntro.innerText = bookX.introduction();
cardOutro.innerText = bookX.introductionII();
cardSectionLeft.appendChild(cardIntro);
cardSectionRight.appendChild(cardOutro);
bookArray = [title.value, author.value, pages.value];
const bookArrayToObject = Object.assign({}, bookArray);
console.log(bookArrayToObject);
myLibrary.push(bookArrayToObject);
console.log(myLibrary);
console.log(myLibrary[0]);
console.log(myLibrary[1]);
console.log(myLibrary[2]);
const radio = document.getElementById("read");
if (radio.checked) {
card.classList.add("read");
} else if (!radio.checked) {
card.classList.add("notread");
}
errorMessage.innerText = `saved: ${this.title.value} by ${this.author.value}, with ${this.pages.value} pages`;
errorMessage.style.color = "green";
} else if (!title.value || !author.value || !pages.value) {
errorMessage.innerText =
"please make sure you have filled in all input fields";
errorMessage.style.color = "red";
}
}
function deleteBook() {
for (let i = 0; i < myLibrary.length; i++) {
console.log(myLibrary.splice(i, 1));
}
}
The splice method on JavaScript arrays mutates the array in place. This means that, once it's run (even if you're just logging the result, etc), it's already actually changed the array.
Let's take a closer look at what it does, from the MDN page:
The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
start: Zero-based index at which to start changing the array
deleteCount: An integer indicating the number of elements in the array to remove from start.
Returns: An array containing the deleted elements.
I believe you're probably expecting that return value to be the new array, with just the element you want deleted, and that's why the result looks like it's deleting a "random" element as you put it.
So, lets look at how to delete correctly, either you can take in the index of the book, or you can take in the book itself and delete it (I think that's the better approach, but you'll have to do a little refactoring to get there).
function deleteBookByIndex(bookIndex) {
myLibrary.splice(bookIndex, 1);
}
function deleteBook(book) {
const bookIndex = myLibrary.indexOf(book);
myLibrary.splice(bookIndex, 1);
}
You will, of course, also need to think about how updates to your internal data store are reflected in the HTML, e.g. to remove the element. Ideally maybe your Book class keeps some reference to the DOM elements that you create, so you can directly update them, as that would be cleaner than having to go searching through the DOM to find them again later.
A few notes on your code as it stands:
deleteButton.setAttribute("onclick", "deleteBook();")
Using the onclick etc attributes on HTML is very much not the recommended way to add event listeners anymore, as it only allows for one, and you have to write JavaScript code as a string, which is problematic for many reasons --- especially in a case like this one, where we'll want to pass an argument in. Instead, lets do it like so:
deleteButton.addEventListener("click", () => deleteBook());
You seem to create both an class instance (let bookX = new Book(title, author, pages);), an array (bookArray = [title.value, author.value, pages.value]) and an object (Object.assign({}, bookArray)) to represent your book. My advice would be to pick one representation and stick with it.
Use ES6 classes rather than the prototype approach you have here at the moment if you choose to stick with using the Book class.

Wrapping a string in a div in pure javascript

Is there a way in pure javascript to wrap a nacked string?
I have a string that I'm splitting based on a character to separate the header from the rest of the content. I would very much like to style that header, but i can't seem to find a good way to wrap a div around it with a class.
All I can seem to find is wrapping a div around something that already has other elements.
My code looks like this
var string = "Title*This is the very long content";
var title = string.split('*')[0]
var body = string.split('*')[1]
//put them back together
string = title + body;
but i can't seem to find a good way to wrap a div around it with a
class?
You can create an element (which is at the end a tag HTML) with createElement
and attach it a class with className
let string = "Title*This is the very long content";
/*
let title = string.split('*')[0]
let body = string.split('*')[1]
*/
let [title, body] = string.split('*'); // Destructuring assignment
let headerTitle = document.createElement('h1');
headerTitle.textContent = title;
headerTitle.className = "red";//headerTitle.classList.add('red');
let bodyHTML = document.createElement('p');
bodyHTML.textContent = body;
document.querySelector('#content').innerHTML = headerTitle.innerHTML +"<br/>"+ bodyHTML.innerHTML;
.red{
color: red;
}
<div id="content" />
Tip Try to avoid var keyword for declaring variable and replace them with either let or const and better using Destructuring assignment

Create element and assign class in one step

Here are two ways that I know to create an element and assign it a class:
const el = document.createElement('div');
el.classList.add('foo');
const el = document.createElement('div');
foo.className = 'foo';
Is there a one-step solution for it? I tried
const el = document.createElement('div').classList.add('foo');
but it doesn't work.
Although the usual way to do it is a two-step technique, try this:
const el = Object.assign(document.createElement('div'), { className: 'foo' });
console.log(el);
console.log(el.className);
You're stuck with either using an HTML string and innerHTML or trying jQuery which allows the chaining of commands, but for something so small it doesn't make sense to bring jQuery into the mix, what's the reason you needed a one-liner?
For a rough working example in vanilla js
var myHTML = "<div class='hello'></div>";
document.body.innerHTML += myHTML

Want JS to continue if ID (document.getElementById) isn't found

I'm trying to make a javascript just "ignore" a missing div-id and keep running down the lines. Been searching for solutions, but most of them are either replacing the missing ID or putting information into an already existing one. I just want my script to be "okay" with the fact that certain ID's will not be found.
var Info01 = `Some text`;
document.getElementById("Info01").innerHTML = Info01;
var Info02 = `Some other text`;
document.getElementById("Info02").innerHTML = Info02;
If the div-id "Info01" isn't present, I want it to just be cool with that and do the next line and so on.
I've been trying some if-statements, but I'm just not good enough to figure it out by myself and google isn't providing me with the solution I'm looking for.
Hopefully someone can help!
Going a bit further with Zachary McGee's answer. You could avoid some repetition (and fetching twice the id within DOM):
const info01 = "Some text"
const info02 = "Some other text";
const setText = (id, content) => {
const item = document.getElementById(id)
if (item === null) return
item.innerText = content
}
setText("Info01", info01)
setText("Info02", info02)
<div id="Info02"></div>
Also not that I am using .innerText rather than .innerHTML, since the former is sufficient for the needs of your question and the latter is subject to XSS.
Try something like this:
Check if the element with that ID exists first.
var Info01 = "Some text";
if(document.getElementById("Info01")) {
document.getElementById("Info01").innerHTML = Info01;
}
var Info02 = "Some other text";
if(document.getElementById("Info02")) {
document.getElementById("Info02").innerHTML = Info02;
}

Using getElementsByTagName to find all hrefs in a variable

In a variable I'm holding HTML source code, which I obtained from DB. I'd like to search this content through for all the "a href" attributes and list them in a table.
Now I've found here how to search it in a DOM (like below), but how to use it to search within a variable?
var links = document.getElementsByTagName("a").getElementsByAttribute("href");
Got this currently, which is searching by RegEx, but it doesn't work very well:
matches_temp = result_content.match(/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?ยซยปโ€œโ€โ€˜โ€™&quote]))/ig);
In result_content I'm holding that HTML Source.
getElementsByTagName returns a nodelist that does not have a method called getElementsByAttribute but ONLY if you have DOM access
Without DOM (for example node.js)
const hrefRe = /href="(.*?)"/g;
const urlRe = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?ยซยปโ€œโ€โ€˜โ€™&quote]))/ig;
const stringFromDB = `000
Something something 001 something`
stringFromDB.match(hrefRe).forEach(
(href) => console.log(href.match(urlRe)[0] )
);
// oldschool:
// stringFromDB.match(hrefRe).forEach(function(href) { console.log(href.match(urlRe)[0] ) });
In this code I create a DOM snippet first
Also I ONLY get anchors that have an href to begin with
NOTE the getAttribute so the browser does not try to interpret the URL
With the regex if you wanted to only match SPECIFIC types of href:
const re = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?ยซยปโ€œโ€โ€˜โ€™&quote]))/ig;
const stringFromDB = `000
001`
let doc = document.createElement("div");
doc.innerHTML = stringFromDB
doc.querySelectorAll("a[href]").forEach(
(x) => console.log(x.getAttribute("href").match(re)[0])
);
Without the regex
const stringFromDB = `000
001`
let doc = document.createElement("div");
doc.innerHTML = stringFromDB
doc.querySelectorAll("a[href]").forEach(
(x) => console.log(x.getAttribute("href"))
);
Firstly, you shouldn't be using RegEx to parse HTML. This answer explains why.
Secondly, you're using getElementsByAttribute incorrectly - it does exactly what it says and gets elements by attributes. You should just use querySelectorAll on all elements with a href, and then map out the hrefs:
var hrefs = document.querySelectorAll("a[href*=http]");
var test = Array.prototype.slice.call(hrefs).map(e => e.href);
console.log(test);
Example
Example 1
Example 2
Example 3

Categories

Resources