Dynamic Slideshow

This application shows an example how one can utilise the feeds and child replication features in Blocks to populate a slideshow based on the content of a folder on the server.

Try it out

You need a computer with a running Blocks server (min version5.0) and a web browser to run this application note. Here's the Blocks root for this application note. See the general setup section for details on downloading, installation and how to open a display spot in your own browser.

:!: The feed script being used uses some late features in the script base. It you attempt to just pick the feed script and create the rest yourself, please make sure you are using the latest script base from github.

  • Open a browser window and create a display spot in the browser using the URL http://localhost:8080/spot, replace local host with your server address if you use a server on the network.
  • Make sure the Main block group is selected.
  • Assign the Slideshow block to the display spot by dragging the block to it.

The example slideshow should now show up on your display.

The Slideshow block

The block uses the child replication feature of Blocks. Edit the Slideshow block and have a look how it is configured. The outermost Block, aka the root Block, is a slideshow block. The interesting bit is down the bottom of the slideshow properties. Indexed property child replication is enabled and bound to the SimpleSlideshowFeed feed-script. This feed script will create one feed per slideshow folder stored under the public/feed/slideshows folder in the server. There is only one slide present here and that is the template, and this will automatically replicate once per item in the feed.

The template slide is very simple, just a Media URL block. The block Image URL binding is set to Relative/url where Relative in this case refers to the Slideshow and URL is the property this particular item in the feed.

As you may have noticed the editor will not show the block with the images, they are only there in runtime on the display spot.

The source directory

In the example the image source directories is found under /public/feed/slideshows/ There are two slideshow source directories present.

To use this type of set up in production and let a user manage the files, a smb share for this directory must be setup on the server. A separate user and credentials should be used with minimal permissions. This folder should ideally also be mapped up as a network resource on the users own computer. How to do this is out of scope for this application note.

Files can then be deleted and added as required using the native filesystem browser.

The folders are not picked up dynamically, any directories have to be present at server start. The content inside the directories may be changed whole the server is running.

Reload the slideshow to reflect changes in the source image files

This slideshow is not dynamic in the sense that changes in the slideshow folder is recognised automatically by the slideshow. Therefore, we must do something in blocks to reload the slideshow, there are a few methods that can be used:

  • Create a task that reloads the display spot at an interval.
  • Nest the slideshow in another slideshow and play the dynamic slideshow as slide 1 and perhaps some other content such as a logo or something as slide 2. This will make the slideshow reload at every rotation of the outer slideshow.
  • A button on a control panel can be used to manually reload the display.

The feed script

Feed scripts is very much a driver for feeds and are stored under /script/feed/ in the Blocks root directory.

More information regaring the Anatomy of a Feed Script can be found here.

This script is pretty well commented and looks like this:

/*	A Blocks feed script providing URLs to images in a set of designated directories on the server.
	Can be used to build slideshows dynamically, outside of Blocks, by simply populating those
	directories with suitable images.

 	Copyright (c) 2021 PIXILAB Technologies AB, Sweden (http://pixilab.se). All Rights Reserved.
 */

import {field} from 'system_lib/Metadata';
import { SimpleFile } from 'system/SimpleFile';
import {Feed, FeedEnv, FeedListSpec, StaticFeed} from 'system_lib/Feed';

export class Slideshows extends Feed {
	static readonly pathToSlideshows = '/public/feed/slideshows';

	constructor(env: FeedEnv) {
		super(env);

		// Obtain a list of subdirectories to pathToSlideshows, making a SlideshowFeed for each
		SimpleFile.list(Slideshows.pathToSlideshows, true).then(dirInfo => {
			if (!dirInfo.directories.length)
				console.warn("No slideshow directories found in", Slideshows.pathToSlideshows);
			for (const dirName of dirInfo.directories) {
				this.establishFeed(new SlideshowFeed(
					dirName,
					Slideshows.pathToSlideshows + '/' + dirName
				));
			}
		}).catch(error => console.error("Required directory not found", Slideshows.pathToSlideshows, error));
	}
}

/**
 * A single "feed", named by the directory containing its slides. Thus, to add more
 * slideshows, just add one more directory inside pathToSlideshows, with its images.
 */
class SlideshowFeed implements StaticFeed<SlideImageData, SlideImageData> {
	readonly listType = SlideImageData;
	readonly itemType = SlideImageData;

	constructor(
		readonly name: string,
		private readonly pathToSlides: string
	) {
		console.log("Established feed", name);
	}

	async getList(spec: FeedListSpec) {
		const filePaths = await SimpleFile.list(this.pathToSlides);
		/*	Use makeJSArray to convert the "array like" list of files to a true
			JS array in order to apply JS array functions such as sort and map.
		 */
		const sortedPaths = Feed
			.makeJSArray(filePaths.files)
			.sort()
			.map(path => new SlideImageData(path));
		return { items: sortedPaths };
	}

	/*	Since listType and itemType are both the same, there's no need
		to implement any getDetails function here.
	 */
}

/**
 * Data used by each individual slide. Just provides the URL from where to load the image.
 * This is used both as "listType" and "itemType" above, since it's so simple and there's
 * no additional data tpo fetch details for.
 */
class SlideImageData {
	@field("Image URL")
	readonly url: string;

	constructor(url: string) {
		this.url = url;
	}
}

Credits

Examples photos by Ethen Dow, Johannes Plenio, Kalen Emsley, Bailey Zindel, and Mostafa Meraji on Unsplash.