Example of question collection page

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>
AttributeDescription
data-projectidYour Rek.ai project ID
data-srekYour project secret key
data-entitytypeContent type to query. Use rekai-qna for Q&A content
data-nrofhitsNumber of results to return per page
data-paginationSet to true to include pagination info in the response
data-includetagsSet to true to include tag aggregations in the response
data-termFree-text search query. Updated by the search input
data-tagsActive tag filter. A single tag value (only one tag can be active at a time)
data-offsetPagination 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_hits is the number of results returned on the current page — this can be less than data-nrofhits on the last page. Always use data-nrofhits from 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.