Some of the img elements that I am dynamically building may (do) fail. For those cases, I have some code I got from here:Is there a way to programmatically determine that an image link is bad? namely this:
function getNatlBookCritics() {
var htmlBuilder = '';
// Doesn't do diddly-squat - wrong spot for it?
$('img').error(function () {
$(this).attr("src", "Content/NoImageAvailable.png");
});
$.getJSON('Content/NBCCJr.json', function (data) {
$.each(data, function (i, dataPoint) {
. . .
...but it's not working. Warum nicht?
UPDATE
With this code inside the .each portion of the $.getJSON() call:
var jObject = $('<img src=\"' + dataPoint.imghref + '\"/>');
$(jObject).error(function () {
$(this).attr("src", "Content/NoImageAvailable.jpg");
});
...all of the images fail. dataPoint.imghref contains such values as:
http://www.amazon.com/exec/obidos/ASIN/B00655KLOY/garrphotgall-20
UPDATE 2
In a nuts hell, I'm adding the "img src" like so:
function getNatlBookCritics() {
var htmlBuilder = '';
$.getJSON('Content/nbcc.json', function (data) {
$.each(data, function (i, dataPoint) {
if (IsYear(dataPoint.category)) {
htmlBuilder += '<div class=\"yearBanner\">' + dataPoint.category + '</div>';
} else {
htmlBuilder += '<section class=\"wrapper\" ><a id=\"mainImage\" class=\"floatLeft\" href=\"' +
dataPoint.imghref + '\"' + ' target=\"_blank\"><img height=\"160\" width=\"107\" src=\"' +
dataPoint.imgsrc + '\"' +
dataPoint.imgalt + '></img></a>' +
. . .
htmlBuilder += '</section>';
}
// this is where I had the img err code
}); //each
$('#BooksContent').append(htmlBuilder);
}); //getNatlBookCritics
...so as you can see the img is getting added to the DOM; maybe the fact that I've got height and width properties with my img are the problem...
UPDATE 3
ManMohan Vyas: Do you mean like so:
}); //each
$('#BooksContent').append(htmlBuilder).
find("img").error(function(){
$(this).attr("src", "Content/NoImageAvailable.png");
});
}); //getJSON()
?
UPDATE 4
This:
var jObject = $(htmlBuilder);
jObject.find("img").error(function () {
$(this).attr("src", "Content/NoImageAvailable.png");
});
$('#BooksContent').append(jObject);
...didn't work.
And FWIW, changing this:
$('#BooksContent').html('');
. . . $('#BooksContent').append(htmlBuilder);
...to this:
$('#BooksContent').replaceWith(htmlBuilder);
...didn't work well (the right stuff populated, but the formatting got all messed up (instead of a solid black background, each section had a black background, but the overall background was silver).
UPDATE 5
I just thought of something that may be causing my problem: the images that I'm attempting to show are all jpgs, but the "Image Not Available" image is a png. Does that make a difference? Is that possibly what is causing the rendering engine to get confused? If so, I'll just save the fallback img as a jpg...
UPDATE 6
Nope, these last two attempts didn't work either. I tried Joseph Myers idea, then Prestauls, as I changed this:
dataPoint.imghref + '\"' + ' onerror=\"imgError(this);\" target=\"_blank\"><img height=\"160\" width=\"107\" src=\"' +
dataPoint.imgsrc + '\"' +
..to this:
dataPoint.imgsrc + '\" onerror=\"imgError(this);\"' +
dataPoint.imgalt + '></img></a>' +
...and no difference. I asked this on the jQuery forum a bit ago: I'm grasping at straws here, but I wonder if having mismatched jQuery/jQueryUI versions could be the problem? In order to support older browsers, I'm still using jQuery 1.9.1, but am on the "bleeding edge" as regards jQueryUI with version 1.10.3.
UPDATE 7
Okay, here's all the pertinent code (some redundant and moot code that will be refactored out has been elided to comply with SO's length limits). The (static) CSS shouldn't matter, right? The only other "code" is Web.config and things of that nature, so none of that should be having an effect on why I can't get the fallback images to display.
A lot of my failed attempts to get NoImageAvailable.png to display are commented out.
@{
Layout = "~/_SiteLayout.cshtml";
Page.Title = "My Next Winner";
}
<div id="tabs" class="content-wrapper">
<ul>
<li><a href="#tab-Books">Books</a></li>
<li><a href="#tab-Movies">Movies</a></li>
<li><a href="#tab-Music">Music</a></li>
</ul>
<div id="tab-Books">
<select id="bookDropDown">
<option value="Pulitzer">Pulitzer</option>
<option value="NBCC">National Book Critics Circle</option>
<option value="NBA">National Book Awards</option>
<option value="NOBA">National Outdoors Book Awards</option>
</select>
<div id="BooksContent" class="clearfix">Content in Books tab</div>
</div>
<div id="tab-Movies">
. . . . . .
<script>
$.ajaxSetup({ cache: false });
var currentBookSelection = '';
var currentMovieSelection = '';
var currentMusicSelection = '';
function imgError(image) {
image.onerror = "";
image.src = "Content/NoImageAvailable.png";
return true;
}
// BOOKS
// TODO: Refactor: just have one "getBooks()" function, passing in the name of the json file
function getNatlBookCritics() {
var htmlBuilder = '';
$.getJSON('Content/nbcc.json', function (data) {
$.each(data, function (i, dataPoint) {
if (IsYear(dataPoint.category)) {
htmlBuilder += '<div class=\"yearBanner\">' + dataPoint.category + '</div>';
} else { // see snippet at top of unit for dealing with landscape-oriented books (such as some children's books) to change height and width of img
htmlBuilder += '<section class=\"wrapper\" ><a id=\"mainImage\" class=\"floatLeft\" href=\"' +
dataPoint.imghref + '\"' + ' target=\"_blank\"><img height=\"160\" width=\"107\" src=\"' +
//dataPoint.imghref + '\"' + ' onerror=\"imgError(this);\" target=\"_blank\"><img height=\"160\" width=\"107\" src=\"' +
//dataPoint.imgsrc + '\" onerror=\"imgError(this);\"' +
dataPoint.imgsrc + '\"' +
dataPoint.imgalt + '></img></a>' +
'<div id=\"prizeCategory\" class=\"category\">' +
dataPoint.category +
'</div><br/><cite id=\"prizeTitle\" >' +
dataPoint.title +
'</cite><br/><div id=\"prizeArtist\" class=\"author\">' +
dataPoint.author +
'</div><br/>';
if (dataPoint.kindle.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.kindle) + '\"' +
' target=\"_blank\">Kindle</a></button>';
}
if (dataPoint.paperback.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.paperback) + '\"' +
' target=\"_blank\">Paperback</a></button>';
}
if (dataPoint.hardbound.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.hardbound) + '\"' +
' target=\"_blank\">Hardcover</a></button>';
}
htmlBuilder += '</section>';
//// Doesn't work
//$('img').error(function () {
// $(this).attr("src", "Content/NoImageAvailable.png");
//});
// When get answer, try this: <-- they all fail with this
//var jObject = $('<img src=\"' + dataPoint.imghref + '\"/>');
//var jObject = $('<img src=' + dataPoint.imghref + ' />');
//$(jObject).error(function () {
// $(this).attr("src", "Content/NoImageAvailable.jpg");
//});
}
}); //each
//var jObject = $(htmlBuilder).find('img').error(function () {
// $(this).attr("src", "Content/NoImageAvailable.png")
//});
//$("#BooksContent").html(jObject);
//var jObject = $(htmlBuilder);
//jObject.find("img").error(function () {
// $(this).attr("src", "Content/NoImageAvailable.png");
//});
//$('#BooksContent').append(jObject);
// 7/23
//imageError = function (it) {
// $(it).attr("src", "Content/NoImageAvailable.png");
//};
//htmlBuilder = htmlBuilder.replace(/<img/g, '<img onerror="imageError(this)"');
//var jObject = $(htmlBuilder);
//$("#BooksContent").html(jObject);
// </ 7/23
//$('#BooksContent').html('');
//$('#BooksContent').append(htmlBuilder);
////try this 7/24/2013
//var $jObject = $('<img>');
//$jObject.error(function () { //$jObject is already a jquery object, don't wrap it again
// $(this).attr("src", "Content/NoImageAvailable.jpg");
//}).attr('src', dataPoint.imghref);
//</try this 7/24/2013
//$('#BooksContent').html(htmlBuilder);
$('#BooksContent').html(htmlBuilder).
find('img, button').click(function (evt) {
$(this).css('border', '1px solid red')
//evt.preventDefault();
//find('img').error(function() {
// this.src = "/Content/NoImageAvailable.png"
//})
});
//$('#BooksContent').replaceWith(htmlBuilder);
//.find('img').error(function() {
// this.src = "Content/NoImageAvailable.png"
// //this.src = "http://www.gravatar.com/avatar/317f4b62da2b0186feac9b6209793505?s=80&d=http%3A%2F%2Fimg.zohostatic.com%2Fdiscussions%2Fv1%2Fimages%2FdefaultPhoto.png";
//});
$('#BooksContent').css('background-color', 'black');
$('button').button();
}); //getJSONnbcc
$largest = 0;
$(".wrapper").each(function () {
if ($(this).height() > $largest) {
$largest = $(this).height();
}
});
$(".wrapper").css("height", $largest);
} // getNatlBookCritics()
function getPulitzers() {
// Since pulitzers will be the one that shows when site first opens, added rel="nofollow"
// in each href; in this way only this method differs from the other "getX" book methods
var htmlBuilder = '';
$.getJSON('Content/pulitzers2.json', function (data) {
$.each(data, function (i, dataPoint) {
if (IsYear(dataPoint.category)) {
htmlBuilder += '<div class=\"yearBanner\">' + dataPoint.category + '</div>';
} else { // see snippet at top of unit for dealing with landscape-oriented books (such as some children's books) to change height and width of img
htmlBuilder += '<section class=\"wrapper\" ><a id=\"mainImage\" class=\"floatLeft\" href=\"' +
dataPoint.imghref + '\"' + ' target=\"_blank\"><img height=\"160\" width=\"107\" src=\"' +
dataPoint.imgsrc + '\"' +
dataPoint.imgalt + '></img></a>' +
'<div id=\"prizeCategory\" class=\"category\">' +
dataPoint.category +
'</div><br/><cite id=\"prizeTitle\" >' +
dataPoint.title +
'</cite><br/><div id=\"prizeArtist\" class=\"author\">' +
dataPoint.author +
'</div><br/>';
if (dataPoint.kindle.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.kindle) + '\"' +
' target=\"_blank\" rel=\"nofollow\" >Kindle</a></button>';
}
if (dataPoint.hardbound.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.hardbound) + '\"' +
' target=\"_blank\" rel=\"nofollow\" >Hardcover</a></button>';
}
if (dataPoint.paperback.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.paperback) + '\"' +
' target=\"_blank\" rel=\"nofollow\" >Paperback</a></button>';
}
htmlBuilder += '</section>';
}
}); //each
$('#BooksContent').html(htmlBuilder).
find('img, button').click(function (evt) {
$(this).css('border', '1px solid red')
});
$('#BooksContent').css('background-color', 'black');
$('button').button();
}); //getPulitzers
$largest = 0;
$(".wrapper").each(function () {
if ($(this).height() > $largest) {
$largest = $(this).height();
}
});
$(".wrapper").css("height", $largest);
// This is not working; axed a question on the jQuery forum
$('img, button').click(function (evt) {
$(this).css('border', '5px solid green');
evt.preventDefault();
});
// added this 7/24/2013 - does nothing
//$(function () {
// $('a').click(function () {
// open(this.href, 'NewWin', 'toolbar=yes');
// self.focus();
// return false;
// });
//});
} // getPulitzers()
function getNatlBook() {
. . . } // getNatlBook()
function getNOBA() {
// load bookContents using getJSON
}
// MOVIES
// Movies differ from books and music in that some of the awards do not always have a person as winner - just the movie
// So we have to check for that and conditionally add that bit of html (what corresponds to author in books and
// artist in music)
function getMovies(pathToJsonFile) {
var htmlBuilder = '';
$.getJSON(pathToJsonFile, function (data) {
// I tried renaming the above to nbcc.json, but it won't work with that name...?!? $.getJSON('Content/nbcc.json', function (data) {
$.each(data, function (i, dataPoint) {
if (IsYear(dataPoint.category)) {
htmlBuilder += '<div class=\"yearBanner\">' + dataPoint.category + '</div>';
} else { // see snippet at top of unit for dealing with landscape-oriented books (such as some children's books) to change height and width of img
htmlBuilder += '<section class=\"wrapper\" ><a id=\"mainImage\" class=\"floatLeft\" href=\"' +
dataPoint.imghref + '\"' + ' target=\"_blank\"><img height=\"160\" width=\"107\" src=\"' +
dataPoint.imgsrc + '\"' +
dataPoint.imgalt + '></img></a>' +
'<div id=\"prizeCategory\" class=\"category\">' +
dataPoint.category +
'</div><br/><cite id=\"prizeTitle\" >' +
dataPoint.film +
'</cite><br/>';
if (dataPoint.person.trim().length > 2) {
htmlBuilder += '<div id=\"prizeArtist\" class=\"person\">' + dataPoint.person + '</div><br/>';
}
if (dataPoint.bluray.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.bluray) + '\"' +
' target=\"_blank\" >BluRay</a></button>';
}
if (dataPoint.dvd.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.dvd) + '\"' +
' target=\"_blank\" >DVD</a></button>';
}
htmlBuilder += '</section>';
}
}); //each
$('#MoviesContent').html(htmlBuilder).
find('img, button').click(function (evt) {
$(this).css('border', '1px solid silver')
});
$('#MoviesContent').css('background-color', 'black');
$('button').button();
//console.log(htmlBuilder); <-- may want this for response to click on tab when movie tab is selected
}); //getOscars
$largest = 0;
$(".wrapper").each(function () {
if ($(this).height() > $largest) {
$largest = $(this).height();
}
});
$(".wrapper").css("height", $largest);
}
// MUSIC
// "work" is used for "album or song or recording or performance"
//TODO: Make this a generic "Music" function a la Movies above
function getGrammies() {
var htmlBuilder = '';
$.getJSON('Content/grammies.json', function (data) {
$.each(data, function (i, dataPoint) {
if (IsYear(dataPoint.category)) {
htmlBuilder += '<div class=\"yearBanner\">' + dataPoint.category + '</div>';
} else { // see snippet at top of unit for dealing with landscape-oriented books (such as some children's books) to change height and width of img
htmlBuilder += '<section class=\"wrapper\" ><a id=\"mainImage\" class=\"floatLeft\" href=\"' +
dataPoint.imghref + '\"' + ' target=\"_blank\"><img height=\"160\" width=\"107\" src=\"' +
dataPoint.imgsrc + '\"' +
dataPoint.imgalt + '></img></a>' +
'<div id=\"prizeCategory\" class=\"category\">' +
dataPoint.category +
'</div><br/><cite id=\"prizeTitle\" >' +
dataPoint.work +
'</cite><br/><div id=\"prizeArtist\" class=\"work\">' +
dataPoint.artist +
'</div><br/>';
if (dataPoint.mp3.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.mp3) + '\"' +
' target=\"_blank\">mp3</a></button>';
}
if (dataPoint.dvd.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.dvd) + '\"' +
' target=\"_blank\">DVD</a></button>';
}
if (dataPoint.vinyl.trim().length > 2) {
htmlBuilder += '<button><a href=\"' + Urlify(dataPoint.vinyl) + '\"' +
' target=\"_blank\">Vinyl</a></button>';
}
htmlBuilder += '</section>';
//// Doesn't work
//$('img').error(function () {
// $(this).attr("src", "Content/NoImageAvailable.png");
//});
}
}); //each
$('#MusicContent').html(htmlBuilder).
find('img, button').click(function (evt) {
$(this).css('border', '1px solid gold')
});
$('#MusicContent').css('background-color', 'black');
$('button').button();
}); //getJSONMusic
$largest = 0;
$(".wrapper").each(function () {
if ($(this).height() > $largest) {
$largest = $(this).height();
}
});
$(".wrapper").css("height", $largest);
}
function configLoading() {
$('#lblLoading').show();
// TODO: Not working for some reason - the configLoaded never sets them back to enabled...
//$('bookDropDown').Attr('disabled', true);
//$('moviesDropDown').Attr('disabled', true);
//$('musicDropDown').Attr('disabled', true);
}
function configLoaded() {
$('#lblLoading').hide();
//$('bookDropDown').Attr('disabled', false);
//$('moviesDropDown').Attr('disabled', false);
//$('musicDropDown').Attr('disabled', false);
}
$(document).ready(function () {
$('#tabs').tabs({
beforeActivate: function (event, ui) {
// Pulitzers is loaded at first; any time the books tab is clicked, something will already be there
if (ui.newTab.index() == 1) {
moviesContent = $('#MoviesContent').html();
if (moviesContent == 'Content in Movies tab') {
// TODO: When it's ready, uncomment this: getOscars();
}
}
else if (ui.newTab.index() == 2) {
musicContent = $('#MusicContent').html();
if (musicContent == 'Content in Music tab') {
// TODO: When it's ready, uncomment this: getGrammies();
}
}
}
});
$('body').on('error', 'img', function (e) {
$(e.currentTarget).attr("src", "Content/NoImageAvailable.png");
});
// This makes the external hrefs / targets "pop up"; I don't think I want that...
//$('body').on('click', 'a', function () {
// open(this.href, 'NewWin', 'toolbar=yes')
// self.focus();
// return false;
//});
// Books tab is default view; load the default list (Pulitzer); the other two default lists (oscars and grammies)
// will load the first time the user selects the corresponding tab (see beforeActivate() above)
getPulitzers();
currentBookSelection = "Pulitzer";
configLoaded();
$('#bookDropDown').change(function () {
// TODO: May want to keep track of when in loading mode, and if so, exit/return
configLoading();
$('#body').removeClass('bronzeBackground silverBackground goldBackground').addClass('bronzeBackground');
var sel = this.value;
if ((sel == "NBCC") && (currentBookSelection != "NBCC")) {
getNatlBookCritics();
currentBookSelection = "NBCC";
}
else if ((sel == "NBA") && (currentBookSelection != "NBA")) {
getNatlBook();
currentBookSelection = "NBA";
}
else if ((sel == "NOBA") && (currentBookSelection != "NOBA")) {
getNOBA();
currentBookSelection = "NOBA";
}
else if ((sel == "Pulitzer") && (currentBookSelection != "Pulitzer")) {
getPulitzers();
currentBookSelection = "Pulitzer";
}
configLoaded();
}); //bookDropDown
$('#moviesDropDown').change(function () {
configLoading();
$('#body').removeClass('bronzeBackground silverBackground goldBackground').addClass('silverBackground');
var sel = this.value;
if ((sel == "Oscars") && (currentMovieSelection != "Oscars")) {
currentMovieSelection = "Oscars";
getMovies('Content/oscars.json');
}
else if ((sel == "GoldenGlobe") && (currentMovieSelection != "GoldenGlobe")) {
currentMovieSelection = "GoldenGlobe";
getMovies('Content/goldenglobe.json');
}
else if ((sel == "Cannes") && (currentMovieSelection != "Cannes")) {
currentMovieSelection = "Cannes";
getMovies('Content/cannes.json');
}
else if ((sel == "Sundance") && (currentMovieSelection != "Sundance")) {
currentMovieSelection = "Sundance";
getMovies('Content/sundance.json');
}
configLoaded();
}); //moviesDropDown
$('#musicDropDown').change(function () {
configLoading();
$('#body').removeClass('bronzeBackground silverBackground goldBackground').addClass('goldBackground');
var sel = this.value;
if ((sel == "Grammies") && (currentMusicSelection != "Grammies")) {
currentMusicSelection = "Grammies";
getGrammies();
}
else if ((sel == "AMA") && (currentMusicSelection != "AMA")) {
currentMusicSelection = "AMA";
getAMA();
}
else if ((sel == "CMA") && (currentMusicSelection != "CMA")) {
currentMusicSelection = "CMA";
getCMA();
}
else if ((sel == "Indies") && (currentMusicSelection != "Indies")) {
currentMusicSelection = "Indies";
getIndies();
}
configLoaded();
}); //musicDropDown
// added 7/24/2013, changed nothing
//$(function() {
// $('a').click(function() {
// open(this.href, 'NewWin', 'toolbar=yes');
// self.focus();
// return false;
// });
//});
}); //ready
</script>
UPDATE 8
barvaz's answer also does not work for me; maybe I'm doing it wrong? Based on his answer, this is what I added:
CSS
.noImg {
background:url(~/Content/NoImageAvailable.png);
}
jQuery
0) Added this within the ready handler:
replaceEmptyImage = function ($img) {
$img.parent().addClass('noImg');
$img.remove();
};
1) Changed this line:
dataPoint.imghref + '\"' + ' target=\"_blank\"><img height=\"160\" width=\"107\" src=\"' +
...to this:
dataPoint.imghref + '\"' + ' target=\"_blank\"><img height=\"160\" width=\"107\" onerror=\"replaceEmptyImage($(this))\" src=\"' +
UPDATE 9
Here's what it looks like (the image "block" or "object" is there, it's just that it's black/blank):
BTW, The Travels of Jamie McPheeters is an awesome book at any rate, but perhaps especially to read to your kids (any age, but perhaps pre-teen is optimal).
Update based on partial code emailed to me
This is just a guess because all your main scripts and JSON data are missing, so I can't test it. However, these changes will hopefully make it work for you. Please test them!
Right before your code says
you should add this code exactly as written to your page (I noticed that you had not included this in your page yet):
Then, inside of your function
configLoaded
you need to add a second block of code (the new changes to your code are marked by NEW):This way when your menus load new images, the progress/error handler will be attached again (in case that is needed after the new images are generated). The most important thing of course, is that you include the
imagesloaded.pkgd.min.js
script code, because the second part of code can do nothing without that.Note to the author of the question
All of my suggestions work 100% of the time for me, fully tested like proper Stack Overflow answers should be. Unfortunately, these solutions have not had the chance to be properly incorporated into your code because I am lacking your full code or a self-contained example.
The nutshell that you have given, and the JS Fiddle you provided in another question (with only one line of JavaScript there), and the JS Fiddles from you and other people in various comments are all giving me a clear picture of what your problem is. Yet without enough of your actual code (ideally all of it), I cannot show you how to incorporate the solution properly--it would be like a car mechanic trying to fix a car when the car isn't there, or a math teacher trying to teach someone math problems using hand motions instead of something to write with.
I can make any of my solutions work for you, but all I need is a link to your full code which contains the problem, or if for some reason it is confidential, then at minimum a self-contained example. Please see: http://sscce.org
Would you mind providing a link to your full code or a self-contained example? I would be happy to modify it so that it works for you. I.e., if these solutions aren't working for you, then there is something else in your code that needs to be fixed, and until that point, I am providing this answer for the benefit of the community.
Update with recommended solutions
Add this script to your page:
That script provides the ability to properly detect all broken images on the page. Read about it here, including a demo which shows exactly the functionality that you are wanting to achieve for your own site: http://desandro.github.io/imagesloaded/
Then add one of the following code options to your existing JavaScript. (You may also need to remove all other attempts at detecting image loading errors from your code to make sure there is no conflict.)
This code should work just as well or better, and is even shorter.
General Observations
Interpretation: You cannot reliably catch image loading errors with a jQuery selector in a document where HTML content is being generated asynchronously from the jQuery command.
Therefore, when registering an error handler for an image (such as replacing it with a "no image available" image), it is important to register the error handler on the image before the image element has been inserted into the DOM or becomes a live document fragment. If your image
src
exists in a pure JavaScript string, you need to make sure than anonerror
handler is specified before that string is converted to a live document fragment. If your imagesrc
exists in a JavaScript image object, then it is already too late to specify an error handler--in that case, you must specify the handler before thesrc
.Otherwise, there is a race condition going on after the image element has been inserted into the DOM, and before registering the event handler. An image can return a 404 Not Found very quickly since no image data need to load. If the event handler is not fully attached before the 404 has been detected, then the event handler will not be called.
Having an error image be PNG rather than JPEG does not matter.
The way your code is constructed, it forms an HTML string then converts it to a live HTML object by using it as the argument to the jQuery constructor, and only then sets the error handler for the
img
elements that it contains. This causes the same problem as having HTML source that contains noonerror
attribute, and then afterwards trying to set the error handler with a script. The behavior you mentionThis works for me, just changing the last few lines in your JS Fiddle, and setting the attribute in the HTML string before anything is converted to actual live elements:
You indicated that this does not work, but it should (after changing
placeholder.jpg
and#container
to your own values), and a proper diagnosis requires me to see everything in your page, since anything else you are doing could affect why this isn't working for you.Other Options You Have (Updated)
Properly implementing the solution of using an error handler requires you to register the
onerror
/error
event handler before specifying thesrc
of the image in a live document fragment.There are other alternatives, however. For example, after the entire document is loaded, you can make a function which loops through all of the page's image elements, locating those that failed to load, and changing their
src
attribute.That solution is outlined here:
https://stackoverflow.com/a/93017/2188862
I can't guarantee that it will work because as I mentioned, anything else you are doing could affect why something isn't working for you.
This method of solving the problem also has a plug-in that may work with your code. https://github.com/desandro/imagesloaded
You can use it like this after first inserting script tags for the plug-in http://desandro.github.io/imagesloaded/imagesloaded.pkgd.min.js into your document:
Again, it is possible that something else you are doing will conflict with this plug-in, but it is definitely worth a try! It works perfectly in my example here:
http://jsfiddle.net/cookies/xdfjU/15/
Summary
Since image error events don't bubble, a global error tracking function solution is not possible, leaving two general ways of dealing with this problem:
src
is specified, but at minimum before it becomes a live HTML fragment or JavaScript image object. The advantage of this method is that error handling will happen immediately for each image, replacing the broken image icon with your placeholder image as soon as the 404 Not Found is detected.hmtlBuilder
string, that would require complete re-programming to allow images to individually have error handlers assigned.The ideal way to solve this problem (which doesn't exist) would be for browsers to "bubble" the image
error
event into a general document error event, which could be monitored by a single event handler, checking to see if the source of the error event was animg
element, and if so, then checking the kind of error and replacing an image not found with the desired placeholder.Once again, I would be happy to help you make one of these two correct solution methods work if you send me a link to your entire page. The specifics of making any JavaScript work with your code requires one to see all of your code since there can be other lurking variables.
.on('error' handler);
is available sincejQuery 1.7
image loading error don't bubble up and a delegate don't work binding a simple function works and is clean