Q&A Widget — Implementation Guide
This guide explains how to build an interactive Q&A page using the Rek.ai autocomplete add-on. The widget lets users search for questions, filter by category tags, and paginate through results — all driven by data attributes on a single HTML element.
How it works
The Rek.ai autocomplete script scans the DOM for any element with the class rek-prediction. It reads its data-* attributes to construct an API request, fetches matching Q&A results, renders them inside the element, and then calls your rekPredictionDone() callback with the full response object.
Everything — search, tag filtering, pagination — works by updating data-* attributes on the .rek-prediction element and then calling:
window.__rekai.checkAndCreatePredictions(window.__rekai.customer);This tells the Rek.ai script to re-read the attributes and fire a new request.
Required scripts
<!-- Rek.ai core — initialises window.__rekai -->
<script defer src="https://static.rekai.se/YOUR_SCRIPT_ID.js"></script>
<!-- Rek.ai autocomplete add-on — handles .rek-prediction elements -->
<script src="https://static.rekai.se/addon/rekai_autocomplete.min.js"></script>The .rek-prediction element
Place an empty div with the class rek-prediction wherever you want results to appear. Configure it with data-* attributes:
<div class="rek-prediction"
data-projectid="YOUR_PROJECT_ID"
data-srek="YOUR_SECRET_KEY"
data-entitytype="rekai-qna"
data-nrofhits="10"
data-pagination="true"
data-includetags="true"
data-term=""
data-tags=""
data-offset="0"
>
</div>| Attribute | Description |
|---|---|
data-projectid | Your Rek.ai project ID |
data-srek | Your project secret key |
data-entitytype | Content type to query. Use rekai-qna for Q&A content |
data-nrofhits | Number of results to return per page |
data-pagination | Set to true to include pagination info in the response |
data-includetags | Set to true to include tag aggregations in the response |
data-term | Free-text search query. Updated by the search input |
data-tags | Active tag filter. A single tag value (only one tag can be active at a time) |
data-offset | Pagination offset. Set to (page - 1) * data-nrofhits to change page |
The rekPredictionDone callback
Define this function globally before the Rek.ai scripts load. It is called automatically every time a response is received.
function rekPredictionDone(data, container) {
// data — the full API response object
// container — the .rek-prediction DOM element
}Response shape
{
"predictions": [
{
"id": "3k6e520ix9",
"question": "Vilken KTP-plan gäller för dig som jobbar hos en privat arbetsgivare?",
"answer": "Om du är född 1981 eller senare gäller KTP 1 för dig.",
"url": "https://example.com/path/to/page",
"pagetitle": "Tjänstepension för dig med privat arbetsgivare",
"tags": ["Försäkringar", "Lön", "Kollektivavtal"]
}
],
"pagination": {
"offset": 0,
"nr_of_hits": 10,
"total_hits": 1327
},
"tags": [
{ "tag": "Försäkringar", "count": 42 },
{ "tag": "Lön", "count": 38 }
]
}Note:
pagination.nr_of_hitsis the number of results returned on the current page — this can be less thandata-nrofhitson the last page. Always usedata-nrofhitsfrom the element when calculating total pages and offsets.
Features
Free-text search
Read the input value, reset the offset to page 1, write it to data-term, and trigger a new request. Wire this to both the button click and the Enter key:
function doSearch() {
const term = document.getElementById('search-input').value;
const rekPrediction = document.querySelector('.rek-prediction');
rekPrediction.setAttribute('data-term', term);
rekPrediction.setAttribute('data-offset', '0');
window.__rekai.checkAndCreatePredictions(window.__rekai.customer);
}
document.getElementById('search-button').addEventListener('click', doSearch);
document.getElementById('search-input').addEventListener('keydown', function(e) {
if (e.key === 'Enter') doSearch();
});Tag filtering
The response includes a tags array with each tag and its result count. Build these as buttons only on the first response — this preserves the selected state across searches and pagination without needing to restore it on every rebuild.
Only one tag can be active at a time. Selecting a tag deselects any previously active one. An "Alla kategorier" button is always shown first and clears the filter.
Always reset data-offset to 0 when changing the tag filter to avoid landing on an empty page.
// Inside rekPredictionDone — only run this block on the first response:
if (tagsContainer.children.length === 0) {
// "Alla kategorier" — clears the tag filter
const allBtn = document.createElement('button');
allBtn.textContent = 'Alla kategorier';
allBtn.classList.add('active');
allBtn.addEventListener('click', function() {
rekPrediction.setAttribute('data-tags', '');
rekPrediction.setAttribute('data-offset', '0');
tagsContainer.querySelectorAll('button').forEach(b => b.classList.remove('active'));
allBtn.classList.add('active');
window.__rekai.checkAndCreatePredictions(window.__rekai.customer);
});
tagsContainer.appendChild(allBtn);
data.tags.forEach(function(tagObj) {
const btn = document.createElement('button');
btn.textContent = tagObj.tag + ' (' + tagObj.count + ')';
btn.addEventListener('click', function() {
// Deselect all, then activate only this button
tagsContainer.querySelectorAll('button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
rekPrediction.setAttribute('data-tags', tagObj.tag);
rekPrediction.setAttribute('data-offset', '0');
window.__rekai.checkAndCreatePredictions(window.__rekai.customer);
});
tagsContainer.appendChild(btn);
});
}Pagination
Use data.pagination together with data-nrofhits from the element to calculate total pages and the current page. Do not use pagination.nr_of_hits for this — it reflects the actual results returned on the current page, which is less on the last page and will produce the wrong page count.
const { offset, total_hits } = data.pagination;
const pageSize = parseInt(rekPrediction.getAttribute('data-nrofhits'));
const totalPages = Math.ceil(total_hits / pageSize);
const currentPage = Math.floor(offset / pageSize) + 1;
function goToPage(page) {
rekPrediction.setAttribute('data-offset', (page - 1) * pageSize);
window.__rekai.checkAndCreatePredictions(window.__rekai.customer);
}For the page number list, always show page 1, the last page, and the current page ± 1, with ... for any gaps:
const pages = new Set(
[1, totalPages, currentPage - 1, currentPage, currentPage + 1]
.filter(p => p >= 1 && p <= totalPages)
);
const sorted = Array.from(pages).sort((a, b) => a - b);
sorted.forEach(function(page, i) {
if (i > 0 && page - sorted[i - 1] > 1) {
// render ellipsis
}
// render page button
});Results count
Update a text element with how many results are currently shown. Do this before any early returns in rekPredictionDone so it also updates when the result count is zero:
if (data.pagination) {
resultsText.textContent =
'Visar ' + data.pagination.nr_of_hits + ' frågor av ' + data.pagination.total_hits;
}Loading state
Show a loading indicator whenever a new request is triggered, and hide it when rekPredictionDone is called. This gives users immediate feedback while results are being fetched.
function showLoading() {
document.querySelector('.loading-indicator').classList.add('visible');
document.querySelector('.results-area').classList.add('hidden');
}
function hideLoading() {
document.querySelector('.loading-indicator').classList.remove('visible');
document.querySelector('.results-area').classList.remove('hidden');
}Call showLoading() before every checkAndCreatePredictions() call (search, tag buttons, pagination), and hideLoading() at the top of rekPredictionDone.