Is there a less convoluted way of writing this in JavaScript? - javascript

I'm creating an array of DOM elements (HTML) without having to look up into the DOM, like so:
const frames = [
...document.createRange()
.createContextualFragment(
new String(
new Array(options.length)
.fill()
.map((v, i) => `<div class="page"><iframe src="./renders/page-${i + 1}"></iframe></div>`)
)
)
.querySelectorAll('div')
].map((page, index) => _addPageWrappersAndBaseClasses(page, index))
Is there a more sane way of doing this?

Sure, just don't put everything on one line. Instead make sensible use of variables and line breaks.
const divStrings = new Array(options.length)
.fill()
.map((v, i) =>
`<div class="page"><iframe src="./renders/page-${i + 1}"></iframe></div>`
)
const frag = document.createRange().createContextualFragment(new String(foo))
const divs = [...frag.querySelectorAll('div')]
const frames = divs.map(_addPageWrappersAndBaseClasses)
Only change to the code above, other than the formatting and variables, is that the anonymous callback to .map at the end was extraneous. I changed it to pass the function being called directly.
You could get rid of a variable or two, so it doesn't need to be quite that drawn out, but there's not really any benefit to packing everything into a single expression.

Yes, it would be both more efficient and more concise to use regular DOM methods.
for(let i = 0; i < options.length; i++) {
const page = document.createElement("div");
page.className = "page";
const frame = document.createElement("iframe");
frame.src = `./renders/page-${i + 1}`;
page.appendChild(frame);
_addPageWrappersAndBaseClasses(page, i);
}
You should also try to avoid parsing HTML with strings in JavaScript.

Related

Most efficient way to create a div with several children

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.

is there a way to avoid the nested loop in this code?

I'm building a chrome extension that lets the user replace words with tiny images. this is the code I have.
lookup=[['text','img.png']...];
var text = document.querySelectorAll('h1,h2,h3,h4,h5,p,li,td,caption,span,a')
for (let i=0; i< text.length; i++) {
var height = window.getComputedStyle(text[i]).fontSize
for (let j=0;j<lookup.length;j++){
text[i].innerHTML = text[i].innerHTML.replace(new RegExp(lookup[j][0],"gi"),"<img src=\"img/"+lookup[j][1]+"\" width=\""+height+"\" height=\""+height+"\">");
}
}
since this code has to run every time any text in the page changes I'm afraid the nested loop might cause serious performance degradation. is there anything in javascript that can avoid it?
You could create an object lookup and create a regex with alternation |. Use a function as the second parameter in replace, and use the lookup object to get the image based on the match
const lookup= { 'text': 'img.png' },
elements = document.querySelectorAll('h1,h2,h3,h4,h5,p,li,td,caption,span,a'),
regex = new RegExp(Object.keys(lookup).join("|"), 'gi')
elements.forEach(e => {
const height = window.getComputedStyle(e).fontSize;
e.innerHTML = e.innerHTML.replace(regex, m => `<img src="img/${lookup[m]}" width="${height}" />`)
})

How to replace 2 values at one replace call

So i am wondering how i can replace two or more at once with single replace call.
I haven't tried anything so far, as i don't have a clue how i can do that.
let links = {
_init: "https://%s.website.com/get/%s",
}
So as you can see here i have a link with 2x %s which i want to replace.
I am thinking about something like this:
links._init.replace('%s', 'name', 'query')
obviously it won't work. So i am wondering if there is other way of doing it.
I know that languages like python, c# etc have similar feature.
One option is to use a replacer function, and an array that you shift() from:
let links = {
_init: "https://%s.website.com/get/%s"
};
const replaceWith = ['name', 'query'];
const replaced = links._init.replace(
/%s/g,
() => replaceWith.shift()
);
console.log(replaced);
If you don't want to mutate the existing array, you can also use a counter that gets incremented on every iteration:
let links = {
_init: "https://%s.website.com/get/%s"
};
const replaceWith = ['name', 'query'];
let i = 0;
const replaced = links._init.replace(
/%s/g,
() => replaceWith[i++]
);
console.log(replaced);

Is there a more javascript way to handle zipping two arrays instead of using a for loop?

This code works as intended based on the problem I am supposed to solve. However, as I am new to JavaScript, I was curious if this is the proper way to handle zipping two arrays. I've already read a few examples using map which seem concise, but I am more curious if there is a generally agreed upon proper method in JavaScript to do this?
var faker = require("faker");
var products = Array.from({length:10}, () => faker.commerce.productName());
var prices = Array.from({length:10}, () => faker.commerce.price())
var strOut = "";
for(var i=0; i<10; i++){
strOut += `${products[i]} - ${prices[i]} \n`
}
console.log('====================\n'+
'WELCOME TO MY SHOP!\n'+
'====================\n'+
`${strOut}`);
1) reduce:
const strOut = products.reduce(
(result, product, index) => `${result} ${product} ${prices[index]}\n`,
''
);
2) map + join:
const strOut = products.map(
(product, index) => `${product} ${prices[index]}`
).join("\n");
second argument of map's callback is the array index, you can use it for second array.

Adding a property to an array of objects from another array

I have an array like this with names and address:
BTDevices = [
{name:"n1", address:"add1"},
{name:"n2", address:"add2"},
{name:"n3", address:"add3"}]
And another array with alias and address:
EqAlias = [
{btAlias:"a1", address:"add0"},
{btAlias:"a2", address:"add2"},
{btAlias:"a3", address:"add9"}]
I want to add btAlias property to all objects in BTDevices and set the value only if the address are the same, for example in this case I want the following result:
BTDevices:
name:"n1", address:"add1", btAlias:""
name:"n2", address:"add2", btAlias:"a2"
name:"n3", address:"add3", btAlias:""
My first solution was adding btAlias property using forEach and then using two for loops:
// Add Alias
this.BTDevices.forEach(function(obj) { obj.btAlias = "" });
// Set Alias
for (let m = 0; m < this.EqAlias.length; m ++)
{
for (let n = 0; n < this.BTDevices.length; n++)
{
if (this.BTDevices[n].address == this.EqAlias[m].address)
this.BTDevices[n].btAlias = this.EqAlias[m].btAlias;
}
}
Is there a better way to do the same? I guess using forEach
Using forEach instead of for will just replace the two for loop with forEach. We could argue on which is the best between for and forEach but i don't think there's a good answer. In modern javascript you can also use the for of loop.
Your algorithm is the simpliest and it will work.
But, if you want to address some performances issues, you should want to know that your algorithm is also the slowest (O(n²) complexity)
Another way to do that is to store items of BTDevices in a map to find them faster. Example:
let map = new Map();
BTDevices.forEach(e => map.set(e.address, e));
EqAlias.forEach(e => {
let device = map.get(e.address);
if (device) {
device.btAlias = e.btAlias;
}
});
The only advantage of this code is that looking for an item in a Map is faster (between O(1) and O(n), it depends of Map implementation). But you won't see any differences unless you try to manipulate some very big arrays.
You can use map and find
Use map to loop the array and create a new array.
Use find to check if a string is in an array.
let BTDevices = [{name:"n1", address:"add1"},{name:"n2", address:"add2"},{name:"n3", address:"add3"}];
let EqAlias = [{btAlias:"a1", address:"add0"},{btAlias:"a2", address:"add2"},{btAlias:"a3", address:"add9"}];
let result = BTDevices.map( v => {
v.btAlias = ( EqAlias.find( e => e.address == v.address ) || { btAlias:"" } ).btAlias;
return v;
});
console.log( result );
Please check doc: .map, .find
You could also do something like this.
var BTDevices = [{name:"n1", address:"add1"},{name:"n2", address:"add2"},{name:"n3", address:"add3"}];
var EqAlias = [{btAlias:"a1", address:"add0"},{btAlias:"a2", address:"add2"},{btAlias:"a3", address:"add9"}];
var EqAliasAdd = EqAlias.map((e)=>e.address);
var BTDevicesAdd = BTDevices.map((e)=>e.address);
BTDevicesAdd.map(function(i,k) {
BTDevices[k].btAlias = "";
if(EqAliasAdd.indexOf(i) >= 0)
BTDevices[k].btAlias = EqAlias[k].btAlias;
});
console.log(BTDevices);

Categories

Resources