Fallback handling for $image widget

@EricShulman , like your refinements.

One query, your ‘let’ statements wherein you include an ‘else’ alternative, is it possible to do something similar to an $image statement, trying to load an image from, say, local file store and if Not there, load from an http url?

Eg, <$image source="Media/a.jpg" else <baseURL>"Media/a.jpg" />

Bobj

If would be nice if the $image widget had support for “fallback” content when an image fails to load, similar to the way the $transclude widget permits fallback content if the specified tiddler doesn’t exist. For example, something like this:

<$image source="Media/a.jpg">image unavailable</$image>

If such a fallback mechanism existed, it would also be possible show an alternative remote image if the local image doesn’t exist, by writing something like this:

<$image source="Media/a.jpg">
   <$image source={{{ [<baseURL>addsuffix[/Media/a.jpg] }}}/>
</$image>

Unfortunately, the current implementation of the $image widget does NOT support the use of fallback content. The documentation for the $image widget (see https://tiddlywiki.com/#ImageWidget) specifically notes that “Any content of the <$image> widget is ignored.

However, the $image widget DOES currently have the ability to detect when an image loads (or fails to load) and in response, adds either a tc-image-loaded or tc-image-error CSS class to the img DOM element. So, in theory, it should be possible to extend the underlying TWCore javascript code to add handling for fallback content and then only display that content if the tc-image-error class is added to the img DOM element.

I will try to dig into the code a bit and see what can be achieved. If I can make it work, I’ll open a ticket to propose adding it to the TWCore.

-e

4 Likes

Thanks @EricShulman

Bobj

I think it would be useful to have a TW event that can be used to determine the status of of any Load from an external source.

Bobj

OK! I’ve got it working… and it was only 4 additional lines of code in $:/core/modules/widgets/image.js:

in the definition of ImageWidget.prototype.render, find these lines (near the end of the function):

	domNode.addEventListener("error",function() {
		$tw.utils.removeClass(domNode,"tc-image-loading");
		$tw.utils.addClass(domNode,"tc-image-error");
	},false);
	// Insert element
	parent.insertBefore(domNode,nextSibling);
	this.domNodes.push(domNode);

and change to these (adding the lines marked with /* ELS */ comments):

	domNode.addEventListener("error",function() {
		$tw.utils.removeClass(domNode,"tc-image-loading");
		$tw.utils.addClass(domNode,"tc-image-error");
		domNode.self.makeChildWidgets(); /* ELS */
		domNode.self.renderChildren(domNode.parentNode,domNode.nextSibling); /* ELS */
		if(domNode.self.children.length!=0) domNode.style.display="none"; /* ELS */
	},false);
	// Insert element
	parent.insertBefore(domNode,nextSibling);
	this.domNodes.push(domNode);
	domNode.self=this; /* ELS */

Notes:

  • in the event listener “error” function, there are three new lines at the end:
    • domNode.self.makeChildWidgets(); builds the “widget tree” for the fallback content
    • domNode.self.renderChildren(...); renders the child widgets as DOM elements (HTML)
    • If there are rendered child DOM elements, then hide the missing “broken image” display
  • at the very end of the render(), save the image widget object (this) so it can be referenced in the event listener as domNode.self

Results:

  • If the outer $image widget is loaded successfully, then the error handler is never triggered, the image renders normally, and no fallback content is processed.
  • If the outer $image widget fails to load, the error handler is triggered:
    • any enclosed fallback content is rendered.
    • If there is rendered fallback content, the browser’s usual “broken image” is hidden, otherwise the “broken image” remains visible.

Here’s a JSON containing the modified $image widget code, as well as a test image (“jeremy.jpg”) and some wikitext to test the $image widget handling (“Test Image Fallback”).

ImageFallback.json (48.5 KB)

I’ve opened a Github [Idea] “issue” ticket to propose adding this change to the TWCore:

enjoy,
-e

@pmario: How do I split this thread to move this post (and the related “fallback image” discussion posts) to a new thread?

5 Likes

See: How to Split a Discourse Thread

@EricShulman , what can I say that I’ve not said before?? Oh I know, its the King’s Birthday here so a proclamation might be in order or maybe even an Copanion Order of Australia for services to Tiddlywiki :slight_smile:

bobj

2 Likes

@EricShulman works but for one funny.

My code


\procedure displayImageCaptionCopyright(ICCTitle,ICCWidth:"100%")

<$transclude $variable="displayimageonly" imageTitle=<<ICCTitle>> imageWidth=<<ICCWidth>> />
<br>
<$transclude $tiddler=<<ICCTitle>> $field="imageCaption" />
<br>
<$transclude $tiddler=<<ICCTitle>> $field="imageCopyright" />

\end

\procedure displayimageonly(imageTitle,imageWidth:"100%")

<$let 
     baseURL={{{ [{$:/TLS/onlineImages!!onlineimageURL}] }}}
     onlineURL={{{ [<imageTitle>get[imageURL]] }}}  
>
<$image source={{{ [<imageTitle>get[imageURL]] }}} width=<<imageWidth>> loading="lazy" >
<$image source={{{ [<baseURL>addsuffix<onlineURL>] }}} width=<<imageWidth>> loading="lazy" />
</$let>
\end

The display is

As you can see, there is a </$let> statement after the image. If I remove that statement from the code, the statement also disappears from the rendered tiddler.

Any idea why it is there?

bobj

You are missing </$image> to close the “outer” $image widget.

<$let 
     baseURL={{{ [{$:/TLS/onlineImages!!onlineimageURL}] }}}
     onlineURL={{{ [<imageTitle>get[imageURL]] }}}  
>
<$image source={{{ [<imageTitle>get[imageURL]] }}} width=<<imageWidth>> loading="lazy" >
   <$image source={{{ [<baseURL>addsuffix<onlineURL>] }}} width=<<imageWidth>> loading="lazy" />
</$image>
</$let>

-e

Bugger…

Maybe, though, it’s a hint to slightly change things a bit. The problem is the widget within a widget and the internal one having the expected /> tag which confused me.

Might it not be more explicit to have a fallback parameter in the first widget tag? That way, only a single close tag.

I’m not being picky but I’m sure others will make the same mistake.

Apart from that it works very well though.

Bobj

Using a “fallback parameter” would only permit specifying an alternative image source (local file or URL). In contrast, by using the $image widget’s inner content, you can render ANY kind of wikitext as the fallback output (e.g. a formatted text message, a table, a form to select a different image, etc.).

In addition, while it might be a little bit confusing at first, the “widget within a widget” code pattern (<$outerwidget><$innerwidget/></$outerwidget>) is already well-established in TiddlyWiki for use with other kinds of widgets.

For example, rendering a tiddler caption with fallback to the tiddler title, like this:

<$view field="caption">
   <$view field="title"/>
</$view>

or, providing a remote URL link as a fallback when transcluding a missing tiddler title, like this:

<$transclude tiddler="TiddlyTools/SaveChanges/Info" mode=block>
   For documentation, see https://TiddlyTools.com/#TiddlyTools/SaveChanges/Info
</$transclude>

(I’ve used the above technique to directly show the …/Info tiddler contents if present, or a fallback message and link to a remote URL if the …/Info tiddler is missing).

-e

1 Like

Ah, I understand and agree, keep the current format. I had not come across this before.

Thank you for your understanding of my ignorance.

Bobj

currently I use the object tag for image fallback (only works with external images):

			<object data=<<card!!image>> >
				<object data=<<card!!altfavicon>> >
					{{$:/core/images/globe}}
				</object>
			</object>

<<card!!image>> is a url.

Interesting, must look this widget up.

This has opened up so many areas of Tiddlywiki I have not come across before.

Bobj

@EricShulman , can this fallback facility be extended to include PDF files? If so how can I do that?

bobj

FYI: <object> is a html tag and not a widget. Much of html can be used in tiddlywiki. A somewhat underutilised fact.

EDITED BY @jeremyruston 13th August 2025

Please ignore this code. It is a garbled mess that could not possibly work.

We’ve discussed elsewhere that as well as clearly marking LLM generated code, it is incumbent on the poster to verify that the code does perform as expected.


@EricShulman , I asked ChatGPT how we might accomplish a similar fallback facility for PDF files. It came back with

In TiddlyWiki, you can use a combination of JavaScript and `img` or `embed` elements to handle a fallback for loading a PDF. Unfortunately, there is no direct built-in support for this, but you can create a custom solution using the HTML and JavaScript within TiddlyWiki.

Here’s an approach using JavaScript and an embedded `<iframe>` to display the PDF. If the first PDF fails to load, it will fall back to the second PDF:

### TiddlyWiki Code:

```html
<$wikify bind="$pdfUrl">
  <div class="pdf-container">
    <iframe id="pdfViewer" width="100%" height="600px" src="{{pdfUrl}}"></iframe>
  </div>

  <script>
    (function() {
      var pdfUrl = $pdfUrl;
      var fallbackUrl = "https://example.com/alternate.pdf";  // Put your fallback PDF URL here
      var iframe = document.getElementById("pdfViewer");

      function checkPdfLoading() {
        var pdfIframe = iframe.contentWindow || iframe;
        
        // Try to catch if the PDF doesn't load after some time
        setTimeout(function() {
          if (!pdfIframe.location || pdfIframe.location.href === "about:blank") {
            iframe.src = fallbackUrl; // Set the fallback URL if the PDF didn't load
          }
        }, 3000); // 3 seconds to check (you can adjust this)
      }

      // Initial check when the page loads
      iframe.onload = checkPdfLoading;

      // Check after setting the source of the iframe
      checkPdfLoading();
    })();
  </script>
</$wikify>

### Explanation:

1. **Initial PDF URL**: The main PDF is loaded using an iframe that links to a PDF URL.
2. **Fallback Logic**: A simple JavaScript function `checkPdfLoading` tries to detect whether the PDF is loaded correctly. If it’s not loaded within a few seconds, the `iframe.src` will be switched to a fallback PDF URL.
3. **Timeout for Checking**: The timeout (`setTimeout`) checks if the iframe content fails to load (i.e., stays blank for a few seconds), triggering the fallback.

### How to Use:

* Replace `{{pdfUrl}}` with the URL or reference to your primary PDF.
* Set your fallback PDF URL in the `fallbackUrl` variable.
* Adjust the timeout value or logic as needed to suit your use case.

This method relies on the fact that the iframe will fail to load a PDF if the source is incorrect or inaccessible. If the PDF fails to load, it switches the source to the fallback URL.

I haven’t tried it yet but thought you’d be interested anyway.

bobj

I suspect this is a halucination as the script tags are sanitised by default.

  • Bind is not a parameter on the wikify widget and “$pdfUrl” an odd value.

I’ve edited @Bob_Jansen’s post with the following note:


EDITED BY @jeremyruston 13th August 2025

Please ignore this code. It is a garbled mess that could not possibly work.

We’ve discussed elsewhere that as well as clearly marking LLM generated code, it is incumbent on the poster to verify that the code does perform as expected.


2 Likes

Apologies to this list. I did state it was the result of a ChatGPT query and I was going to give it a go. As @jeremyruston indicated, it didn’t work at all.

Still looking for a solution to pdf file display fallback if anyone can advise

bobj