Creating an image gallery with tag filtering and pagination in Power Pages
In my previous blogpost I showed how to create a tag filtering and pagination features in Power Pages using FetchXML, Liquid, JavaScript, CSS and HTML.
This is not officially a continuation of that blogpost but I wanted to leverage those features to create this time an image gallery in Power Pages and also adding a download feature every time the user clicks on an image.
The business case this time will focus on the product catalog for the sales module. So let’s imagine that we want to make available to our customers the product catalog but showing an image of each product, and every time the user clicks on an image it will be downloaded.
Without further introduction let’s go to it.
Product Catalog
The first step would be to configure the product catalog, in my scenario I’ve created a few products:
And on each product I’ve created a note with an image attached:
I’m pretty sure at this point you would be wondering why store the image as a note attachment and not in SharePoint or any other file storage.
I want to retrieve the images using FetchXML and liquid, that’s why I store the images in Dataverse as note attachments, and it should be noted that the storage occupied by these images is not the same as the storage occupied by the records in Dataverse.
But let’s imagine you decide to store the images in SharePoint or Blob storage, the only thing that will change is that instead of using FetchXML to retrieve the images you will have to make an http request to a cloud flow, Azure function or an API to retrieve the images.
For this time let’s continue with the FetchXML method.
Web Page
Using the Portal Management Model Driven App, go to Web Pages and click on the +New button.
This is how I filled out the form:
That would be it for now in the web page, we will be back in a moment to continue with it.
Web Template
The next step is to create a web template, it will contain the FetchXML to retrieve all the notes that are documents and it will also contain the HTML needed to create the image gallery with the pagination.
Here is the code of the web template:
{% assign org_url = ‘https://’ | append: website.adx_primarydomainname %}
{% assign pageurl = page.url %}
{% if request.params[‘page’] == null %}
{% assign page = 1 %}
{% else %}
{% assign page = request.params[‘page’] | integer %}
{% endif %}
{% assign nextPage = page | plus: 1 | string %}
{% assign prevPage = page | minus: 1 | string %}
{% fetchxml getimages %}
<fetch version=‘1.0’ output-format=‘xml-platform’ mapping=“logical” page=“{{ request.params[‘page’] | default:1 }}” count=“3” distinct=“true”>
<entity name=‘annotation’>
<attribute name=‘subject’ />
<attribute name=‘notetext’ />
<attribute name=‘filename’ />
<attribute name=‘annotationid’ />
<attribute name=‘filesize’ />
<attribute name=‘documentbody’ />
<order attribute=‘subject’ descending=‘false’ />
<filter type=‘and’>
<condition attribute=‘isdocument’ operator=‘eq’ value=‘1’ />
</filter>
<link-entity name=‘product’ from=‘productid’ to=‘objectid’ link-type=‘inner’ alias=‘ac’ />
</entity>
</fetch>
{% endfetchxml %}
<div class=“wrapper”>
<!– filter Items –>
<nav>
<div class=“items”>
<span class=“item active” data-name=“all”>All</span>
<span class=“item” data-name=“bag”>Bag</span>
<span class=“item” data-name=“shoe”>Shoe</span>
<span class=“item” data-name=“watch”>Watch</span>
<span class=“item” data-name=“camera”>Camera</span>
<span class=“item” data-name=“headphone”>Headphone</span>
</div>
</nav>
<!– filter Images –>
<div class=“gallery” style=“width: 100%;”>
{% for photo in getimages.results.entities %}
<div class=“image” data-name=“{{ photo.subject }}” onclick=“preview(this)”>
<span>
<a id=“imgdownload {{ photo.subject }}” href=“data:image/jpg;base64,{{ photo.documentbody }}” download=“{{ photo.subject }}.jpg”>
<img src=“data:image/jpg;base64,{{ photo.documentbody }}” alt=”>
</a>
</span>
<div>
<p style=‘color:white;’>{{ photo.subject }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
<!– Image preview box –>
<div class=“preview-box”>
<div class=“details”><span class=“title”>Image Category: <p></p></span><span class=“icon fas fa-times”></span></div>
<div class=“image-box”><img src=“” alt=“”></div>
</div>
<div class=“shadow”></div>
<!– Pagination –>
<br>
<div>
<nav role=“navigation”>
<ol class=“pagination”>
{% if page > 1 %}
<li>
<a href=“{{ org_url | append: pageurl | append: ‘?page=’ | append: prevPage }}”>
<span>«</span><span class=“visuallyhidden”>Prev</span>
</a>
</li>
{% else %}
<li class=“disabled”>
<span>»</span><span class=“visuallyhidden”>Prev</span>
</li>
{% endif %}
{% if getimages.results.more_records %}
<li>
<a href=“{{ org_url | append: pageurl | append: ‘?page=’ | append: nextPage }}”>
<span class=“visuallyhidden”>Next</span><span>»</span>
</a>
</li>
{% else %}
<li class=“disabled”>
<span class=“visuallyhidden”>Next</span><span>»</span>
</li>
{% endif %}
</ol>
</nav>
</div>
I will not explain the pagination part because that is part of the previous blogpost, but let me explain the HTML.
First we have a div element that will be the main container and just below another div with the “items” class that will be the container of the tag filters. Just below another div that will contain the foreach to iterate the result of FetchXML.
In each iteration a link <a> element is created that also contains an image element.
This trick of putting an image inside of a link element is to allow the user to download the image each time they click on it.
Then we have a div with the class class=”preview-box”, and the purpose of this is when the user clicks on an image it will be displayed in a larger size but as a modal
And the rest of the HTML is for the pagination feature.
Applying JavaScript and CSS to the Web Page Content
Now we have to go to the web page content and in the “Content HTML” column we have to add the call to the web template we’ve just created:
Then we go to the advance tab in which we will see two sections, one for JavaScript and the second one for the CSS:
Here is the code for the JavaScript:
//selecting all required elements
const filterItem = document.querySelector(“.items”);
const filterImg = document.querySelectorAll(“.gallery .image”);
window.onload = ()=>{ //after window loaded
filterItem.onclick = (selectedItem)=>{ //if user click on filterItem div
if(selectedItem.target.classList.contains(“item”)){ //if user selected item has .item class
filterItem.querySelector(“.active”).classList.remove(“active”); //remove the active class which is in first item
selectedItem.target.classList.add(“active”); //add that active class on user selected item
let filterName = selectedItem.target.getAttribute(“data-name”); //getting data-name value of user selected item and store in a filtername variable
filterImg.forEach((image) => {
let filterImges = image.getAttribute(“data-name”); //getting image data-name value
//if user selected item data-name value is equal to images data-name value
//or user selected item data-name value is equal to “all”
if((filterImges == filterName) || (filterName == “all”)){
image.classList.remove(“hide”); //first remove the hide class from the image
image.classList.add(“show”); //add show class in image
}else{
image.classList.add(“hide”); //add hide class in image
image.classList.remove(“show”); //remove show class from the image
}
});
}
}
for (let i = 0; i < filterImg.length; i++) {
filterImg[i].setAttribute(“onclick”, “preview(this)”); //adding onclick attribute in all available images
}
}
//image preview function
//selecting all required elements
const previewBox = document.querySelector(“.preview-box”),
categoryName = previewBox.querySelector(“.title p”),
previewImg = previewBox.querySelector(“img”),
closeIcon = previewBox.querySelector(“.icon”),
shadow = document.querySelector(“.shadow”);
function preview(element){
//once user click on any image then remove the scroll bar of the body, so user can’t scroll up or down
document.querySelector(“body”).style.overflow = “hidden”;
let selectedPrevImg = element.querySelector(“img”).src; //getting user clicked image source link and stored in a variable
let selectedImgCategory = element.getAttribute(“data-name”); //getting user clicked image data-name value
previewImg.src = selectedPrevImg; //passing the user clicked image source in preview image source
categoryName.textContent = selectedImgCategory; //passing user clicked data-name value in category name
previewBox.classList.add(“show”); //show the preview image box
shadow.classList.add(“show”); //show the light grey background
closeIcon.onclick = ()=>{ //if user click on close icon of preview box
previewBox.classList.remove(“show”); //hide the preview box
shadow.classList.remove(“show”); //hide the light grey background
document.querySelector(“body”).style.overflow = “auto”; //show the scroll bar on body
}
}
The above code will work for tag filtering and for previewing each image.
In a nutshell, what the JavaScript is doing for tag filtering is playing with CSS classes to hide or show articles based on the tag the customer clicked on.
And here is the code for the CSS part:
Table Permissions
Finally, don’t forget to grant read access to Note and Product tables to the specific web roles, otherwise the customer will not see anything on the website:
Testing
Here is the result:

This is a bit better CSS than the previous blogpost, but still requires a lot more care, so I’m sure you’ll do much better work than me to make a nicer UI.
Conclusion
As I said in the previous blogpost, the combination of Liquid, JavaScript, CSS and HTML is a great combo to create a tag filtering and pagination experience, but I also wanted to show that the same code (with some modifications) can be applied to solve different business cases while providing a better user experience at the same time.
As you can see I’m not the best at CSS, so I’m sure you will do it better than me.
It is also worth mentioning that if you store the images in SharePoint or in another file storage, you will need to call a cloud flow, Azure function or an API to retrieve the images instead of using FetchXML. Which brings us to the performance topic, we must take into account the number and size of images in order to choose the best way to retrieve the images.
I hope you find it useful for a Power Pages project you are working on, and I hope you enjoy reading this blog as much as I did creating it.

Hello there! Welcome to my blog!
My name is Wilmer Alcivar and I’m a Power Platform fan
I’d love to share my knowledge, so please do not hesitate to connect!
Categories
Recent posts

Cancel multiple running instances of a Cloud Flow using PowerShell

Import Excel file using Custom API and Alternate keys

Speed up your development process with GitHub Copilot

Dynamics 365 CE Release Wave 2 2023

Power Platform Release wave 2 2023

I am actually thankful to the holder of this website who has shared this impressive article at at this place.
Howdy! This article could not be written much better!
Going through this article reminds me of my previous roommate!
He constantly kept talking about this. I will send this article
to him. Fairly certain he’ll have a very good read.
Thanks for sharing!