Kookaburra Pages and Albums / Album Sets

<div id="albumPlaceholder" />

<script>
  const albumMarkup = // whatever markup you've generated for the album

  const placeholder = document.getElementById("albumPlaceholder");
  placeholder.appendChild(albumMarkup);
</script>

But I would change how those tabs work. I’ll try to put something together later today.

Do I have to insert this code somewhere in the page or in the phplugins.php?

I know, stupid question but like this???

<script>
  const albumMarkup = [album:123456] // whatever markup you've generated for the album

  const placeholder = document.getElementById("albumPlaceholder");
  placeholder.appendChild(albumMarkup);
</script>

No. That’s not going to work at all. Like I said, I’ll slap something together later on.

Thank you so much!

Okay, here’s the HTML.

  <ul class="tabs">
    <li data-album="123456">One</li>
    <li data-album="789098">Two</li>
  </ul>

And here’s the PHP function that embed the JavaScript:

		// Use the JavaScript JSON API to get album data;
		// logs the response to the browser's debug console.
		function scripts() {
			$siteURL = siteURL();
			$apiPath = "{$siteURL}backlight/" . (URLHelper::rewriteEnabled() ? "api" : "?m=publisher&c=jsonApi");
			$apiToken = AuthHelper::getJSONAPIToken();

			echo <<<HTML
			<script src="{$siteURL}backlight/modules/module-publisher/lib/js/api.js"></script>
			<script>
				// instantiate the api.
				const backlight = new Backlight("{$apiPath}");

				// create a placeholder for our images.
				const albums = {};

				// create an HTML template to be used for individual photos
				const renderTemplate = photo => (`
					<figure itemscope itemtype="http://schema.org/ImageObject">
						<img alt="\${photo.title}" src="\${photo.path}thumbnails/\${photo.filename}" />
					</figure>
				`);

				// create a function to update the view.
				// * pumps photos into the template, above;
				// * appends the images to the page.
				const updateAlbumView = (listEl, albumId) => {
					listEl.nextSibling.remove();

					const view = document.createElement("div");
					view.classList.add("dynamic-album-view");

					let output = "";
					Object.values(albums[albumId]).forEach(photo => {
						output += renderTemplate(photo);
					})
					view.innerHTML = output;
					listEl.insertAdjacentElement("afterend", view);
				}

				// create a `get` function to fetch album data by albumId.
				const getAlbum = (listEl, albumId) => {
					if (albums[albumId]) {
						updateAlbumView(listEl, albumId);
					} else {
						backlight.getAlbum(albumId, {
							apiToken: "{$apiToken}",
							done: data => {
								const { id, photos } = data.album;
								albums[id] = {};
								photos.forEach(photo => {
									albums[id][photo.id] = photo;
								});

								updateAlbumView(listEl, id);
							},
						});
					}
				}

				// keeps the tab list up-to-date
				const setSelectedIdOnList = (listEl, albumId) => {
					listEl.dataset.selected = albumId;
					return albumId;
				}

				// on page load, get and set first album
				const listElements = document.querySelectorAll(".tabs");
				for (const listEl of listElements) {
					const firstAlbumId = listEl.querySelector("[data-album]").dataset.album;
					setSelectedIdOnList(listEl, firstAlbumId);
					getAlbum(listEl, firstAlbumId);
				}

				// listen for clicks;
				// if click event occurs within `tabs` and yields an album ID, fetch album data.
				document.addEventListener("click", (event) => {
					const listEl = event.target.closest("ul.tabs");

					if (!listEl) return; // not clicking a tab; eject!

					const selectedId = setSelectedIdOnList(
						listEl,
						event.target.dataset.album,
					);

					for (const tab of listEl.children) {
						if (tab.dataset.album === selectedId) {
							tab.classList.add("selected");
						} else {
							tab.classList.remove("selected");
						}
					}

					getAlbum(listEl, selectedId);
				});
			</script>
HTML; // do not indent this line
			return false;
		} // END /**/

You’ll need to do your own CSS, and you will probably want to update the renderTemplate function to be more robust. Like, add fancybox attributes to open large images. Just look at Kookaburra’s album grids for reference, and mind your syntax.

Dear Matt, THANK YOU! You are genius!
The images are now on the page in the specified tab and I think I’ll get the rest working somehow.

Again, thank you very much for your help and of course for your all engagement.

Best regards,
Oliver

Hi all,
because I am not sure on how to update the renderTemplate function I made a test and asked ChatGPT.
See the following conversation:

how to update the renderTemplate function in phplugins
ChatGPT

The “renderTemplate” function in PHPlugins is typically used to render HTML templates with dynamic content. To update the function, you’ll need to modify the code that generates the HTML output.

Here’s an example of how you could update the renderTemplate function in PHPlugins:

php

function renderTemplate($template, $data = array()) {
// Load the template file
$templateFile = PHPLUGINS_DIR . ‘/templates/’ . $template . ‘.php’;
if (!file_exists($templateFile)) {
throw new Exception("Template file not found: " . $templateFile);
}
// Load the template data
extract($data);
// Start capturing the output
ob_start();
// Include the template file
include $templateFile;
// Return the captured output
return ob_get_clean();
}

In this updated function, we first check if the template file exists and throw an exception if it does not. We then extract the data array into variables, which allows us to use the data in the template file. Next, we start capturing the output using the ob_start function, which will allow us to modify the output before it is sent to the browser. Finally, we include the template file and return the captured output using ob_get_clean.

You can modify this updated function to suit your specific needs, such as by adding additional checks or modifying the output format. Keep in mind that any changes you make to the function could affect the functionality of your PHPlugins installation, so it’s important to thoroughly test any changes before deploying them to a production environment.
User
und auf deutsch
ChatGPT

Die “renderTemplate” Funktion in PHPlugins wird normalerweise verwendet, um HTML-Vorlagen mit dynamischem Inhalt zu rendern. Um die Funktion zu aktualisieren, müssen Sie den Code ändern, der die HTML-Ausgabe generiert.

Hier ist ein Beispiel, wie Sie die renderTemplate Funktion in PHPlugins aktualisieren könnten:

php

function renderTemplate($template, $data = array()) {
// Vorlagendatei laden
$templateFile = PHPLUGINS_DIR . ‘/templates/’ . $template . ‘.php’;
if (!file_exists($templateFile)) {
throw new Exception("Vorlagendatei nicht gefunden: " . $templateFile);
}
// Vorlagendaten laden
extract($data);
// Beginne die Ausgabe zu erfassen
ob_start();
// Vorlagendatei einbinden
include $templateFile;
// Erfasse die Ausgabe und gib sie zurück
return ob_get_clean();
}

In dieser aktualisierten Funktion überprüfen wir zuerst, ob die Vorlagendatei existiert, und werfen eine Ausnahme, wenn dies nicht der Fall ist. Wir extrahieren dann das Datenarray in Variablen, was es uns ermöglicht, die Daten in der Vorlagendatei zu verwenden. Als Nächstes erfassen wir die Ausgabe mit der ob_start Funktion, die es uns ermöglicht, die Ausgabe zu modifizieren, bevor sie an den Browser gesendet wird. Schließlich binden wir die Vorlagendatei ein und geben die erfasste Ausgabe mit ob_get_clean zurück.

Sie können diese aktualisierte Funktion entsprechend Ihren spezifischen Anforderungen anpassen, z. B. indem Sie zus
User
how to add fancybox attributes to open large images in phplugins.php?
ChatGPT

To add Fancybox attributes to open large images in PHPlugins, you can modify the existing code that generates the HTML output for the images. Here’s an example of how you could modify the code:

php

function renderImage($imageUrl, $altText = ‘’, $width = null, $height = null) {
// Generate the HTML for the image
$html = ‘’;
$html .= ‘<img src="’ . $imageUrl . ‘" alt="’ . $altText . ‘"’;
if ($width !== null) {
$html .= ’ width="’ . $width . ‘"’;
}
if ($height !== null) {
$html .= ’ height="’ . $height . ‘"’;
}
$html .= ‘>
’;

// Return the HTML
return $html;

}

In this updated function, we’ve added a class of “fancybox” and a “data-fancybox” attribute with a value of “images” to the anchor tag that wraps the image. This tells Fancybox to open the image in a lightbox when clicked.

You’ll also need to include the necessary Fancybox files in your HTML output. Here’s an example of how you could include the Fancybox files:

html

Make sure to include these files before the closing tag in your HTML output.

With these modifications, when an image is displayed using the renderImage function, it will be wrapped in an anchor tag with the appropriate Fancybox attributes, allowing it to be opened in a lightbox.
User
the following code is part of my phplugins.php: // Use the JavaScript JSON API to get album data;
// logs the response to the browser’s debug console.
function scripts() {
$siteURL = siteURL();
$apiPath = “{$siteURL}backlight/” . (URLHelper::rewriteEnabled() ? “api” : “?m=publisher&c=jsonApi”);
$apiToken = AuthHelper::getJSONAPIToken();

		echo <<<HTML
		<script src="{$siteURL}backlight/modules/module-publisher/lib/js/api.js"></script>
		<script>
			// instantiate the api.
			const backlight = new Backlight("{$apiPath}");

			// create a placeholder for our images.
			const albums = {};

			// create an HTML template to be used for individual photos
			const renderTemplate = photo => (`
				<figure itemscope itemtype="http://schema.org/ImageObject">
					<img alt="\${photo.title}" src="\${photo.path}thumbnails/\${photo.filename}" />
				</figure>
			`);

			// create a function to update the view.
			// * pumps photos into the template, above;
			// * appends the images to the page.
			const updateAlbumView = (listEl, albumId) => {
				listEl.nextSibling.remove();

				const view = document.createElement("div");
				view.classList.add("dynamic-album-view");

				let output = "";
				Object.values(albums[albumId]).forEach(photo => {
					output += renderTemplate(photo);
				})
				view.innerHTML = output;
				listEl.insertAdjacentElement("afterend", view);
			}

			// create a `get` function to fetch album data by albumId.
			const getAlbum = (listEl, albumId) => {
				if (albums[albumId]) {
					updateAlbumView(listEl, albumId);
				} else {
					backlight.getAlbum(albumId, {
						apiToken: "{$apiToken}",
						done: data => {
							const { id, photos } = data.album;
							albums[id] = {};
							photos.forEach(photo => {
								albums[id][photo.id] = photo;
							});

							updateAlbumView(listEl, id);
						},
					});
				}
			}

			// keeps the tab list up-to-date
			const setSelectedIdOnList = (listEl, albumId) => {
				listEl.dataset.selected = albumId;
				return albumId;
			}

			// on page load, get and set first album
			const listElements = document.querySelectorAll(".tabs");
			for (const listEl of listElements) {
				const firstAlbumId = listEl.querySelector("[data-album]").dataset.album;
				setSelectedIdOnList(listEl, firstAlbumId);
				getAlbum(listEl, firstAlbumId);
			}

			// listen for clicks;
			// if click event occurs within `tabs` and yields an album ID, fetch album data.
			document.addEventListener("click", (event) => {
				const listEl = event.target.closest("ul.tabs");

				if (!listEl) return; // not clicking a tab; eject!

				const selectedId = setSelectedIdOnList(
					listEl,
					event.target.dataset.album,
				);

				for (const tab of listEl.children) {
					if (tab.dataset.album === selectedId) {
						tab.classList.add("selected");
					} else {
						tab.classList.remove("selected");
					}
				}

				getAlbum(listEl, selectedId);
			});
		</script>

HTML; // do not indent this line
return false;
} // END /**/

Which code do I have to implement to add fancybox attributes to open large images?
ChatGPT

To add fancybox attributes to open large images, you will need to modify the renderTemplate function to include the required HTML attributes. Here is an example of how you can modify the function to include the attributes for fancybox:

kotlin

// create an HTML template to be used for individual photos
const renderTemplate = photo => ( <figure itemscope itemtype="http://schema.org/ImageObject"> <a href="${photo.path}${photo.filename}" data-fancybox="gallery" data-caption="${photo.title}"> <img alt="${photo.title}" src="${photo.path}thumbnails/${photo.filename}" /> </a> </figure>);

In this example, we have added the following attributes to the a tag:

href: This is the URL of the full-size image that will be displayed in fancybox when the thumbnail is clicked.
data-fancybox: This attribute tells fancybox that the images are part of a gallery and should be displayed as such.
data-caption: This attribute is used to display the image caption in fancybox.

Note that you will also need to ensure that the fancybox library is included in your HTML document for this to work. You can add the following code to your HTML header to include the fancybox library:

php

Make sure that this code is included before the renderTemplate function is called.
User
that doesnt work on a backlight 5 kookaburra page which uses java script
ChatGPT

In that case, you can try the following steps to add Fancybox attributes to open large images in your Backlight 5 Kookaburra page:

Download the latest version of Fancybox from the official website.

Extract the contents of the downloaded file and copy the jquery.fancybox.min.css and jquery.fancybox.min.js files to your Backlight theme directory.

Edit the renderTemplate function in your phplugins.php file to add the necessary Fancybox attributes to the img tag. Here's an example of what the updated function might look like:

kotlin

const renderTemplate = photo => ( <figure itemscope itemtype="http://schema.org/ImageObject"> <a href="${photo.path}${photo.filename}" data-fancybox="gallery" data-caption="${photo.title}"> <img alt="${photo.title}" src="${photo.path}thumbnails/${photo.filename}" /> </a> </figure>);

Note that we have added the data-fancybox and data-caption attributes to the a tag. The data-fancybox attribute is used to group images into a gallery, while the data-caption attribute is used to display a caption for the image.

Update the scripts function to include the Fancybox JavaScript and initialize it. Here's an example of what the updated function might look like:

javascript

function scripts() {
// Your existing code here

echo <<<HTML
<script src="{$siteURL}backlight/modules/module-publisher/lib/js/api.js"></script>
<script src="{$siteURL}/path/to/jquery.fancybox.min.js"></script>
<link rel="stylesheet" href="{$siteURL}/path/to/jquery.fancybox.min.css" />

<script>
    // Your existing code here

    // Initialize Fancybox
    $("[data-fancybox]").fancybox({
        animationEffect: "zoom-in-out",
        transitionEffect: "slide",
    });
</script>

HTML;
return false;
}

Note that we have added the jquery.fancybox.min.css and jquery.fancybox.min.js files to the echo statement. We have also added a new script block to initialize Fancybox. In this example, we have set the animationEffect to “zoom-in-out” and the transitionEffect to “slide”, but you can adjust these options to suit your needs.

Save your changes and reload your Backlight page to see the updated gallery with Fancybox attributes.

:face_with_spiral_eyes: best regards,
Oliver

I would throw all of that in the trash. ChatGPT is a large language model, meaning that is sources it’s information from the Internet and whatever examples it has access to. Backlight remains obscure, and so I think ChatGPT simply will not know what to do with it.

If you look at the renderTemplate function, it’s pretty easy to understand. It’s HTML, and you can see that various values from the photo object are inserted into it.

Inspect the Kookaburra album grid, and you should find similar code, e.g. <figure ... /figure>, for each thumbnail image. If you copy that and fill in the renderTemplate function to use the same structure, class names, etc., then your thumbnails should pick up all of the same customizations as your standard grid, and would respond to your styling changes. You can also see how Fancybox is implemented on those thumbnails; it’s just data attributes, like data-fancybox data-src="..." data-srcset="..." and so on.

Console log the albums object, or individual photo objects from just about anywhere in the script to get eyes on the data you’re working with.

Dear Matt,
I tried ChatGPT on a whim and was just surprised by the communication and responses :flushed:.

I will of course continue to work according to your instructions and report back as soon as I have results.

Cheers, Oliver

Hi Matt,

Of course I can see what code is embedded between <figure... and figure>. Also when I look at the source code of an existing Kookaburrum album I see the attributes but I have no idea what to do with them.
I tried copying and pasting the <figure> code from grid__standard.php, that doesn’t work either.

I don’t get it and give up and wait for a more advanced Kookaburra version where hopefully all this can be done without great programming skills.

Thanks for your time and efforts.

Best regards,
Oliver

Okay, for the sake of explanation, I’ll walk you through it. Don’t just skip to the end, as understanding the whole thing will make it easier for you to make adjustments specific to your own site.

Here’s an image from one of my local Kookaburra galleries:

<figure itemscope itemtype="http://schema.org/ImageObject">
	<div>
		<a
			data-caption="20220430-163808-mc-1006"
			data-download-src="https://backlight.repo/galleries/kookaburra-set/kookaburra-album/photos2x/20220430-163808-mc-1006.jpg" 
			data-fancybox="album-479539"
			data-src="https://backlight.repo/galleries/kookaburra-set/kookaburra-album/photos/20220430-163808-mc-1006.jpg"
			data-srcset="https://backlight.repo/galleries/kookaburra-set/kookaburra-album/photos/20220430-163808-mc-1006.jpg 1x, https://backlight.repo/galleries/kookaburra-set/kookaburra-album/photos2x/20220430-163808-mc-1006.jpg 2x"
			data-thumb="https://backlight.repo/galleries/kookaburra-set/kookaburra-album/thumbnails/20220430-163808-mc-1006.jpg"
			data-width="1024"
			href="https://backlight.repo/galleries/kookaburra-set/kookaburra-album/20220430-163808-mc-1006-single"
		>
			<img
				alt="20220430-163808-mc-1006.jpg"
				class=""
				src="https://backlight.repo/galleries/kookaburra-set/kookaburra-album/thumbnails/20220430-163808-mc-1006.jpg"
				srcset="https://backlight.repo/galleries/kookaburra-set/kookaburra-album/thumbnails/20220430-163808-mc-1006.jpg 1x, https://backlight.repo/galleries/kookaburra-set/kookaburra-album/thumbnails2x/20220430-163808-mc-1006.jpg 2x"
				height="160"
				title="20220430-163808-mc-1006.jpg"
				width="256"
			>
		</a>
		<figcaption>
			<div class="photo-caption">
				<p>20220430-163808-mc-1006</p>
				<p>20220430-163808-mc-1006.jpg</p>
			</div>
		</figcaption>
	</div>
</figure>

If I rip out the values, that leaves me with a template in HTML:

<figure itemscope itemtype="http://schema.org/ImageObject">
	<div>
		<a
			data-caption=""
			data-download-src="" 
			data-fancybox=""
			data-src=""
			data-srcset=""
			data-thumb=""
			data-width=""
			href=""
		>
			<img
				alt=""
				class=""
				src=""
				srcset=""
				height=""
				title=""
				width=""
			>
		</a>
		<figcaption>
			<div class="photo-caption">
				<p></p>
				<p></p>
			</div>
		</figcaption>
	</div>
</figure>

So, what I need to do is to put that into the renderTemplate function, they hydrate with data from the photo object.

The easiest way to get started is to put the empty template into the function, then console log the photo object on the spot, like this:

const renderTemplate = photo => {
	console.log("Photo", photo);
	return (`
		<figure itemscope itemtype="http://schema.org/ImageObject">
			<div>
				<a
					data-caption=""
					data-download-src="" 
					data-fancybox=""
					data-src=""
					data-srcset=""
					data-thumb=""
					data-width=""
					href=""
				>
					<img
						alt=""
						class=""
						src=""
						srcset=""
						height=""
						title=""
						width=""
					>
				</a>
				<figcaption>
					<div class="photo-caption">
						<p></p>
						<p></p>
					</div>
				</figcaption>
			</div>
		</figure>
	`)
};

I can then see the object representing each photo in the browser console, and can cherry pick the values I need to fill in the template.

In the end, that leaves me with this:

				const renderTemplate = photo => (`
					<figure itemscope itemtype="http://schema.org/ImageObject">
						<div>
							<a
								data-caption="\${photo.title}"
								data-download-src="\${photo.path}photos2x/\${photo.filename}"
								data-fancybox="album-\${photo.album_id}"
								data-src="\${photo.path}photos/\${photo.filename}"
								data-srcset="\${photo.path}photos/\${photo.filename} 1x, \${photo.path}photos2x/\${photo.filename} 2x"
								data-thumb="\${photo.path}thumbnails/\${photo.filename}"
								data-width="\${photo.renditions.photos.width}"
								href="\${photo.url}"
							>
								<img
									alt="\${photo.filename}"
									class="lazyload"
									src="\${photo.path}thumbnails/\${photo.filename}"
									srcset="\${photo.path}thumbnails/\${photo.filename} 1x, \${photo.path}thumbnails2x/\${photo.filename} 2x"
									height="\${photo.renditions.thumbnails.height}"
									title="\${photo.filename}"
									width="\${photo.renditions.thumbnails.width}"
								>
							</a>
							<figcaption>
								<div class="photo-caption">
									<p>\${photo.title}</p>
									<p></p>
								</div>
							</figcaption>
						</div>
					</figure>
				`);

And the result on the page is a perfectly styled album, just like any other that Kookaburra would spit out.

Now, that’s based on the settings in my album template. If you’ve made changes in the designer, you may need to make some adjustments to accommodate for your particular configuration.

One other change we want to make.

Find this line in the PHP function from above:

view.classList.add("dynamic-album-view");

And change it to this:

view.dataset.columns = "4";
view.dataset.presentation = "grid";

And just to put a bow on things, for anyone following along or finding this later, the final outcome, in its entirety, is this:

		// Use the JavaScript JSON API to get album data;
		// logs the response to the browser's debug console.
		function scripts() {
			$siteURL = siteURL();
			$apiPath = "{$siteURL}backlight/" . (URLHelper::rewriteEnabled() ? "api" : "?m=publisher&c=jsonApi");
			$apiToken = AuthHelper::getJSONAPIToken();

			echo <<<HTML
			<script src="{$siteURL}backlight/modules/module-publisher/lib/js/api.js"></script>
			<script>
				// instantiate the api.
				const backlight = new Backlight("{$apiPath}");

				// create a placeholder for our images.
				const albums = {};

				// create an HTML template to be used for individual photos
				const renderTemplate = photo => (`
					<figure itemscope itemtype="http://schema.org/ImageObject">
						<div>
							<a
								data-caption="\${photo.title}"
								data-download-src="\${photo.path}photos2x/\${photo.filename}"
								data-fancybox="album-\${photo.album_id}"
								data-src="\${photo.path}photos/\${photo.filename}"
								data-srcset="\${photo.path}photos/\${photo.filename} 1x, \${photo.path}photos2x/\${photo.filename} 2x"
								data-thumb="\${photo.path}thumbnails/\${photo.filename}"
								data-width="\${photo.renditions.photos.width}"
								href="\${photo.url}"
							>
								<img
									alt="\${photo.filename}"
									class="lazyload"
									src="\${photo.path}thumbnails/\${photo.filename}"
									srcset="\${photo.path}thumbnails/\${photo.filename} 1x, \${photo.path}thumbnails2x/\${photo.filename} 2x"
									height="\${photo.renditions.thumbnails.height}"
									title="\${photo.filename}"
									width="\${photo.renditions.thumbnails.width}"
								>
							</a>
							<figcaption>
								<div class="photo-caption">
									<p>\${photo.title}</p>
									<p></p>
								</div>
							</figcaption>
						</div>
					</figure>
				`);

				// create a function to update the view.
				// * pumps photos into the template, above;
				// * appends the images to the page.
				const updateAlbumView = (listEl, albumId) => {
					listEl.nextSibling.remove();

					const view = document.createElement("div");
					view.dataset.columns = "4";
					view.dataset.presentation = "grid";

					let output = "";
					Object.values(albums[albumId]).forEach(photo => {
						output += renderTemplate(photo);
					})
					view.innerHTML = output;
					listEl.insertAdjacentElement("afterend", view);
				}

				// create a `get` function to fetch album data by albumId.
				const getAlbum = (listEl, albumId) => {
					if (albums[albumId]) {
						updateAlbumView(listEl, albumId);
					} else {
						backlight.getAlbum(albumId, {
							apiToken: "{$apiToken}",
							done: data => {
								const { id, photos } = data.album;
								albums[id] = {};
								photos.forEach(photo => {
									albums[id][photo.id] = photo;
								});

								updateAlbumView(listEl, id);
							},
						});
					}
				}

				// keeps the tab list up-to-date
				const setSelectedIdOnList = (listEl, albumId) => {
					listEl.dataset.selected = albumId;
					return albumId;
				}

				// on page load, get and set first album
				const listElements = document.querySelectorAll(".tabs");
				for (const listEl of listElements) {
					const firstAlbumId = listEl.querySelector("[data-album]").dataset.album;
					setSelectedIdOnList(listEl, firstAlbumId);
					getAlbum(listEl, firstAlbumId);
				}

				// listen for clicks;
				// if click event occurs within `tabs` and yields an album ID, fetch album data.
				document.addEventListener("click", (event) => {
					const listEl = event.target.closest("ul.tabs");

					if (!listEl) return; // not clicking a tab; eject!

					const selectedId = setSelectedIdOnList(
						listEl,
						event.target.dataset.album,
					);

					for (const tab of listEl.children) {
						if (tab.dataset.album === selectedId) {
							tab.classList.add("selected");
						} else {
							tab.classList.remove("selected");
						}
					}

					getAlbum(listEl, selectedId);
				});
			</script>
HTML; // do not indent this line
			return false;
		} // END /**/
1 Like

Oh, one last thing worth noting. This works only for Kookaburra albums, with images published to a Kookaburra template.

Renditions will be missing if:

  • using an albumId for a Pangolin album;
  • images were originally published to an Okapi or Pangolin template, and the album was switched to a Kookaburra template.

Dear Matt,

I would never have come up with this solution on my own, because I simply lack the necessary vocabulary and understanding, and I also didn’t know what to look for in the first place.Until
Up to the following I could still understand:

<figure itemscope itemtype="http://schema.org/ImageObject">
	<div>
		<a
			data-caption=""
			data-download-src="" 
			data-fancybox=""
			data-src=""
			data-srcset=""
			data-thumb=""
			data-width=""
			href=""
		>
			<img
				alt=""
				class=""
				src=""
				srcset=""
				height=""
				title=""
				width=""
			>
		</a>
		<figcaption>
			<div class="photo-caption">
				<p></p>
				<p></p>
			</div>
		</figcaption>
	</div>
</figure>

But that’s about it.

The reference to console.log is praiseworthy but even here I would not know how I should generate the values with the help of the browser console and where that can be found at all.

Therefore I am super grateful that you provide an explanation as well as the correct code so that I and hopefully others can continue to work on a website based solely on Kookaburra!

THANK YOU!
Oliver

So, anything that you console log, you can view in the browser. Just open the dev tools – on a Mac, Opt-Cmd-I, or right-click and choose “Inspect …”, or you can open them from the browser’s application menus – and whatever you’ve told it to put there, it will print.

So here, I have the photo object that I’m passing into renderTemplate, and if I tell the function to console.log(photo), then it will show me an object like this for each photo:

For example, you can see filename on the object, so I can access that value by using photo.filename in my code, and so on.

You can also write JavaScript directly into the console, and can access any function that’s loaded with the page. For example, you can log “Hello, world!” and it will then print that to the console.

Screenshot 2023-05-03 at 16.15.19

And you may notice that the console.log statement is removed from the final version of the code. You never want to leave this lying around. It’s nice while you’re in the midst of working, though, as you can use it to check data, confirm expected behavior, print errors, etc.

Hi Matt,

thanks again for your explanation.
I can understand now how console log works but it is not clear to me where to get which $ attributes cherry picked.

Still, you made my day, as I can continue working on my Kookaburra website :partying_face:.

Many greetings,
Oliver

backlight.getAlbum(albumId, {
	apiToken: "{$apiToken}",
	done: data => {
		const { photos } = data.album;
		photos.forEach(photo => {
			console.log(photo);
		});
	},
});

^^^ A modified, simplified API call. This is basically how I would start any project using the API.

If you look at the API call that gets the album data, you can see where photos come from, and you can log each individual photo. A console-logged photo looks like the picture above, where you can see the property names and their values for that photo.

Beginning in this way, I can confirm the data response, and by inspecting the photo objects, I can observe what data I have to work with for each photo.

Later on, I then send each photo to the renderTemplate function, e.g. renderTemplate(photo).

I hope that makes sense now.

Heads up, @volvoxturbo! The next release of Backlight will break the PHPlugins function we’ve created above.

Backlight 5.3.0 will introduce at least partial implementation of the Essay feature set for Kookaburra, and will conflict with the above code. Here’s an updated version that should work, unless I break things in the meantime. This actually simplifies the PHPlugins function, because Essay will do a lot of the setup, allowing us to piggyback on that work.

This will NOT work in version 5.2.x, so do wait for the update. Just bookmark this for later.

// Use the JavaScript JSON API to get album data;
// logs the response to the browser's debug console.
function scripts() {
	echo <<<HTML
	<script>
		// create a placeholder for our images.
		const myAlbums = {};

		// create a function to update the view.
		// * pumps photos into the template, above;
		// * appends the images to the page.
		const updateAlbumView = (listEl, albumId) => {
			listEl.nextSibling.remove();

			const view = document.createElement("div");
			view.dataset.columns = "4";
			view.dataset.presentation = "grid";

			let output = "";
			Object.values(myAlbums[albumId]).forEach(photo => {
				output += renderFigure(photo, `album-\${albumId}`);
			})
			view.innerHTML = output;
			listEl.insertAdjacentElement("afterend", view);
		}

		// create a `get` function to fetch album data by albumId.
		const getAlbum = (listEl, albumId) => {
			if (myAlbums[albumId]) {
				updateAlbumView(listEl, albumId);
			} else {
				backlight.getAlbum(albumId, {
					apiToken: "{$apiToken}",
					done: data => {
						const { id, photos } = data.album;
						myAlbums[id] = {};
						photos.forEach(photo => {
							myAlbums[id][photo.id] = photo;
						});

						updateAlbumView(listEl, id);
					},
				});
			}
		}

		// keeps the tab list up-to-date
		const setSelectedIdOnList = (listEl, albumId) => {
			listEl.dataset.selected = albumId;
			return albumId;
		}

		// on page load, get and set first album
		const listElements = document.querySelectorAll(".tabs");
		for (const listEl of listElements) {
			const firstAlbumId = listEl.querySelector("[data-album]").dataset.album;
			setSelectedIdOnList(listEl, firstAlbumId);
			getAlbum(listEl, firstAlbumId);
		}

		// listen for clicks;
		// if click event occurs within `tabs` and yields an album ID, fetch album data.
		document.addEventListener("click", (event) => {
			const listEl = event.target.closest("ul.tabs");

			if (!listEl) return; // not clicking a tab; eject!

			const selectedId = setSelectedIdOnList(
				listEl,
				event.target.dataset.album,
			);

			for (const tab of listEl.children) {
				if (tab.dataset.album === selectedId) {
					tab.classList.add("selected");
				} else {
					tab.classList.remove("selected");
				}
			}

			getAlbum(listEl, selectedId);
		});
	</script>
HTML; // do not indent this line
	return false;
} // END /**/
1 Like