I'm working on a system that puts all images inside paragraph-tags, as such:
<p>
<img src="..." />
</p>
So I wrote a function to move the images out of the paragraph tags. It looks like this:
moveImagesOutOfPtags() {
let images = document.querySelectorAll('p > img');
Object.entries(images).forEach(entry => {
let value = entry[1];
if( value.parentNode.nodeName === 'P' || value.parentNode.nodeName === 'p' ){
value.parentNode.outerHTML = value.parentNode.innerHTML;
}
});
}
But it doesn't work if there are two images inside the same p-tag, as such:
<p>
<img src="..." />
<img src="..." />
</p>
... Since the parent is removed/rewritten/overwritten with the value.parentNode.outerHTML = ...-line, for the first image. So when it gets to the second image in that p-tag, then it throws the error:
Failed to set the 'outerHTML' property on 'Element': This element has no parent node.
Any suggestions on a good way to solve this?
This is easy to achieve with DOM manipulating methods instead of setting HTML strings, like this:
function moveImagesOutOfPtags() {
let images = document.querySelectorAll('p > img');
images.forEach(img => {
const parent = img.parentElement;
// Insert the image as a previous sibling to the paragraph
parent.parentElement.insertBefore(img, parent);
if (parent.children.length === 0) {
// Remove the empty paragraph
parent.remove();
}
});
}
moveImagesOutOfPtags();
<p>
<img src="...">
<img src="...">
</p>
<p>
<img src="...">
</p>
You don't have to check the parent, since the query will select the images wrapped in a paragraph only, just insert the image as a previous sibling of the parent. Then check if the parent is empty, and remove if needed.
use id for every paragraph-tags and check the previously modified id with current one . i don't think this is the best way but this will let the job done.
html contents
<p id = "p_id_1">
<img src="..." />
<img src="..." />
</p>
<p id = "p_id_2">
<img src="..." />
<img src="..." />
</p>
script will be
moveImagesOutOfPtags() {
let images = document.querySelectorAll('p > img');
var p_id='';
Object.entries(images).forEach(entry => {
let value = entry[1];
if(p_id != value.parentNode.id && (value.parentNode.nodeName === 'P' || value.parentNode.nodeName === 'p' )){
value.parentNode.outerHTML = value.parentNode.innerHTML;
p_id = value.parentNode.id;
}
});
}
You could just catch the error and not carry on like this:
Try this:
moveImagesOutOfPtags() {
document.querySelectorAll('p > img').forEach(entry => {
const value = entry && entry[0]; // catches error if p has been replaced and doesn't continue.
if(value){ // you don't have to test if parent is p tag, you've already determined that with your direct child selector p > img
value.parentNode.outerHTML = value.parentNode.innerHTML;
}
});
}
Related
The following JS gives me a list of all the img alt text used on a webpage, showing either a name "name" or empty quotes "".
const altAtt = document.querySelectorAll("*[alt]");
const altText = Array.from(altAtt, (al, i) => al.getAttribute('alt'));
console.log(altText);
Question:
How do I get all the images with alt="" or alt="some name" and add them to a unordered list injected into the DOM?
I can create the structure document.createElement('ul'); etc. But no idea where to start adding the information into the list.
Somehow using li.innerHTML = altText[i];
Thanks in advance for any help.
I do have this using jQuery but really wish to write this in vanilla JS. The jQuery adds a Div element after each image which is what I want to eventually achieve with vanilla JavaScript :-)
$.each($("img"), function() {
var altText = $(this).attr('alt');
if (altText === "") {
$(this).after("<div class=\"no-alt-text\">This image is decoration</div>");
} else {
$(this).after("<div class=\"show-alt-text\">This image ALT text is: " + altText + "</div>");
$(function () {
$("div.show-alt-text:contains(\"undefined\")").each(function () {
$(this).removeClass("show-alt-text").html("<div class=\"empty-alt\">This image is missing an ALT attribute</div>");
$('div:empty').remove();
});
});
}
});
I think this is close to what you are looking for.
const imgElements = document.querySelectorAll("img");
const showAltText = document.createElement("div");
showAltText.classList.add("show-alt-text");
const noAltText = document.createElement("div");
noAltText.classList.add("no-alt-text");
noAltText.innerHTML = "This image is decoration";
for(let i = 0; i < imgElements.length; i++){
var altText = imgElements[i].alt;
if(altText === ""){
imgElements[i].after(noAltText.cloneNode(true));
}
else{
const element = showAltText.cloneNode(true);
element.innerHTML = "This image ALT text is: " + altText;
imgElements[i].after(element);
}
}
<!-- Alt Text -->
<img height="100" alt="test1" src="https://www.worldanvil.com/uploads/images/03220ab14fe9a946322a5329bd7977ad.png" />
<!-- Alt Text -->
<img height="100" alt="test2" src="https://www.worldanvil.com/uploads/images/03220ab14fe9a946322a5329bd7977ad.png" />
<!-- Empty Alt Text -->
<img height="100" alt="" src="https://www.worldanvil.com/uploads/images/03220ab14fe9a946322a5329bd7977ad.png" />
<!-- No Alt Text -->
<img height="100" src="https://www.worldanvil.com/uploads/images/03220ab14fe9a946322a5329bd7977ad.png" />
I try to find out the number of <img> elements that do not have "style" attribute in a HTML file by using JavaScript.
My solution: find out numbers of <img> tags as "imgCount", then get number of <img> tags with "style" attribute as "imgStyCount". After that, use "imgCount" minus "imgStyCount" to get the final result that I wish to know.
However, something goes wrong. My browser keep told me
TypeError: document.getElementsByTagName(...)[K].hasAttribute is not a function
At the if statement.
And the weird thing is, the alert(document.getElementsByTagName("img")[k].hasAttribute("style") show the if statement result is TRUE.
How it can be like not a function and give the true value?
var imgCount = 0;
var imgStyCount = 0;
var result;
for (k in document.getElementsByTagName("img")) {
if (document.getElementsByTagName("img")[k].hasAttribute("style") == true) {
alert(document.getElementsByTagName("img")[k].hasAttribute("style"));
console.log(" <img> =: ", document.getElementsByTagName("img")[k].style);
imgStyCount++;
}
imgCount++;
}
result = imgCount - imgStyCount;
<img height="150px" src="Http://flax.nzdl.org/images/ngf.jpeg" style="vertical-align:middle;margin-right:20px;" />
<img src="Http://flax.nzdl.org/images/abc.jpg" />
<img src="Http://flax.nzdl.org/images/fbc.jpg" />
<img src="Http://flax.nzdl.org/images/agc.jpg" />
<img src="Http://flax.nzdl.org/images/abt.jpg" />
Here's a simple way without using loop.
You can use querySelectorAll with attribute selector
document.querySelectorAll('img[style]') will select all the <img> elements on the page having style attribute.
var result = document.querySelectorAll('img').length - document.querySelectorAll('img[style]').length;
alert(result);
<img height="150px" src="Http://flax.nzdl.org/images/ngf.jpeg" style="vertical-align:middle;margin-right:20px;" />
<img src="Http://flax.nzdl.org/images/abc.jpg" />
<img src="Http://flax.nzdl.org/images/fbc.jpg" />
<img src="Http://flax.nzdl.org/images/agc.jpg" />
<img src="Http://flax.nzdl.org/images/abt.jpg" />
Use for-loop to iterate image elements than for-in
var imgStyCount = 0;
var elems = document.getElementsByTagName("img");
for (var k = 0; k < elems.length; k++) {
if (elems[k].hasAttribute("style")) {
imgStyCount++;
}
}
var result = elems.length - imgStyCount;
alert(result);
<img height="150px" src="Http://flax.nzdl.org/images/ngf.jpeg" style="vertical-align:middle;margin-right:20px;" />
<img src="Http://flax.nzdl.org/images/abc.jpg" />
<img src="Http://flax.nzdl.org/images/fbc.jpg" />
<img src="Http://flax.nzdl.org/images/agc.jpg" />
<img src="Http://flax.nzdl.org/images/abt.jpg" />
Fiddle demo
You're welcome.
function hasAttr(el, attr) {
if(typeof el === 'object' && el !== null && 'getAttribute' in el && el.hasAttribute(attr)) return true
else return false
}
<span medium-img>Whatever</span>
alert($('span').is('[medium-img]')); // Returns true
Suppose my DOM looks something like this (purely fictional example):
<div>
<img id="img1" />
Text 1
</div>
<img id="img2" />
<div>
<p>Text 2</p>
<div>
<img id="img3" />
</div>
<img id="img4" />
</div>
What I'm attempting to do is to find all text (if any) between consecutive elements independent of nesting level (so in this case, I'd find Text1 between #img1 and #img2, Text 2 between #img2 and #img3, and nothing/an empty string between #img3 and #img4)
I don't know ahead of time how the dom is going to be structured.
I've tried using JQuery's nextUntil(), but that only seems to work for sibling nodes.
You can use the contents() method which returns all child nodes including textnodes.
This way, doing $('body *').contents().addBack() returns a flattened representation of the DOM.
Now, you can iterate between img (or whatever tag you want) elements and get the textnodes (having a nodeType of 3)
function textBetweenTags(tag){
var contents = $('body *').contents().addBack(),
allOfType = contents.filter(tag),
count = allOfType.length,
map = allOfType.map(function(){return contents.index( this );}),
texts = [];
for (var i = 0, l = map.length-1; i < l; i++){
var start = map[i],
end = map[i+1],
textnodes = contents.slice(start,end).get().filter(function(item,index){return item.nodeType===3;});
texts.push( {
start: contents[start],
end: contents[end],
text: $.trim($(textnodes).text())
});
}
return texts.filter(function(item,index){return item.text !== '';});
}
var texts = textBetweenTags('img');
console.log(texts);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<img id="img1" />
Text 1
</div>
<img id="img2" />
<div>
<p>Text 2</p>
<div>
<img id="img3" />
</div>
<img id="img4" />
</div>
I think my solution is more closely achieves what you specified. This is also using contents() but does the traversing/filtering too, based on the ids.
function textBetweenIds(firstId, lastId) {
var texts = [],
betweenIds = false,
recurse = function ($item) {
$item.each(function() {
var text,
$item = $(this),
itemId = $item.attr("id"),
contents = $item.contents();
if (itemId == firstId) {
betweenIds = true;
} else if (itemId == lastId) {
betweenIds = false;
}
if (contents.length == 0 && betweenIds) {
text = $item.text().trim();
if (text != undefined && text.length > 0) {
texts.push(text);
}
}
recurse(contents);
});
return texts;
};
recurse($("body"));
return texts;
}
result = textBetweenIds("img1", "img3");
// result: ["Text 1", "Text 2"]
I have a data collection thats gets rendered from the server. This is how the HTML might looks like:
<div>
<img class="image_98" alt="..." height="..." src="..." width="..." />
</div>
<div>
<img class="image_99" alt="..." height="..." src="..." width="..." />
</div>
<div>
<img class="image_99" alt="..." height="..." src="..." width="..." />
</div>
<div>
<img class="image_100" alt="..." height="..." src="..." width="..." />
</div>
<div>
<img class="image_100" alt="..." height="..." src="..." width="..." />
</div>
<div>
<img class="image_100" alt="..." height="..." src="..." width="..." />
</div>
The image class is dynamic depending on the id of the image in the database. How can I hide all images except the first one of every class?
In this example I only want to see the first "image_98", "image_99" and "image_100" class.
You can do it like this, using attribute selector [attribute="value"] and each() method
var img=$('[class^="image_"]');
img.hide(); //hide all image
img.each(function(){
$('.'+this.className).eq(0).show(); // show first image in each class
});
Use
show() , to show the selected item
hide() , to hide the selected item.
Old school method :
var slice = Array.prototype.slice,
imgs = slice.call(document.getElementsByTagName('img')),
img, ct, cls;
while (img = imgs.shift()) {
if (cls === img.className) {
ct = img.parentNode;
ct.parentNode.removeChild(ct);
} else {
cls = img.className;
}
}
Yuk indeed...
Using jQuery, I would do quite the same :
var prevCls;
$('img').each(function () {
var cls = $(this).attr('class'); // I have a doubt about .className support
if (prevCls === cls) {
$(this.parentNode).remove(); // or .hide() if preferred
} else {
prevCls = cls;
}
});
Advantage comparing to the accepted answer : a single loop.
Here is another method using recursivity - quite close to the first one :
(function deldup(list, prevCls) {
var item = list.shift();
if (item.className !== prevCls) {
prevCls = item.className;
} else {
item = item.parentNode;
item.parentNode.removeChild(item);
}
if (list.length) {
deldup(list, prevCls);
}
})(
Array.prototype.slice.call(
document.getElementsByTagName('img')
)
);
Advantage : no global variables.
In case images are mixed :
(function deldup(list, cls) {
var item = list.shift();
if (!cls[item.className]) {
cls[item.className] = true;
} else {
item = item.parentNode;
item.parentNode.removeChild(item);
}
if (list.length) {
deldup(list, cls);
}
})(
Array.prototype.slice.call(
document.getElementsByTagName('span')
), {} // notice this empty object
);
Demo : http://jsfiddle.net/wared/QpZD5/.
Say I had the following three divs with unique ids on a page
<div id="product-id-001"></div>
<div id="product-id-002"></div>
<div id="product-id-003"></div>
What code would I need to add the following image elements based on the id of the div?
<div id="product-id-001">
<img src="images/product-id-001-1.jpg"></img>
<img src="images/product-id-001-2.jpg"></img>
<img src="images/product-id-001-3.jpg"></img>
</div>
<div id="product-id-002">
<img src="images/product-id-002-1.jpg"></img>
<img src="images/product-id-002-2.jpg"></img>
<img src="images/product-id-002-3.jpg"></img>
</div>
<div id="product-id-003">
<img src="images/product-id-003-1.jpg"></img>
<img src="images/product-id-003-2.jpg"></img>
<img src="images/product-id-003-3.jpg"></img>
</div>
Thanks for any tips.
$('div[id^=product]').each(function(index, elem) {
for(var i = 1; i < 4; i++) {
$('<img>', {
src: '/images/' + elem.id + i
}).appendTo(this);
}
});
Demo: http://www.jsfiddle.net/9S9Av/
(You need Firebug or another DOM inspector to see the result)
From the jQuery docs:
"The .append() method inserts the specified content as the last child of each element in the jQuery collection"
So:
$('#product-id-001").append('<img src="images/product-id-001-1.jpg"></img>');
etc...
// Select all elements with an id beginning with product-id:
var $productContainers = $('[id^=product-id]');
// Loop through the matched elements and append the images:
$productContainers.each(function () {
var $this = $(this),
productId = $this.attr('id'),
numImages = 3,
extension = '.jpg';
// Create and append the images based on the configuration above. Note that this
// assumes that each product have three valid images.
for (var i = 1; i <= numImages; i++)
{
var $image = $('<img alt="" />').attr('src', 'images/'+productId+'-'+i+extension);
$image.appendTo($this);
}
});