2

I have this piece of code.

<img data-bind="attr: {src: 'imagePath'}, style: { 'background-image': 'url('imagePath')' }" class="img-responsive">

The problem is it is showing two images. One is the image coming from src and other one coming from background image. My goal was to enable the background image when the src image is not available.

user2281858
  • 1,957
  • 10
  • 41
  • 82
  • Why don't you only use the `src` attribute, and wire it to an observable that will resolve the image source when it becomes available? – cl3m Feb 15 '16 at 20:25
  • Default to showing the fallback image, and replace it if and when the other image is found. – Gary Hayes Feb 15 '16 at 20:45
  • Using a background image as the alternate will create some problems, e.g., it will still display a broken image icon and you will need to set the height and width. That is why using only the src seems a better solution. – Yogi Feb 15 '16 at 21:02
  • Just wondering if my answer did, in fact, answer your question? – JDR Feb 20 '16 at 23:24

3 Answers3

9

What you can do is create a custom binding, let's call it safeSrc.

In this binding, you listen to the load and error events of your image - rendering your image if it's loaded successfully and rendering a fallback if it is not.

In practice, it could look like the following:

ko.bindingHandlers.safeSrc = {
  update: function(element, valueAccessor) {
    var options = valueAccessor();
    var src = ko.unwrap(options.src);
    $('<img />').attr('src', src).on('load', function() {
      $(element).attr('src', src);
    }).on('error', function() {
      $(element).attr('src', ko.unwrap(options.fallback));
    });
  }
};

And then, you can apply the binding like this:

<img alt="Foo" data-bind="safeSrc: {src: imageObservable, fallback: 'https://example.com/fallback.png'}" />

Note that this presumes you're using jQuery - but you can easily rewrite the event listener.

Finally, I would also like to say that you should be rendering a different src instead of the background image - unless you have a specific reason to require one?

Either way, you can simply change the line $(element).attr('src', ko.unwrap(options.fallback)); to $(element).css('background-image', 'url(' + ko.unwrap(options.fallback) + ')');.

JS Fiddle Demo

Here, you can see it all in action: https://jsfiddle.net/13vkutkv/2/

(EDIT: I replaced the cheeky hotlink to the Knockout JS logo with Placehold.it)

P.S.: Depending on how you wish to interact with the element in future (interacting with/updating the binding), you may wish to use applyBindingsToNode (i.e. the Knockout-way), rather than manipulating the src attribute directly on the DOM element.

JDR
  • 1,094
  • 1
  • 11
  • 31
  • Be careful that the fallback image doesn't fail and trigger the on error, it can end up in an infinite loop. – Ed' Feb 16 '16 at 08:04
  • Very good point. But I have already accounted for that issue: this binding creates a virtual image element for the purpose of checking if it is loaded successfully, rather than using the original DOM element that's passed into the binding. Unless I misunderstood your point. – JDR Feb 16 '16 at 08:09
  • Ahh yes, nice solution! I didn't pay that much attn to the code as it looked so similar to something I'd implemented. Might have to pinch that idea for future use. – Ed' Feb 16 '16 at 09:43
  • No worries. But you're right that this solution doesn't take care of a missing fallback image - let's leave it up to the developer not to fall back on a 404. :) – JDR Feb 16 '16 at 09:46
0

To show alternate image if img src is not found make alternate image link in your server logic and use only src: 'imagePath' in your front-end

Or if it is important to do it in front-end, you should look at this post: Display alternate image

Community
  • 1
  • 1
Mi Ka
  • 135
  • 1
  • 9
0

I always check my images with a deferred object to be sure they will load. This is using the jquery deferred method, but you could use any deferred library. I coded this from memory, so there may be some errors.

<img data-bind="attr: {src: $root.imagePath()}, style: { 'background-image': 'url('imagePath')' }" class="img-responsive">

var myController = function()
{
    var self = this;
    this.imagePath = ko.observable('myPath.png'); // Make the image url an observable
    var getImagePath = function(path)
    { 
        var imagePath = this.imagePath();
        isLoaded(imagePath).done(function(result)
        {
            // The image will load fine, do nothing.
        }).fail(function(e)
        {
            self.imagePath('defaultImageOnFail.png'); // replace the image if it fails to load
        });
    };
    getImagePath();
};

var isLoaded = function(img)
{
    var deferred = new $.Deferred();
    var imgObj = $("<img src='"+img+"'/>");
    if(imgObj.height > 0 || imgObj.width > 0)
    {
        deferred.resolve(true);
    }
    else
    {
        imgObj.on("load", function(e)
        {
            deferred.resolve(true);
        });
        imgObj.on("error", function(e)
        {
            console.info("VoteScreenController.isLoaded URL error");
            deferred.reject();
        });
    }
    return deferred.promise();
};
Mardok
  • 1,360
  • 10
  • 14