Why does my img error function fail?
Asked Answered
S

8

7

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):

enter image description here

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).

Sweettempered answered 18/7, 2013 at 2:4 Comment(13)
I've deleted my answer, and I'll say for anyone else who would suggest event delegation (e.g., with .on): error events do not bubble in most browsers, even though the spec says they should. The .on docs say this as well: "In all browsers... error events (e.g., on an <img> element) do not bubble. Such events are not supported for use with delegation..."Abohm
"Dang it!" <-- Kip Dynamite. I had high hopes for that methodology.Slob
@ClayShannon Try this solution, working JS Fiddle here: jsfiddle.net/cookies/xdfjU/15 It just requires inserting <script src="desandro.github.io/imagesloaded/…> into your document, plus the script code that is at the JS Fiddle link.Renelle
Why would I need to add that src val? Is that just supposed to [in,pro]voke the err condition? If so, no worries - I can do that with several of my imgs without any "outside help" that way.Slob
That script contains everything you need to perform the "other option" that I described in my answer below, i.e., finding all broken images as soon as the page has loaded. It is complicated to do this (and the simple way of doing it that I originally linked to in my answer did not work for you). You can see in my JS Fiddle that once that script has been included, then it is simple to find all broken images and change their src to a placeholder image of your choice. I am updating my answer to explain more clearly what to do, since the comment is putting ellipses into my instructions.Renelle
@ClayShannon I have posted several tested solutions all of which work 100%, with general community value. This is the most I can do until you link to or post all of your code or a self-contained example containing all your code that might have any relationship to your question. I think the issue now is helping you install the solution into your page rather than finding a solution. But to "install the solution" is the same as installing a new battery into a car. The car needs to be there in order for the installation to happen. :) Looking forward to helping make this work for you.Renelle
I thank you all for your assistance (both here and on the jQuery forum), but so far, nothing works. I have posted the pertinent code above, I think. I can do similar things that work, such as adding a click handler for imgs, but for some odd reason this is not working.Slob
You're welcome, but since none of the code your posted above does anything on its own, it is not all of the pertinent code. If our answers aren't working for you, that means we need more from you. You are treating all of the people answering your question like idiots saying that our answers don't work, when you haven't given us a chance to make them work for you. How do you expect anyone to feel like helping you any more?Renelle
BTW, @roasted below has a perfectly good answer, too, except you would need to rewrite your getNatlBookCritics() function to use DOM objects instead of strings before that method will work. If you don't want to post your code, then I suggest that you try at least rewriting that function to use roasted's method, and then perhaps roasted will help you finish.Renelle
@JosephMyers: If you will give me your email address (you can email me at bclayshannon at att dot net), I will send you the entire code. I am very curious as to why none of the solutions have worked.Slob
@ClayShannon Yes, I am curious as well. When you email it, be sure to put the file(s) into a folder and then zip it so that none of the scripts are removed by email spam filters (if you attach an HTML file directly, usually the scripts are deleted). My Stack Overflow email is e_mayilme at hotmail.comRenelle
It really seems to me that using delegate is logically the way to go (but somebody recommended that earlier, and that also didn't work).Slob
@JosephMyers: Thanks, I'll try to get it out tonight.Slob
G
3

use the images loaded plugin

https://github.com/desandro/imagesloaded

Leverages jQuery deferred objects awesomeness. This will even handle figuring out whether the image loaded for you, so you dont have to use jQuery.error (TBH, that's not really an appropriate use for that).

Guzman answered 18/7, 2013 at 2:4 Comment(7)
TBH == To Be Holistic? Truth Be Hassle-free?Slob
Both actually... How did you know!? :)Guzman
I'm not really seeing how that could help me; it does have a fail event, but I already know that it fails sometimes. It seems to be just a general fail for the entire operation; I need one for each instance of failure, to replace the busted pic with a generic one. The reason it seems to me to be just an overall "it failed" event is because the example shows: .fail( function() { console.log('all images loaded, at least one is broken'); })Slob
It's not a general fail. The general fail object has a list of all images you passed to the function stored in an array. There are a few ways you can go about using this. The simple way is to test each image for an isLoaded state... The more accurate way is to use the jQuery deferred object setup... You almost got there, just need to scroll down my link a little further ;)Guzman
Try the demo instead: desandro.github.io/imagesloaded <- load a bunch of images incessantly - eventually some will fail and you will see the broken image replacement in action.Guzman
I'm awarding you the bounty although I haven't tried this yet; I started a new question re: why my alt text is not displaying (#17957438)Slob
Trust me, it's a much simpler solution to your problem. Once you start using it, you won't go back - Images loaded is a very sophisticated & well written plugin... it was started by Paul Irish & built upon by a couple of very smart developers with a lot of community support.Guzman
E
6

Though your explaination is not really clear, the most probable reason is that your <img> tag is not there at the time of calling function $('img').error();

try

1) create a dynamic image with some id.
2) call error function after putting the the img tag in DOM.

The <img> tag is not bound with the error function, and that is the only reason I can see for not working of your code. now the working sample :

html:

<div id="myId">
    h
</div>

jQuery Script:

var jObject = $("<img src='helo.png' />");

$(jObject).error(function(){
    $(this).attr("src", "http://rack.2.mshcdn.com/media/ZgkyMDEyLzEyLzAzL2U0L3NlZWhvd3lvdXJnLjlyMS5qcGcKcAl0aHVtYgk5NTB4NTM0IwplCWpwZw/8fec6ce4/e71/see-how-your-google-results-measure-up-with-google-grader-video--6b8bbb4b41.jpg");

});

$("#myId").html(jObject);

you can check the working fiddle here [http://jsfiddle.net/jyXqw/][1]

EDIT *

var jObject = $(htmlBuilder);
 jObject.find("img").error(function(){
      $(this).attr("src", "Content/NoImageAvailable.png");
    });
 $('#BooksContent').append(jObject);

EDIT-2 * Check the fiddle, your code seems to be working perfectky fine: http://jsfiddle.net/jYbQx/

Eel answered 18/7, 2013 at 2:12 Comment(12)
Yes, I agree it's apparently not bound, but why? And how to rectify it? I don't want to just call the error function, because I don't know when it should be called - I don't just want to call it arbitrarily or as a matter of course.Slob
@ClayShannon kindly check my edited post ...working example included. :)Eel
So in a $.getJSON() call, where inside the .each section I build dynamic html, including references to images, that code should go within the .each part, at the bottom, prior to the next iteration of .each, correct?Slob
@ClayShannon can you please create a fiddle on www.jsfiddle.net. I can't find the code where you are actually putting it in DOM. $("#myId").html(jObject); THIS LINE IS IMPORTANT. AS THIS IMAGE ELEMENT HAS THE BIND EVENT ATTACHEDEel
The basic code (and a link to a fiddle) can be seen here: #17690144. I'm updating again with more specifics.Slob
secondly ... return jQuery object like $(htmlBuilder).find("img").error(function(){ your error function}); hope this would completely solve your problem ... fiddle don't have any javascript else than a buttonEel
Updated again (#3) with a verfication question for you.Slob
As I understand it now, though, wouldn't it be too late? All the html has already been added at this point...or does it "look back" for errors that had occurred when trying to render bogus img urls?Slob
let us continue this discussion in chatEel
Yep, it works there; not on my machine, though - for whatever reason.Slob
check for the version of jquery plugin u are usingEel
If you mean jQuery UI, I'm using 1.10.3 - the latest.Slob
G
3

use the images loaded plugin

https://github.com/desandro/imagesloaded

Leverages jQuery deferred objects awesomeness. This will even handle figuring out whether the image loaded for you, so you dont have to use jQuery.error (TBH, that's not really an appropriate use for that).

Guzman answered 18/7, 2013 at 2:4 Comment(7)
TBH == To Be Holistic? Truth Be Hassle-free?Slob
Both actually... How did you know!? :)Guzman
I'm not really seeing how that could help me; it does have a fail event, but I already know that it fails sometimes. It seems to be just a general fail for the entire operation; I need one for each instance of failure, to replace the busted pic with a generic one. The reason it seems to me to be just an overall "it failed" event is because the example shows: .fail( function() { console.log('all images loaded, at least one is broken'); })Slob
It's not a general fail. The general fail object has a list of all images you passed to the function stored in an array. There are a few ways you can go about using this. The simple way is to test each image for an isLoaded state... The more accurate way is to use the jQuery deferred object setup... You almost got there, just need to scroll down my link a little further ;)Guzman
Try the demo instead: desandro.github.io/imagesloaded <- load a bunch of images incessantly - eventually some will fail and you will see the broken image replacement in action.Guzman
I'm awarding you the bounty although I haven't tried this yet; I started a new question re: why my alt text is not displaying (#17957438)Slob
Trust me, it's a much simpler solution to your problem. Once you start using it, you won't go back - Images loaded is a very sophisticated & well written plugin... it was started by Paul Irish & built upon by a couple of very smart developers with a lot of community support.Guzman
R
3

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

<div id="tabs" class="content-wrapper">

you should add this code exactly as written to your page (I noticed that you had not included this in your page yet):

<script src="//desandro.github.io/imagesloaded/imagesloaded.pkgd.min.js">
</script>

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):

function configLoaded() {
    $('#lblLoading').hide();
    var imgLoad = imagesLoaded('body');                    /* NEW */
    imgLoad.on( 'progress', function( instance, image ) {  /* NEW */
        var result = image.isLoaded ? 'loaded' : 'broken'; /* NEW */
        image.img.src = 'Your-Placeholder-Image.png';      /* NEW */
    });                                                    /* NEW */
    //$('bookDropDown').Attr('disabled', false);
    //$('moviesDropDown').Attr('disabled', false);
    //$('musicDropDown').Attr('disabled', false);
}

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:

<script src="//desandro.github.io/imagesloaded/imagesloaded.pkgd.min.js">
</script>

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.)

var imgLoad = imagesLoaded('body');
imgLoad.on( 'always', function() {
  console.log( imgLoad.images.length + ' images loaded' );
  // detect which image is broken
  for ( var i = 0, len = imgLoad.images.length; i < len; i++ ) {
    var image = imgLoad.images[i];
    var result = image.isLoaded ? 'loaded' : 'broken';
    console.log( 'image is ' + result + ' for ' + image.img.src );
    if (result == 'broken')
        image.img.src = 'Your-Placeholder-Image.png'; /* CHANGE THIS */
  }
});

This code should work just as well or better, and is even shorter.

var imgLoad = imagesLoaded('body');
imgLoad.on( 'progress', function( instance, image ) {
  var result = image.isLoaded ? 'loaded' : 'broken';
  console.log( 'image is ' + result + ' for ' + image.img.src );
  image.img.src = 'Your-Placeholder-Image.png'; /* CHANGE THIS */
});

General Observations

"In all browsers, the load, scroll, and error events (e.g., on an <img> element) do not bubble. In Internet Explorer 8 and lower, the paste and reset events do not bubble. Such events are not supported for use with delegation, but they can be used when the event handler is directly attached to the element generating the event."

http://api.jquery.com/on/

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 an onerror handler is specified before that string is converted to a live document fragment. If your image src 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 the src.

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 no onerror attribute, and then afterwards trying to set the error handler with a script. The behavior you mention

This 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:

imageError = function(it) {
    $(it).attr("src","placeholder.jpg");
};

htmlBuilder = htmlBuilder.replace(/<img/g, '<img onerror="imageError(this)"');
var jObject = $(htmlBuilder);

 $("#container").html(jObject);

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 the src 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://mcmap.net/q/64338/-jquery-javascript-to-replace-broken-images

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:

var imgLoad = imagesLoaded('body');
imgLoad.on( 'always', function() {
  console.log( imgLoad.images.length + ' images loaded' );
  // detect which image is broken
  for ( var i = 0, len = imgLoad.images.length; i < len; i++ ) {
    var image = imgLoad.images[i];
    var result = image.isLoaded ? 'loaded' : 'broken';
    console.log( 'image is ' + result + ' for ' + image.img.src );
    if (result == 'broken')
        image.img.src = 'Content/NoImageAvailable.png'; /* DOUBLE CHECK THE SRC */
  }
});

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:

  1. A local way of individually registering an error handler for each image, preferably before its 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.
  2. A global way of locating broken images throughout the page. This method has the disadvantages of not immediately fixing broken images (lots of time may be spent loading other large images before finally the broken images will be fixed) and of not working for images which may be added to the page later after the initial loading event happens (e.g., images loaded later from Ajax data), but it has the advantage of working for images which are loaded into the page by various mechanisms, like your 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 an img 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.

Renelle answered 24/7, 2013 at 3:25 Comment(3)
Nope, that doesn't work, either. The only thing different in my code is the name of the replacement image: htmlBuilder = htmlBuilder.replace(/<img/g, '<img onerror="imageError(this)"'); ...and the id of the div that gets the html: $("#BooksContent").html(jObject);Slob
@ClayShannon Can you provide a link to the file on your site where I can simply refresh it a few times until I see some images that fail to load but don't show the error image properly? Then I could debug from there. I don't know if I can see what you are seeing on JS Fiddle, but I could probably see it if you had a normal page that I could just refresh until I see the problem happening.Renelle
@ClayShannon I see your fiddle here jsfiddle.net/clayshannon/cMYEH/1 but I don't see the JavaScript part of it. Is that the right link I should be looking at? I'll check back tomorrow if you don't have a chance to get back to me with a link before I get some sleep for the night.Renelle
B
3

Concerning your UPDATE 5 , no, there is no reason.

You should set error handler before setting attribute src:

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);

This is typically how you should do it. Test and see.

Bombast answered 24/7, 2013 at 9:6 Comment(0)
P
2

I had the same problem. the .error or other delegation methods (attached to the window/document/DOM node) did not work for me on <img> created dynamically after ajax response. My solution is using the onerror attribute inside the <img> tag (w3c standard) that calls a javascript function the removes the image and adds a noImg class to the parent node.

Inside the <img> tag:

onerror='replaceEmptyImage($(this))'

replaceEmptyImage function:

replaceEmptyImage = function($img) {
  $img.parent().addClass('noImg');
  $img.remove();
};

In css:

.noImg {
  background:url(path/to/placeholder/image);    
}

See http://jsfiddle.net/tJpaR/5/

Point answered 18/7, 2013 at 2:4 Comment(10)
What error do you get? your $img.parent() is an <a>. maybe you need to go up the dom another level or give your <a> tag a width, height and display:block style propertiesPoint
Actually, the image "object" displays now, it's just that it's empty. I'll post a scream shot to show you what it looks like (that'll be Update 9).Slob
<a> is an inline element with the inner node width and height. when you remove the inner html you get a 0X0 element. you can remove the <a> like here: jsfiddle.net/tJpaR/7 or give some style to the <a> like here: jsfiddle.net/tJpaR/6Point
The only error (although there are over a dozen "empty" images) in the CDT console is "Failed to load resource: the server responded with a status of 404 (Not Found)"Slob
What do you mean by "the image "object" displays now"? Is the class noImg applied? If you want to keep the link around the not-found image look at jsfiddle.net/tJpaR/6Point
Just what is in the scream shot: that you can tell there's "something" there - previously it was just bla[c,n]k. Probably because I added the shadow to it, though.Slob
OK, seems like the JS works just fine. It means that the problem is now in the stylesheet. 1) Is the ~/Content/NoImageAvailable.png visible to the webapp? setting background:url(~/Content/NoImageAvailable.png); makes it relative the the web page. try absolute url. 2) Is there any css instruction overriding the new background one? What the DOM inspector says?Point
Actually, the shadow around the image has been there the last week or two. Prior to that, the only way one would expect an image belongs there is the same way one expects a certain element to be in a certain spot in Mendelyev's periodic table of elements (no pun intended) - it was just an empty area. Now, the gold shadow shows that "something" should be there. IOW, I don't think your js did anything more than any of the other attempts did (they all work in theory, but none in reality). It's probably something quirky on my end, something "obvious" that I'm failing to do or prevent.Slob
I agree that there is something "obvious" in your end the doesn't work. The empty area have a border or background definition that might break the background url. or a z-index or path issue. If you have a way to publish the work-in-progress site I can give you a hand finding that somethingPoint
Thanks, but it won't be publicly available for a couple of months.Slob
N
2

You edit seems to be the issue. Having a fallback should fix your issue

Negative answered 23/7, 2013 at 20:31 Comment(2)
Well, I have used the err function others have provided, but it hasn't worked. Are you saying the image file type mismatch is the problem? (I won't be able to test it out until I get home from work, but "enquiring minds want to know!").Slob
Based on this: jsfiddle.net/tJpaR/1, that's probably not the problem, as a jpg is sought, and then a png is (successfully) the fallback img.Slob
H
2

Multiple problems here potentially...

Event creation/bubbling

You shouldn't need to bind .error on every img. Instead try this once before you enter your loop:

$('body').on('error', 'img', function (e) {
    $(e.currentTarget).attr("src", "Content/NoImageAvailable.png");
});

Due to event bubbling, every parent element of an img fires an error event as well. So theres no need to bind using .error in the loop.

Next...

Using Templates/Avoiding HTML in JS

You don't need to use something like handlebars or underscore's templating language; however you should attempt to at least avoid any html in your JS. It is prone to error. Your img markup (for instance) isn't valid.

Try the following for your images:

var $img = $('<img>', {
    'src': dataPoint.imgsrc
});

You could start your loop by creating a section:

var $output = $('<section>', {
    'class': 'wrapper'
});

Then as you loop you can append to it:

$output.append($img);

This reduces the surface area for messing up. If you want to further reduce the surface area you could use a template language like handlebars.

Lastly...

Image mime types? Network traffic?

What does your network traffic say is returned by the calls to the images? Does it say 200 success? Does it say 400? Open chrome's developer tools -> click the Network tab. Observe the traffic and try to see what it says about your images.

What's the server code? Is it 200? If it is 200 what's the mime type? Did you properly configure S3? Is the image URL being modified from the JSON in some way?

Harlequinade answered 25/7, 2013 at 4:9 Comment(5)
I will look into all this when I get the chance (probably not until Friday night), but I will say that over 95% of the images load fine, and everything looks good and works well. The images that don't simply don't exist (they are very obscure books that, if there was an image for, it would be the value I'm providing, but there's not) - but I don't know in advance just which ones those are.Slob
So that code you start off with could go in the ready handler, correct? What is "S3"?Slob
CDT shows almost all are "304 Not Modified"; I saw one "200 OK" and a few of the images' Types are supposedly "image/gif" although they all have a .jpg extension.Slob
Oh, and a few (expected) "404"s, too.Slob
@ClayShannon Yea, the code I started with could go in the ready handler. 304 and 200 are both ok codes. 304 means the image is cached and doesn't need to be re-downloaded, 200 is downloaded and OK. Also, as far as the mismatched types gif vs jpg, well, that may result in an error. Not quite sure what's going on there, but it may result in the image not appearing correctly. Probably something in the upload process broke down.Harlequinade
Y
1

.on('error' handler); is available since jQuery 1.7 image loading error don't bubble up and a delegate don't work binding a simple function works and is clean

        function noImage(event){
            $(event.target).attr('src', 'media/noImg.jpg');
        }
        $(function (){
            $('img').on('error', noImage);
            $("#neues_bild_button").on('click', function(ev){
                var newImg = $("<img src='andersBild.jpg'/>");
                newImg.on('error', noImage);
                $('#i2').after(newImg);
            });
        });
Yevetteyew answered 18/7, 2013 at 2:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.