When displaying images in our web applications we never want to see a dead image shown when an image file has been moved or deleted. This can become a more prevalent problem when we are using dynamic image URLs or convention based URL structure. For example, we might always load a User's profile image by combining their ID number and Last name to form the filename and always looking in a specific assets folder - resources/profiles/12345_ashworth.jpg
.
The way to avoid having these dead image placeholders is to have a standard fallback image that we can use in place of the missing picture - often this is a grey silhouette of a head. This is all well and good but we generally don't know that an image is missing until it's too late - when the request for the image returns a 404.
We solve this problem by using the error event of the Ext.dom.Element
class to recognise the failure to load the image. This event encapsulates the error process of the underlying <img>
element - onerror. When this event fires we can then replace the src
attribute with the fallback placeholder image. This seems like a pretty simple solution (and it is!) but it's very effective in keeping our app's images nice and tidy.
We're going to demonstrate how to implement this by creating an extension of the Ext.Img
class which is present in both the Classic and Modern toolkits, so makes this a great Universal solution, albeit with a few toolkit specific tweaks!
The Solution
So, as I've said we're going to make a new component that extends the Ext.Img
class. Let's kick off and lay down the initial class code. You can explore the working code in the following Sencha Fiddle
We'll also make a quick test case to show the component in action - nothing fancy here, just rendering an image to the <body>
with a dud src
config.
As we expect this will result in a broken image placeholder being shown:
Reacting to a Broken Image
As we said earlier we need to use the error
event of the Ext.dom.Element class to determine if the image failed to load.
Classic
The Ext.Img
class caches a reference to the rendered <img>
element in the imgEl
property in the Classic toolkit which we can tap into from the onRender
method. We will override this method and add a listener to the event.
With this code in place we can rerun it and see the console log output in the Developer Tools.
Modern
The Modern toolkit uses a slightly different approach so we must adapt our solution slightly. The Ext.Img
class in Modern can use both a real <img>
element or a background image - this is determined by the mode
config. This means we must cater for both scenarios. The other different is that it already taps into the error
event so we must override it's existing functionality.
To do this we override the onError
method - we can add this to our Universal solution because in the Classic toolkit this method will just sit idle, whereas, in the Modern toolkit, the code we added in the last step will be unused. This isn't ideal but is neater than having lots of code spread around the project.
If you look in the framework's source we can see the onError
function is defined as below:
Replacing the Broken Image
Now that we can tell when an image hasn't loaded properly we can then replace it with an image that we know is there and will serve as an adequate fallback. We do this by simply setting the src
config (using the setSrc
setter) in this error
event handler and have that one display instead.
Rather than hardcode this value we will add a config
value - fallbackImg
- which can then we easily customised on a per image basis. In this case I've used my default Gravatar profile image.
Classic
For the Classic toolkit we simply make this call in the onImageError
method.
When we now run our example we can see the two image requests being made and our console.log
call happening in between them.
Modern
To add this functionality to the Modern toolkit we must replace part of the onError
method and call the setSrc
function instead.
This will replicate the Classic toolkit's functionality in the Modern. Perfect!
Complete Code
The complete code for the component can be found below. You're most welcome to use it wherever you like!
To see the code in action or to have a play with it, check out this Sencha Fiddle - https://fiddle.sencha.com/#fiddle/14dc
Conclusion
So there we are! We've created an extremely simple method of ensuring our app never has any dead images, keeping it much more professional looking and our users happy! All the while making it Universal so it works across both the Classic and Modern Toolkits.
Taking it Further...
I've kept this very simple to demonstrate the process but we could extend it further to have more features for example....
A pre-loaded placeholder to be shown before our real image has loaded. To do this we could immediately render the pre-loaded image and then load our real image in memory (using the JavaScript Image
class), once that has loaded then we can swap the URLs.
Enhance the class so that it only loads the real image when it comes into view - this could be really useful when being included in long lists as it will keep the data transfer overhead to a minimum - perfect for mobile devices!
Let me know if you've solved this problem in a different/better way in the past - I'd love to hear from you!