((document) => { // Attribute names const ATTRIBUTES = { VARIANT: 'data-variant', PARAMS: 'data-params', ENDPOINT: 'data-endpoint', IS_AUTHOR: 'data-is-author', IS_ENV_PROD: 'data-is-env-prod', LOCALE: 'data-locale', SOURCE: 'data-source', RESULTS_TEXT: 'data-results-text' } // Constants const MAX_CARDS = 9; // cards per page const NUM_OF_COLS = 3; // num of cols in card grid /** Mock Search API Functions **/ // Mock function that should be returned by the search API that will // return the MAX_CARDS items that we care about. function getItemsForPage(data, pageNumber) { const cards = data[0].cards; const startIndex = (pageNumber - 1) * MAX_CARDS; const endIndex = startIndex + MAX_CARDS; return [{ cards: cards.slice(startIndex, endIndex), numOfResults: cards.length }]; } // Set mapping function template selector const TEMPLATE_SELECTOR = { HEADER_HEADING: '.card-grid__heading .card-grid__heading-text', CARDS: '.card-grid__cards', LAYOUT: '.card-grid__cards .layout', LAYOUT_COL: '.card-grid__cards .layout__col', CARD_LABEL: '.block-feature__label', CARD_TITLE: '.block-feature__title', CARD_PARAGRAPH: '.block-feature__paragraph', CARD_MEDIA: '.media__asset', CARD_ACTION: '.card__content .link', LOAD_MORE_CONTAINER: '.dynamic-content__load-more', LOAD_MORE_BTN: '.dynamic-content__load-more .btn' }; /** * Builds an API URL based on the filter selections array. * @param {Array} filterSelections - Array of filters used to construct the query parameters in the URL. * @param {string} locale - Locale segment of URL. * @param {string} params - String of extraneous parameters that shouldb e passed into the API call. * @returns {string} - Constructed endpoint for the API call. */ function buildApiUrl(filterSelections, locale, params, requestUri) { let baseParams = `locale=${locale}`; // If there are added params, add them to the endpoint if (params && params !== '') { baseParams += params; } if (filterSelections.length === 0) { return `${requestUri}?onload=true&${baseParams}`; } const filters = filterSelections.join(','); return `${requestUri}?for=${filters}&${baseParams}`; } /** Search API Functions **/ /** * Filters an array of card objects based on the selected filters. * @param {Array} data - Array of objects to be filtered * @param {Array} filterSelections - Each object must include all these IDs in its 'parents' array. * @returns {Array} - Filtered array of objects. */ function filterCardsByParent(data, filterSelections) { return data.filter((obj) => { const parentIds = obj.parents.map((parent) => parent.id); return filterSelections.every((selection) => parentIds.includes(selection)); }); } /** * Fetch data from the API, update the number of stories, and process the cards. * * @param {string} apiUrl - The URL to fetch data from. * @param {boolean} isEnvProd - Boolean value indicating whether or not this is prod environment. * @param {boolean} isAuthor - Boolean value indicating whether or not this is authoring environment. * @param {function} processCardsCallback - A callback function to process the fetched cards. */ async function fetchAndProcessCards(apiUrl, isEnvProd, isAuthor, processCardsCallback) { ajaxFilterCardGrid = new AjaxUtil({ isProd: isEnvProd === 'true', isAuthor: isAuthor === 'true' }); // Fetch, Parse, and extract card array from the API data ajaxFilterCardGrid.invokeRequest({ url: apiUrl, method: 'GET' }).then(response => response.json()) .then(data => { const { card } = data; // Call the callback function to process the fetched cards processCardsCallback(card); }).catch(err => { console.log(err); }); } // Get the data from the API and update the Filter search results component on content loaded. document.addEventListener('DOMContentLoaded', () => { // Handles all instances of filter card carousel on the page ocrReimagine.SolutionCenter.getInstances().filter((instance) => instance?.el?.classList?.contains('search-results')).forEach((searchResultsInstance) => { // Filter search results component data (from model). const IS_AUTHOR = searchResultsInstance.el.getAttribute(ATTRIBUTES.IS_AUTHOR); const IS_ENV_PROD = searchResultsInstance.el.getAttribute(ATTRIBUTES.IS_ENV_PROD); const PARAMS = searchResultsInstance.el.getAttribute(ATTRIBUTES.PARAMS); const VARIANT = searchResultsInstance.el.getAttribute(ATTRIBUTES.VARIANT); const LOCALE = searchResultsInstance.el.getAttribute(ATTRIBUTES.LOCALE); const SOURCE = searchResultsInstance.el.getAttribute(ATTRIBUTES.SOURCE); const RESULTS_TEXT = searchResultsInstance.el.getAttribute(ATTRIBUTES.RESULTS_TEXT); const REQUEST_URI = "/msonecloudapi/" + SOURCE + "/cards"; // Dynamically Added Filters const dynamicallyAddedFiltersInstance = searchResultsInstance.getDynamicallyAddedFiltersInstance(); paginationInstance = dynamicallyAddedFiltersInstance.paginationInstance; // Single Select const isSingleSelect = (VARIANT === 'single-select'); // Hide the description and load more container searchResultsInstance.dynamicContent.hideDescriptionContainer(); searchResultsInstance.dynamicContent.hideLoadMoreContainer(); /** Functions **/ function update(data) { searchResultsInstance.update({ filteredData: data, pageType: null, // No page type, setting to null showAllCards: false, pageMaxItems: MAX_CARDS, filteredDataLimit: 0, // show all card data mappingObj: { pageType: 'category', onLoad: false, currentPaginationPage: paginationInstance.activeNumber }, mappingFunc: searchResultsMappingFunction, templateSelector: TEMPLATE_SELECTOR, dynamicContentPostTextOpt1: RESULTS_TEXT }); searchResultsInstance.dynamicContent.renderWithDataSubset({ iterableData: false }); } // Initial load if (isSingleSelect) { // Single Select variant fetchAndProcessCards(buildApiUrl([], LOCALE, PARAMS, REQUEST_URI), IS_ENV_PROD, IS_AUTHOR, (cards) => { // Pagination and number of results data model currentData = [{ numOfResults: cards.length, cards: cards }] // Update the card display with the fetched data update(cards); dynamicallyAddedFiltersInstance.updatePaginationAndResults(cards.length); }); } // If dynamicallyAddedFilterInstance is not null, then bind click events for the pill bar items if (dynamicallyAddedFiltersInstance) { // Clicking on a pill bar item removes a filter // Unchecking/checking a checkbox also all removes/add filters dynamicallyAddedFiltersInstance.el.addEventListener('onDynamicallyAddedFilterIsFiltered', function (e) { if (e.detail) { // This is where you would update the api string based on filters // e.detail is the filterSelection in filters if (isSingleSelect) { // Build the new API URL based on the filter selections const updatedApiUrl = buildApiUrl(e.detail, LOCALE, PARAMS, REQUEST_URI); // Fetch data with new endpoint fetchAndProcessCards(updatedApiUrl, IS_ENV_PROD, IS_AUTHOR, (cards) => { // Filter the fetched cards based on filterSelections const filteredCards = filterCardsByParent(cards, e.detail); currentData = [{ numOfResults: filteredCards.length, cards: filteredCards }] // Update the UI with the filtered data update(filteredCards); dynamicallyAddedFiltersInstance.updatePaginationAndResults(filteredCards.length); }); } } }) // Relevance Dropdown Filter Event Listener dynamicallyAddedFiltersInstance.el.addEventListener('onRelevanceChanged', function (e) { if (e.detail) { if (paginationInstance) { // Reset pagination to 1 dynamicallyAddedFiltersInstance.updatePaginationAndResults(currentData[0].numOfResults); } // When published-date is added or removed, we will reverse the order of the current data currentData = [{ numOfResults: currentData[0].numOfResults, cards: currentData[0].cards.slice().reverse() }] update(currentData); } }) } // Clicking a pagination page updates the data returned if (paginationInstance) { dynamicallyAddedFiltersInstance.el.addEventListener('paginationActivePageChanged', function (e) { if (e.detail) { // simulate search api returning spliced data let activePage = e.detail.activePage; const splicedData = getItemsForPage(currentData, activePage); update(splicedData); } }) } }) }); // MAPPING FUNCTION function searchResultsMappingFunction(obj) { if (!obj.data || (obj.data && obj.data.length === 0)) { return; } const { htmlTemplateClone, singleItem } = obj; let { cards } = singleItem; // cards is not available in single item for Single Select variant if (!cards) { cards = obj.data; } const setCard = (cardData, htmlTemplateClone) => { const { label, title, image, paragraph } = cardData.content; const { text, href, isOpenNewTab, ariaLabel, attributes } = cardData.content.action; const { alt, src } = image; const layoutCol = htmlTemplateClone .querySelectorAll(TEMPLATE_SELECTOR.LAYOUT_COL)[0] .cloneNode(true); const card = layoutCol.querySelector('.card'); const media = card.querySelector(TEMPLATE_SELECTOR.CARD_MEDIA); const action = card.querySelector(TEMPLATE_SELECTOR.CARD_ACTION); this.setHTML(layoutCol, TEMPLATE_SELECTOR.CARD_LABEL, label ?? ''); this.setHTML(layoutCol, TEMPLATE_SELECTOR.CARD_TITLE, title); this.setHTML(layoutCol, TEMPLATE_SELECTOR.CARD_PARAGRAPH, paragraph ?? ''); // remove existing picture element if (card.querySelector('picture')) { card.querySelector('picture').remove(); } // set image element if (media) { const imgNode = document.createElement('img'); imgNode.src = src ?? DefaultImage.src; imgNode.alt = alt ?? "Alt text"; media.append(imgNode); } // set link if (cardData.content.action) { action.href = href; action.setAttribute('aria-label', ariaLabel); action.querySelector('.link__text').innerHTML = text; if (isOpenNewTab) { action.setAttribute('target', '_blank'); } // set link attributes for (const property in attributes) { if (Object.hasOwn(attributes, property)) { action.setAttribute(property, attributes[property]); } } } htmlTemplateClone.querySelector(TEMPLATE_SELECTOR.LAYOUT).append(layoutCol); }; let i = 0; for (const cardData of cards) { i++; if (i > MAX_CARDS) { break; } setCard(cardData, htmlTemplateClone); } htmlTemplateClone.querySelectorAll(TEMPLATE_SELECTOR.LAYOUT_COL)[0].remove(); const layoutContainer = htmlTemplateClone.querySelector(TEMPLATE_SELECTOR.LAYOUT); // Remove the existing class that starts with 'layout--cols-' layoutContainer.classList.remove('layout--cols-'); // Add the new class based on the numOfCols value layoutContainer.classList.add(`layout--cols-${NUM_OF_COLS}`); } })(document);