I have this HTML string in node:
<a data-style="width:32px" id="heilo-wrld" style="height:64px">
Hello world
</a>
The code has data-style and style attributes I would like to merge in one style attribute like this:
<a id="heilo-wrld" style="width:32px; height:64px;">
Hello world
</a>
I could also have complex HTML blocks like this:
<div class="wrapper" data-style="background-color: red;">
<a data-style="width:32px" id="heilo-wrld" style="height:64px">
Hello world
</a>
</div>
To get this result:
<div class="wrapper" style="background-color: red;">
<a id="heilo-wrld" style="width:32px; height:64px;">
Hello world
</a>
</div>
I found some plug-in but it does not do this specific job:
sanitize-html
htmltidy
Does exists some smart way to do that?
Using jsdom, you could define a mergeStyles function like this:
const jsdom = require('jsdom');
function mergeStyles(html, callback) {
return jsdom.env(html, function(errs, window) {
const { document } = window;
Array.from(
document.querySelectorAll('[data-style]')
).forEach(function(el) {
const styles = [];
Array.from(el.attributes).forEach(function(attr) {
if (attr.name !== 'style' && attr.name !== 'data-style') {
return;
}
styles.push(attr.value);
el.removeAttributeNode(attr);
});
if (!styles.length) {
return;
}
el.setAttribute('style', styles.join(';'));
});
const result = document.body.innerHTML;
return callback(null, result);
});
}
Then call it like:
const input = `
<div class="wrapper" data-style="background-color: red;">
<a data-style="width:32px" id="heilo-wrld" style="height:64px">
Hello world
</a>
</div>
`;
mergeStyles(input, function(err, result) {
if (err) {
throw err;
}
// `result` should contain the HTML with the styles merged.
console.log(result);
});
Related
This is an update to a previous question I had. I am making a simple page that filters the first 9 Pokemon by their type after clicking a button. I will include the code below. I ran the code through a validator and there were no issues. Also, I don't see any errors in the console. I did console the type and the array every time a button is clicked so it looks like the filter is working. But, nothing is happening on the page.
Here's a link to the page: https://sobreratas.github.io/pokemonfilter/
const pokemon=[{name:"Bulbasaur",image:"images/bulbasaur.png",number:1,type:"Grass"},{name:"Ivysaur",image:"images/ivysaur.png",number:2,type:"Grass"},{name:"Venusaur",image:"images/venusaur.png",number:3,type:"Grass"},{name:"Charmander",image:"images/charmander.png",number:4,type:"Fire"},{name:"Charmeleon",image:"images/charmeleon.png",number:5,type:"Fire"},{name:"Charizard",image:"images/charizard.png",number:6,type:"Fire"},{name:"Squirtle",image:"images/squirtle.png",number:7,type:"Water"},{name:"Wartortle",image:"images/wartortle.png",number:8,type:"Water"},{name:"Blastoise",image:"images/blastoise.png",number:9,type:"Water"}];
const sectionCenter = document.querySelector('.section-center');
const btnContainer = document.querySelector('.btn_container');
window.addEventListener('DOMContentLoaded', function() {
displayPokemonMenu();
displayMenuButtons();
})
function displayPokemonMenu(array) {
let pokemonMap = pokemon.map(function(item) {
return `<div class="pokemon-item">
<img class="photo" src="${item.image}" alt="${item.name}">
<h2>${item.name}</h2>
<h3># <span id="number">${item.number}</span></h3>
<h4>Type: <span id="type">${item.type}</span></h4>
</div>`
})
pokemonMap = pokemonMap.join('');
sectionCenter.innerHTML = pokemonMap;
}
function displayMenuButtons() {
let types = pokemon.reduce(function(values, item) {
if (!values.includes(item.type)) {
values.push(item.type);
}
return values
}, ['All']);
const typesBtns = types.map(function(type) {
return `<button class="filter-btn" data-id="${type}">${type}</button>`
}).join('');
btnContainer.innerHTML = typesBtns;
const filterBtns = btnContainer.querySelectorAll('.filter-btn');
filterBtns.forEach(function(btn) {
btn.addEventListener('click', function(e) {
let type = e.currentTarget.dataset.id;
console.log(type)
const pokemonFilter = pokemon.filter(function(item) {
if (type === item.type) {
return item;
}
})
if (type === 'All') {
displayPokemonMenu(pokemon);
} else {
displayPokemonMenu(pokemonFilter);
console.log(pokemonFilter);
}
})
})
}
<img class="logo" src="images/logo.png" alt="logo">
<br>
<br>
<div class="btn_container">
<button class="filter-btn" data-id="All">All</button>
</div>
<div class="section-center">
<div class="pokemon-item">
<img class="photo" src="images/bulbasaur.png" alt="Bulbasaur">
<h2>Bulbasaur</h2>
<h3># <span id="number">1</span></h3>
<h4>Type: <span id="type">Grass</span></h4>
</div>
</div>
When I load my webpage up, the word "undefined" appears in a <ul> that is then modified with javascript and .innerHTML after the page is loaded up.
This is how the HTML appears:
<div>
<ul>
"undefined
"
<li>...</li>
</ul>
</div>
If there are no <li>'s, then it just appears as so:
<div>
<ul>undefined</ul>
</div>
Here is the Javascript function that seems to be causing it. The data parameter is the data from the database.
const setupBookmarks = (data) => {
let favoritesHTML, bookmarksHTML = '';
const bookmarkData = [];
if (data && data.length) {
data.forEach(doc => {
const bookmarkId = Math.floor(Math.random() * 10000000);
const bookmark = doc.data();
if (bookmark.favorite) {
favoritesHTML += bookmarkHTML(bookmark, bookmarkId);
} else {
bookmarksHTML += bookmarkHTML(bookmark, bookmarkId);
}
bookmarkData.push({bookmark, bookmarkId});
})
}
favoriteList.innerHTML = favoritesHTML;
bookmarkList.innerHTML = bookmarksHTML;
return bookmarkData;
}
Here is shortened version of the bookmarkHTML function, which simply adds the HTML to the favoritesHTML and bookmarksHTML variables in the previous function:
const bookmarkHTML = (bookmark, bookmarkId) => {
let html = '';
const li = `
<li>
<div class="row card-margin">
<div class="col s12 m12 card-padding">
<div class="card-panel teal card-size">
<img src='${"https://www.google.com/s2/favicons?domain_url=" + bookmark.url}' alt='icon' class='img-size'>
<a class='white-text' href='${bookmark.url}' target='_blank'>${bookmark.website}</a>
<button class='btn-flat btn-small right cross' id='${bookmarkId}'><i class='material-icons'>clear</i></button>
<button class='btn-flat waves-light btn-small right listener' id='${bookmarkId}'><i class='material-icons left'>compare_arrows</i>switch</button>
</div>
</div>
</div>
</li>
`;
html += li;
return html;
}
What could be causing this 'undefined' to appear in the HTML of my website upon being loaded up?
I am new in Unit Test JS. I want create test in jasmine. I dynamically create element HTML in JS.
data.map((channel) => {
const { url, width, height } = channel.thumbnails.medium;
const { title, customUrl } = channel;
const { subscriberCount, videoCount, viewCount } = channel.statistics;
output += `
<li class="channel-wrraper">
<a href='${customUrl}' target="_blank">
<img src='${url}' alt="img-channel" height='${width}' width='${height}' class="channel-img">
</a>
<p class="channel-title">${title}</p>
<div class="channel-statistic">
<div class="statistic-wrraper">
<span class="statistic-name">subscribers:</span>
<span class="subscirber-count">${formatNumber(subscriberCount)}</span>
</div>
<div class="statistic-wrraper">
<span class="statistic-name">videos:</span>
<span class="video-count">${formatNumber(videoCount)}</span>
</div>
<div class="statistic-wrraper">
<span class="statistic-name">views:</span>
<span class="veiw-count">${formatNumber(viewCount)}</span>
</div>
</div>
</li>`
});
channelsList.innerHTML = output;
Then some element will be ordered. This is sort function:
const list = document.querySelector('.channels-list');
const sortNumber = (selector) => {
[...list.children]
.sort((a,b) => a.querySelector(selector).innerText.replace(/,/g, '') - b.querySelector(selector).innerText.replace(/,/g, ''))
.map(node => list.appendChild(node))
}
I read about JSDOM and I watched the tutorials in which they tested the DOM, however, these elements were in the html file...
I want test function sortNumber
But I don`t know how start this task..
You can try using jsdom-global, then you will have document.body setup for you:
require('jsdom-global')()
// you can now use the DOM
document.body.innerHTML = 'put your html here'
An alternative will be to use jest, which comes with JSDOM configured as default
I have problem with my code.
I have products on my website, each product has his own <a><h1>CODE</h1></a> and I need to take this CODE and paste it before an image. I need to copy element with has class="loop1" and paste it into another element with class="lop1" and then take another element with class="loop2" and paste into element with class="lop2" and so on..
I made class with same numbers for easier copying, but it doesnt work. Can sombody help me?
This is my code:
$('#loop').addClass(function(i) {
return 'lop'+(i+1);
});
$('.p-name').addClass(function(i) {
return 'loop'+(i+1);
});
function doForm() {
var numb = ["1","2","3","4","5","6","7","8","9","10","11","13","14"];
for (var i=0;i<numb.length;i++) {
number = numb[i];
selector = '.loop' + number;
if ($(selector).length != 0) {
val = $(selector).html();
$('lop' + number).html(val);
}
}
}
doForm();
Related html:
<div class="columns">
<div id="loop" class="lop1"></div>
<div class="p-image">
<img src="https://" width="290" height="218">
</div>
<div class="p-info">
<span itemprop="name">PRODUCT</span>
</div>
<div>
So I need to take from "p-info > a" and paste it into div "lop1". Depends on number in class copy and paste HTML into div with same number.
Change $('lop' + radek).html(val); to $('.lop' + number).html(val);
Notice the . at the beginning of lop, it will create a selector to fetch element based on the class.
$('#loop').addClass(function(i) {
return 'lop'+(i+1);
});
$('.p-name').addClass(function(i) {
return 'loop'+(i+1);
});
function doForm() {
var numb = ["1","2","3","4","5","6","7","8","9","10","11","13","14"];
for (var i=0;i<numb.length;i++) {
var number = numb[i];
var selector = '.loop' + number;
if ($(selector).length != 0) {
var val = $(selector).html();
$('.lop' + number).html(val);
}
}
}
doForm();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="columns">
<div id="loop" class="lop1"></div>
<div class="p-image">
<img src="https://" width="290" height="218">
</div>
<div class="p-info">
<span itemprop="name">PRODUCT</span>
</div>
<div>
I'm trying to go through all of the elements in the document and pull the ones with a target class name. Importantly, I'm needing to do it without the use of document.getElementsByClassName(className) / document.querySelectorAll, etc. — that's the point of this learning exercise.
Here's the javascript:
var getElementsByClassName = function(className){
var rootElem = document.body;
var collectionResult = [];
if (rootElem.getAttribute("class") == className) {
collectionResult.push(rootElem);
};
var nextTier = function(collectionResult, rootElem) {
var thisTier = rootElem.children;
for (i=0; i<thisTier.length; i++) {
var classes = thisTier[i].getAttribute("class");
if (classes != undefined && classes.includes(className)) {
collectionResult.push(thisTier[i]);
};
var childrenArray = thisTier[i].children;
if (childrenArray.length > 0) {
nextTier(collectionresult, childrenArray)
};
};
};
nextTier(collectionResult, rootElem);
return collectionResult;
};
Here's the section of the HTML structure I'm having trouble with:
<p>
<div class="somediv">
<div class="innerdiv">
<span class="targetClassName">yay</span>
</div>
</div>
</p>
The code works for the rest of the page with any number of non-nested elements. But as soon as var childrenArray = thisTier[i].children get to the div.somediv element, it has childrenArray == undefined rather than pulling the div.innerdiv element.
Am I misunderstanding how element.children works?
Array.prototype.flatMap is an effective tool for flattening trees (like DOM) to an array of values (like a list of elements) -
function getElementsByClassName (node, query) {
function matchAll (children) {
return Array
.from(children)
.flatMap(c => getElementsByClassName(c, query))
}
if (node.classList && node.classList.contains(query))
return [ node, ...matchAll(node.childNodes) ]
else
return matchAll(node.childNodes)
}
const result =
getElementsByClassName(document, "targetClassName")
console.log(result)
// [ <div class="somediv targetClassName">…</div>
// , <span class="targetClassName">yay1</span>
// , <span class="targetClassName">yay2</span>
// , <span class="targetClassName">yay3</span>
// ]
<div class="somediv targetClassName">
<div class="innerdiv">
<span class="targetClassName">yay1</span>
</div>
</div>
<div class="somediv">
<div class="innerdiv">
<span class="targetClassName">yay2</span>
</div>
</div>
<div class="somediv">
<div class="innerdiv">
<span class="targetClassName">yay3</span>
</div>
</div>
You seem to be overcomplicating things.
function getElementsByClassName(className, root) {
if(!root) root = document.documentElement;
return [].reduce.call(root.children, function(arr, child) {
if(child.classList.contains(className)) arr.push(child);
return arr.concat(getElementsByClassName(className, child))
}, []);
}
function getElementsByClassName(className, root) {
if(!root) root = document.documentElement;
return [].reduce.call(root.children, function(arr, child) {
if(child.classList.contains(className)) arr.push(child);
return arr.concat(getElementsByClassName(className, child))
}, []);
}
console.log(getElementsByClassName("targetClassName"));
<div class="somediv targetClassName">
<div class="innerdiv">
<span class="targetClassName">yay1</span>
</div>
</div>
<div class="somediv targetClassName">
<div class="innerdiv targetClassName">
<span class="targetClassName">yay2</span>
</div>
</div>
<div class="somediv">
<div class="innerdiv">
<span class="targetClassName">yay3</span>
</div>
</div>