Skip to content

Instantly share code, notes, and snippets.

@prince-neres
Created May 14, 2024 12:59
Show Gist options
  • Save prince-neres/df20c8bd5be017f246850522c1775df5 to your computer and use it in GitHub Desktop.
Save prince-neres/df20c8bd5be017f246850522c1775df5 to your computer and use it in GitHub Desktop.
Search contents fragment with headless API Liferay
{
"fieldSets": [
{
"fields": [
{
"name": "contentStructureId",
"label": "ID da estrutura do conteúdo",
"type": "text",
"dataType": "int",
"defaultValue": ""
},
{
"name": "vocabularyId",
"label": "ID do vocabulário de categorias há filtrar",
"type": "text",
"dataType": "int",
"defaultValue": ""
},
{
"name": "itemsPerPage",
"label": "Número de items por página",
"type": "select",
"dataType": "int",
"typeOptions": {
"validValues": [
{ "value": "1" },
{ "value": "2" },
{ "value": "3" },
{ "value": "4" },
{ "value": "5" },
{ "value": "6" },
{ "value": "7" },
{ "value": "8" },
{ "value": "9" },
{ "value": "10" }
]
},
"defaultValue": "3"
}
]
}
]
}
.contents-search-container {
background: #f1f2f5;
border: 1px solid #ccc;
}
.contents-search-container .contents-search {
gap: 1rem;
}
.contents-search-container .contents-categories-select,
.contents-search-container .contents-search-input {
width: 100% !important;
padding: 0.625rem !important;
background-color: #fff !important;
border-color: #ced4da !important;
border-style: solid !important;
border-width: 0.0625rem !important;
border-radius: 0.25rem !important;
box-sizing: border-box !important;
appearance: none !important;
-moz-appearance: none !important;
-webkit-appearance: none !important;
position: relative !important;
height: auto !important;
}
.contents-search-container .contents-categories-select {
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><path d='M6 8l4 4 4-4z'/></svg>") no-repeat right center;
}
.contents-search-container .search-icon {
position: absolute;
top: 50%;
right: 0.625rem;
transform: translateY(-50%);
width: 1.5rem;
height: 1.5rem;
background-image: url(/o/gestao-aluno/css/search-icon.svg);
background-repeat: no-repeat;
background-size: contain;
cursor: pointer;
}
.contents-search-container .contents-navigation {
gap: 1rem;
}
.contents-search-container .content-entry:not(:last-child) {
border-bottom: 1px solid #dddddd;
}
.contents-search-container a, .contents-search-container a:focus,
.contents-search-container a:hover {
color: #4a5058;
user-select: none;
}
.contents-search-container a:hover {
opacity: .8;
}
[#assign
vocabularyLocalService = serviceLocator.findService("com.liferay.asset.kernel.service.AssetVocabularyLocalService")
vocabularyId = configuration.vocabularyId
categories = []
/]
[#if vocabularyId?has_content && vocabularyId != 0 ]
[#assign
vocabulary = vocabularyLocalService.getAssetVocabulary(vocabularyId)
categories = vocabulary.getCategories()
/]
[/#if]
<div class="contents-search-container p-3">
<div class="contents-search d-flex">
<select
class="contents-categories-select"
>
<option>Todas Categorias</option>
[#list categories as category]
<option
id="${category.getCategoryId()}"
>
${category.getName()}
</option>
[/#list]
</select>
<div class="contents-search-input-wrapper position-relative inline-block w-100">
<input
class="contents-search-input"
placeholder="Buscar Serviços"
/>
<span class="search-icon"></span>
</div>
</div>
<div class="contents-results my-3">
</div>
<div class="contents-navigation d-flex align-items-center justify-content-center">
</div>
</div>
const CONTENT_STRUCTURE_ID = configuration.contentStructureId;
const API_URL_STRUCTURED_CONTENTS = `/o/headless-delivery/v1.0/content-structures/${CONTENT_STRUCTURE_ID}/structured-contents`;
const ITEMS_PER_PAGE = configuration.itemsPerPage;
const selectCategory = fragmentElement.querySelector(
".contents-categories-select"
);
const searchInput = fragmentElement.querySelector(".contents-search-input");
const contentsResultsElement =
fragmentElement.querySelector(".contents-results");
const contentsNavigation = fragmentElement.querySelector(
".contents-navigation"
);
const locale = themeDisplay.getBCP47LanguageId();
let contents = [];
let currPage = 0;
let totalPages = 0;
const fetchContents = async (page) => {
const categorySelected = Number(selectCategory.selectedOptions[0].id);
const categoryQuery = categorySelected
? `&filter=taxonomyCategoryIds%2Fany%28t%3At+eq+${categorySelected}%29`
: "";
const searchValue = searchInput.value;
const searchQuery = searchValue ? `&search=${searchValue}` : "";
const sortParam = "sort=title%3Aasc";
const pageSizeParam = `pageSize=${ITEMS_PER_PAGE}`;
const pageParam = `page=${page}`;
const url = `${API_URL_STRUCTURED_CONTENTS}?${sortParam}&${pageSizeParam}&${pageParam}&${categoryQuery}&${searchQuery}`;
try {
const response = await Liferay.Util.fetch(url, {
headers: { "Accept-Language": locale },
});
if (!response.ok) {
throw new Error("Failed to fetch contents");
}
const data = await response.json();
const { items } = data;
currPage = data.page;
totalPages = data.lastPage;
return items.map((content) => ({
title: content.title,
url: "w/" + content.friendlyUrlPath,
category: content.taxonomyCategoryBriefs[0]?.taxonomyCategoryName || "",
}));
} catch (error) {
console.error("Error fetching contents:", error);
return [];
}
};
const putContentsResults = () => {
contentsResultsElement.innerHTML = "";
contents.forEach((content) => {
const contentDiv = document.createElement("div");
contentDiv.classList.add("content-entry", "py-3");
const contentAnchor = document.createElement("a");
contentAnchor.classList.add("text-decoration-none", "font-weight-bold");
contentAnchor.innerText = content.title;
contentAnchor.setAttribute("href", content.url);
contentDiv.appendChild(contentAnchor);
contentsResultsElement.appendChild(contentDiv);
});
};
const createNavigation = () => {
contentsNavigation.innerHTML = "";
if (currPage != 1) createNavigationLink("&laquo;", currPage - 1, true);
if (currPage >= 3 || currPage === 2) {
createNavigationLink(1, 1, true);
if (currPage >= 3) {
createNavigationLink("...", 0, false);
}
}
createNavigationLink(currPage, currPage, false);
if (totalPages - currPage >= 2 || currPage === totalPages - 1) {
if (totalPages - currPage >= 2) createNavigationLink("...", 0, false);
createNavigationLink(totalPages, totalPages, true);
}
if (currPage != totalPages)
createNavigationLink("&raquo;", currPage + 1, true);
const navItems = fragmentElement.querySelectorAll("[data-page]");
navItems.forEach((navItem) => {
navItem.addEventListener("click", handleChange);
});
};
const createNavigationLink = (content, page, navigable) => {
const navLink = document.createElement("a");
navLink.innerHTML = content;
navLink.setAttribute("href", "javascript:void(0);");
navLink.classList.add("text-decoration-none");
if (navigable) navLink.setAttribute("data-page", page);
else navLink.classList.add("font-weight-bold");
contentsNavigation.appendChild(navLink);
};
const handleChange = async (event) => {
const page = event.target.dataset?.page || 1;
contents = await fetchContents(page);
putContentsResults();
createNavigation();
};
(async () => {
contents = await fetchContents(1);
putContentsResults();
createNavigation();
})();
searchInput.addEventListener("blur", handleChange);
searchInput.addEventListener("keyup", function (event) {
if (event.key === "Enter" || event.keyCode === 13) {
handleChange(event);
}
});
selectCategory.addEventListener("change", handleChange);
contentsResultsElement.style.height = ITEMS_PER_PAGE * 75 + "px";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment