Hitesh Methani
About Us
Applications
Fehler bei der Verarbeitung der Vorlage.
The following has evaluated to null or missing:
==> publisherCatalogId [in template "3192443#3192485#29283146" at line 202, column 182]
----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----
----
FTL stack trace ("~" means nesting-related):
- Failed at: #assign requestUrl = "/headless-comme... [in template "3192443#3192485#29283146" at line 202, column 1]
----
1<style type="text/css">
2.product-card {
3 border: solid 1px #E2E2E4;
4 border-radius: 10px;
5 box-sizing: border-box;
6 max-height:340px;
7 max-width: 430px;
8 min-width: 430px;
9 position: relative;
10 transition: all 0.3s cubic-bezier(.25,.8,.25,1);
11 cursor:point;
12}
13.product-card:hover{ border: solid 1px #89a7e0!important;}
14
15.product-card-paragraph {
16 -webkit-box-orient: vertical;
17 -webkit-line-clamp: 3;
18 display: -webkit-box;
19 font-size: 14px;
20 font-size: 14px;
21 font-weight: 400;
22 height: 60px;
23 letter-spacing: 0%;
24 line-height: 20px;
25 overflow: hidden;
26 text-overflow: ellipsis;
27 width: 340px;
28}
29
30.product-tag {
31 background-color: #E6EBF5;
32 box-sizing: border-box;
33 color: #2E5AAC;
34 font-family: Source Sans 3;
35 font-size: 13px;
36 font-weight: 400;
37 letter-spacing: 0%;
38 line-height: 16px;
39 max-height: 24px;
40 transition: all 0.3s cubic-bezier(.25, .8, .25, 1);
41 vertical-align: middle;
42}
43 .image-container .app-search-image {
44 object-fit: contain;
45 width:56px;
46 height:56px;
47 }
48 .app-search-image{
49 width:56px;
50 height:56px;
51 fit-content:contain;
52 -webkit-user-drag: none;
53 user-drag: none;
54 }
55
56.price {
57 font-family: Source Sans 3;
58 font-size: 16px;
59 font-weight: 600;
60 letter-spacing: 0%;
61 line-height: 24px;
62 vertical-align: middle;
63}
64
65.apps-container {
66 padding: 5px;
67 gap: 24px;
68
69}
70
71.tags-container {
72 gap: 8px;
73}
74
75.app-type-badge {
76 border-bottom-left-radius: 10px;
77 border-bottom-right-radius: 10px;
78 border-top-left-radius: 2px;
79 border-top-right-radius: 2px;
80 display: inline-block;
81 height: 20;
82 padding-left: 10px;
83 padding-right: 10px;
84 position: absolute;
85 right: 32px;
86 top: -6px;
87 width: 80;
88}
89
90.object-action {
91 background-color: #D1ECFA;
92 color: #166E9E;
93}
94
95 .no-type {
96 background: #cccccc;
97 color: #ffffff;
98 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
99}
100
101.theme {
102 background: #FBE0FF;
103 color: #720086;
104 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
105}
106
107.site-initializer {
108 background: #D1EEDC;
109 color: #0E7835;
110 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
111}
112
113.payment-methods {
114 background: #D2E6FF;
115 color: #2868FF;
116 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
117}
118
119.batch {
120 background: #FFE6C6;
121 color: #9D4C00;
122 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
123}
124 .fragments {
125 background: #DCD7E9;
126 color: #503690;
127 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
128}
129
130.workflow-action {
131 background: #DCD7E9;
132 color: #503690;
133 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
134}
135
136.checkout {
137 background: #DAF4C7;
138 color: #4E7135;
139 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
140}
141 .other {
142 background: #DAF4C7;
143 color: #4E7135;
144 transition: all 0.3s cubic-bezier(.25,.8,.25,1)
145}
146
147.pagination-bar {
148 align-items: center;
149 display: flex;
150 justify-content: space-between;
151 margin-top: 2rem;
152 width: 100%;
153}
154.pagination-items-per-page .dropdown-toggle, .pagination-results {
155 color: #6b6c7e;
156}
157</style>
158
159<#-- Bloco de inicialização de variáveis -->
160<#assign commerceContext=renderRequest.getAttribute("COMMERCE_CONTEXT") />
161<#assign url=themeDisplay.getURLCurrent()?string>
162<#assign basePageURL = url?keep_before("?")>
163<#assign queryString = "">
164<#assign idx = url?index_of("?")>
165<#if idx != -1>
166 <#assign queryString = url?substring(idx + 1)>
167</#if>
168
169<#-- Valores padrão para paginação -->
170<#assign delta = 8>
171<#assign start = 1>
172
173<#-- Leitura dos parâmetros da URL -->
174<#if queryString?has_content>
175 <#assign params = queryString?split("&")>
176 <#list params as param>
177 <#assign keyValue = param?split("=")>
178 <#if keyValue?size == 2>
179 <#if keyValue[0] == "delta">
180 <#assign delta = keyValue[1]?number>
181 </#if>
182 <#if keyValue[0] == "start">
183 <#assign start = keyValue[1]?number>
184 </#if>
185 </#if>
186 </#list>
187</#if>
188
189<#assign baseURL="https://marketplace.liferay.com" />
190
191
192<#assign channelId=commerceContext.getCommerceChannelId() />
193<#assign urlParts=url?split("/")>
194<#assign lastSegment=urlParts[urlParts?size - 1]?split(" \\?")[0]>
195<#assign publisherDetails=restClient.get("/c/publisherdetailses/" + lastSegment ) />
196
197<#if publisherDetails.catalogId?has_content>
198 <#assign publisherCatalogId=publisherDetails.catalogId />
199</#if>
200
201<#-- Construção da URL da API com os parâmetros de paginação -->
202<#assign requestUrl = "/headless-commerce-delivery-catalog/v1.0/channels/" + channelId + "/products?accountId=-1&nestedFields=categories,productSpecifications&filter=catalogId eq "+publisherCatalogId>
203<#assign requestUrl = requestUrl + "&pageSize=" + delta>
204<#assign requestUrl = requestUrl + "&page=" + start>
205
206<#assign catalogApps=restClient.get(requestUrl) />
207
208<#-- Verificação principal: só renderiza se houver conteúdo -->
209<#if catalogApps?has_content && catalogApps.items?has_content>
210 <#assign products = catalogApps.items />
211
212 <div class="d-flex flex-wrap apps-container flex-row mb-5" >
213 <#list products as productEntry>
214 <#if productEntry?has_content>
215
216 <#assign
217 accountEntryId=commerceContext.getAccountEntry().getAccountEntryId()
218 portalURL=portalUtil.getLayoutURL(themeDisplay)
219 productId=productEntry.id
220 productName=productEntry.name
221 remainingCategoriesText=[]
222 productImage=cpContentHelper.getDefaultImageFileURL(accountEntryId, productEntry.id)
223 catalogName=productEntry.catalogName
224 appFriendlyURLName=productEntry.slug
225 />
226 <#if productEntry.categories?has_content>
227 <#assign
228 productCategories=productEntry.categories?filter(productCategory -> productCategory.vocabulary?replace(" ", "-") == "marketplace-app-category")![]
229 categoriesListSize = productCategories?size-1
230 productTypes=productEntry.categories?filter(productCategory -> productCategory.vocabulary?replace(" ", "-") == "marketplace-category")![]
231 />
232
233 </#if>
234 <#if productTypes[0]?has_content>
235 <#assign productType=productTypes[0]/>
236 <#else>
237 <#assign productType=""/>
238 </#if>
239
240 <#if productEntry.productSpecifications?has_content>
241 <#assign productSpecifications=productEntry.productSpecifications![] />
242 </#if>
243 <#if productEntry.description?has_content>
244 <#assign productDescription=stringUtil.shorten(htmlUtil.stripHtml(productEntry.description!""), 150, "..." ) />
245 <#else>
246 <#assign productDescription="" />
247 </#if>
248 <a class="product-card p-5 bg-white border-radius-medium d-flex flex-column mb-0 text-dark text-decoration-none" href="${baseURL}/p/${appFriendlyURLName}">
249 <div class="align-items-center card-image-title-container d-flex">
250 <div class="image-container mr-2 rounded">
251 <img alt="${productName}" class="app-search-image" src="${productImage}" width="56" height="56" />
252 </div>
253 <div>
254 <span class="d-flex justify-content-end">
255 <div>
256 <#if productType?has_content >
257
258 <#if productType.name == 'Other'>
259 <div class="app-type-badge"></div>
260 <#else>
261 <div class="app-type-badge no-type font-weight-bold
262 <#if productType.name == 'Theme'> theme</#if>
263 <#if productType.name == 'Object action'> object-action</#if>
264 <#if productType.name == 'Site Initializer'> site-initializer</#if>
265 <#if productType.name == 'Payment methods'> payment-methods</#if>
266 <#if productType.name == 'Workflow action'> workflow-action</#if>
267 <#if productType.name == 'Batch'> batch</#if>
268 <#if productType.name == 'Checkout'> checkout</#if>
269 <#if productType.name == 'Fragments'> fragments</#if>
270 ">
271 ${productType.name}
272 </div>
273 </#if>
274
275 </#if>
276
277 </div>
278 </span>
279 <div class="font-weight-bold">
280 ${productName}
281 </div>
282
283 <#if productSpecifications?has_content>
284 <#assign productDeveloperName=productSpecifications?filter(item -> item.specificationKey == "developer-name") />
285 <#list productDeveloperName as developerNameItem>
286 <#if developerNameItem.value?has_content>
287 <#assign developerName=developerNameItem.value />
288 <#else>
289 <#assign developerName="" />
290 </#if>
291 <div class="mt-1 text-black-50">
292 ${developerName}
293 </div>
294 </#list>
295 </#if>
296 </div>
297 </div>
298 <div class="d-flex flex-column font-size-paragraph-small h-100 justify-content-between">
299 <div class="font-weight-normal my-6 text-break">
300 ${productDescription}
301 </div>
302 <div class="d-flex flex-column">
303 <#if productSpecifications?has_content>
304 <#assign productPriceModels=productSpecifications?filter(item -> item.specificationKey == "price-model") />
305 <#list productPriceModels as productPriceModel>
306 <#if productPriceModel.value?has_content>
307 <#assign priceModel=productPriceModel.value />
308 <#else>
309 <#assign priceModel="" />
310 </#if>
311 <div class="font-weight-semi-bold my-4 text-capitalize">
312 ${priceModel}
313 </div>
314 </#list>
315 </#if>
316 <#if productCategories?has_content>
317 <#assign
318 principalCategory=productCategories[0]
319 remainingCategories=productCategories?filter(category -> category.name != principalCategory.name)
320 />
321 <#list remainingCategories as category>
322 <#assign remainingCategoriesText=remainingCategoriesText + [category.name] />
323 </#list>
324 </#if>
325 <#if principalCategory?has_content>
326 <div>
327 <span class="product-tag px-2 py-1 rounded mr-3" title="${principalCategory.name}">
328 ${principalCategory.name}
329 </span>
330 <#if categoriesListSize?has_content && remainingCategoriesText?has_content>
331 <span class="product-tag px-2 py-1 rounded" title="${remainingCategoriesText?join('\n')}">
332 + ${categoriesListSize}
333 </span>
334 </#if>
335 </div>
336 </#if>
337 </div>
338 </div>
339 </a>
340 </#if>
341 </#list>
342 </div>
343
344 <#-- Pagination -->
345
346 <#assign currentPage = catalogApps.page />
347 <#assign pageSize = catalogApps.pageSize />
348 <#assign totalCount = catalogApps.totalCount />
349 <#assign totalPages = (totalCount / pageSize)?ceiling />
350
351
352
353 <div class="pagination-bar">
354 <div class="dropdown pagination-items-per-page">
355 <a aria-expanded="false" aria-haspopup="true" class="dropdown-toggle" data-toggle="dropdown" href="javascript:;" role="button">
356 ${pageSize} entries
357 <@clay["icon"] symbol="caret-bottom" />
358 </a>
359 <ul class="dropdown-menu dropdown-menu-top">
360 <#list [8, 16, 24, 48] as deltaOption>
361 <li><a class="dropdown-item" href="${basePageURL}?delta=${deltaOption}&start=1">${deltaOption}</a></li>
362 </#list>
363 </ul>
364 </div>
365
366
367 <#assign startItem = (currentPage - 1) * pageSize + 1 />
368
369 <#assign endItem = [(currentPage * pageSize), totalCount]?min />
370 <div class="pagination-results">Showing ${startItem} to ${endItem} of ${totalCount} entries.</div>
371
372 <ul class="pagination">
373
374 <li class="page-item<#if currentPage == 1> disabled</#if>">
375 <a class="page-link" href="<#if currentPage gt 1>${basePageURL}?delta=${pageSize}&start=${currentPage - 1}<#else>javascript:;</#if>" role="button">
376 <@clay["icon"] symbol="angle-left" />
377 <span class="sr-only">Back</span>
378 </a>
379 </li>
380
381
382 <#assign ellipsisPrinted = false />
383 <#list 1..totalPages as pageNumber>
384 <#assign showPage = false />
385
386 <#if (pageNumber == 1) ||
387 (pageNumber == totalPages) ||
388 (pageNumber >= currentPage - 2 && pageNumber <= currentPage + 2)>
389 <#assign showPage = true />
390 </#if>
391
392 <#if showPage>
393 <li class="page-item<#if pageNumber == currentPage> active</#if>">
394 <a class="page-link" href="${basePageURL}?delta=${pageSize}&start=${pageNumber}">${pageNumber}</a>
395 </li>
396 <#assign ellipsisPrinted = false />
397 <#else>
398 <#if !ellipsisPrinted>
399 <li class="page-item disabled">
400 <a class="page-link" href="javascript:;" tabindex="-1">...</a>
401 </li>
402 <#assign ellipsisPrinted = true />
403 </#if>
404 </#if>
405 </#list>
406
407
408 <li class="page-item<#if currentPage == totalPages> disabled</#if>">
409 <a class="page-link" href="<#if currentPage lt totalPages>${basePageURL}?delta=${pageSize}&start=${currentPage + 1}<#else>javascript:;</#if>" role="button">
410 <@clay["icon"] symbol="angle-right" />
411 <span class="sr-only">Next</span>
412 </a>
413 </li>
414 </ul>
415 </div>
416
417
418
419<#else>
420 <div class="alert alert-info">No apps found for this publisher.</div>
421</#if>
422
423
424<script>
425document.addEventListener("DOMContentLoaded", function () {
426 const paginationBar = document.querySelector('.pagination-bar');
427
428 if (!paginationBar) {
429 return;
430 }
431
432 function closePaginationDropdown() {
433 const openMenu = paginationBar.querySelector('.dropdown-menu.show');
434
435 if (openMenu) {
436 openMenu.classList.remove('show');
437
438 const dropdown = openMenu.closest('.dropdown');
439 if (dropdown) {
440 const toggle = dropdown.querySelector('.dropdown-toggle');
441 if (toggle) {
442 toggle.setAttribute('aria-expanded', 'false');
443 }
444 }
445 }
446 }
447
448 document.addEventListener('click', function (event) {
449
450 const toggle = event.target.closest('.pagination-items-per-page .dropdown-toggle');
451
452 if (toggle) {
453 event.preventDefault();
454
455 const menu = toggle.closest('.dropdown').querySelector('.dropdown-menu');
456
457 if (menu) {
458 const isCurrentlyShown = menu.classList.contains('show');
459
460 closePaginationDropdown();
461
462 if (!isCurrentlyShown) {
463 menu.classList.add('show');
464 toggle.setAttribute('aria-expanded', 'true');
465 }
466 }
467 return;
468 }
469
470 if (!event.target.closest('.pagination-items-per-page')) {
471 closePaginationDropdown();
472 }
473 });
474});
475</script>
WEBSITE