Real-time filters enhance user experience by updating results instantly as users type or select options—without page reloads. This is perfect for searching and filtering in knowledge bases, product catalogs, or event listings within a Power Pages portal.
Use Case Example
You have a list of Knowledge Articles or Products, and users should be able to:
- Type a keyword in a search box
- Click on category/tag checkboxes
- Instantly see results filtered in real time
Step-by-Step Implementation
Step 1: Output Your Full List in HTML
Use Liquid to render all data up-front into the DOM (you’ll later filter with JavaScript).
<ul id="articleList">
{% fetchxml articles %}
<fetch>
<entity name="knowledgearticle">
<attribute name="title" />
<attribute name="description" />
<attribute name="article_category" />
</entity>
</fetch>
{% endfetchxml %}
{% for article in articles.results.entities %}
<li class="article-item" data-category="{{ article.article_category | downcase }}">
<h4 class="title">{{ article.title }}</h4>
<p class="description">{{ article.description }}</p>
</li>
{% endfor %}
</ul>
Step 2: Add Filter Controls
<input type="text" id="searchInput" placeholder="Search articles..." />
<label><input type="checkbox" class="categoryFilter" value="technical"> Technical</label>
<label><input type="checkbox" class="categoryFilter" value="billing"> Billing</label>
<label><input type="checkbox" class="categoryFilter" value="policy"> Policy</label>
Step 3: JavaScript for Real-Time Filtering
<script>
document.addEventListener("DOMContentLoaded", function () {
const searchInput = document.getElementById("searchInput");
const categoryFilters = document.querySelectorAll(".categoryFilter");
const articles = document.querySelectorAll(".article-item");
function filterArticles() {
const searchText = searchInput.value.toLowerCase();
const selectedCategories = Array.from(categoryFilters)
.filter(cb => cb.checked)
.map(cb => cb.value);
articles.forEach(article => {
const title = article.querySelector(".title").textContent.toLowerCase();
const description = article.querySelector(".description").textContent.toLowerCase();
const category = article.dataset.category;
const matchesText = title.includes(searchText) || description.includes(searchText);
const matchesCategory = selectedCategories.length === 0 || selectedCategories.includes(category);
if (matchesText && matchesCategory) {
article.style.display = "";
} else {
article.style.display = "none";
}
});
}
searchInput.addEventListener("input", filterArticles);
categoryFilters.forEach(cb => cb.addEventListener("change", filterArticles));
});
</script>
Step 4: Optional – Count of Results
You can add a dynamic count of visible results:
<p><strong id="resultCount"></strong> articles found</p>
<script>
function updateCount() {
const visible = document.querySelectorAll(".article-item:not([style*='display: none'])").length;
document.getElementById("resultCount").textContent = visible;
}
// Add this line in `filterArticles` after filtering:
updateCount();
</script>
Key Benefits
- Smooth user experience without page reloads
- Supports combined keyword + tag filtering
- Fully client-side — no API calls needed after page load
- Can easily be adapted to any type of list (products, events, blogs, etc.)
Considerations
- For large datasets, consider loading data via Web API or Dataverse Web Resources with pagination
- Don’t forget accessibility and mobile responsiveness
- Secure your data model in Table Permissions even if rendering on the frontend