Manuals Policies Guidelines
Manuals, Processes and Guidelines
An error occurred while processing the template.
The following has evaluated to null or missing: ==> types[0] [in template "20157#20197#1588857" at line 70, column 40] ---- Tip: It's the final [] step that caused this error, not those before it. ---- 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 defaultType = types[0] [in template "20157#20197#1588857" at line 70, column 17] ----
1<#assign dlFileEntryUtil=serviceLocator.findService("com.liferay.document.library.kernel.service.DLFileEntryLocalService")>
2<#assign dlFileEntryClass="com.liferay.document.library.kernel.model.DLFileEntry">
3<#assign portletID="${themeDisplay.getPortletDisplay().getId()}">
4<#assign customTagID="ap_">
5<#assign currentUrl = themeDisplay.getURLCurrent()!"">
6<#if currentUrl?matches(".*[?&]type=([^&]+).*")>
7 <#assign urlType = "ap_" + currentUrl?matches(".*[?&]type=([^&]+).*")?groups[1]!""/>
8<#else>
9 <#assign urlType = "ap_"/>
10</#if>
11
12<!--Type of Document-->
13<div class="container no-padding">
14 <div class="row">
15 <div class="col-md-12" style="padding: 0px;">
16 <#assign types=[]>
17 <#assign categories={}>
18 <#assign categoriesByType={}>
19
20 <#list entries as curEntry>
21 <#if curEntry.getClassName()==dlFileEntryClass>
22 <#assign dlFileEntry=dlFileEntryUtil.getFileEntry(curEntry.getClassPK())>
23 <#assign entryTypes=curEntry.getTags()>
24 <#assign entryCategories=curEntry.getCategories()>
25
26 <!-- Type -->
27 <#list entryTypes as type>
28 <#list type.name?split(",") as typeName>
29 <#if typeName?matches("^" + customTagID + ".*")>
30 <#assign typeTag=typeName?trim>
31 <#if !types?seq_contains(typeTag)>
32 <#assign types=(types + [typeTag])>
33 </#if>
34 </#if>
35 </#list>
36 </#list>
37
38 <!-- Category -->
39 <#list entryCategories as category>
40 <#list category.name?split(",") as categoryName>
41 <#assign categoryTag=categoryName?trim>
42 <#if !categoryTag?matches("^LH.*")>
43 <#if categoriesByType[typeTag]?? && !categoriesByType[typeTag]?seq_contains(categoryTag)>
44 <#assign categoriesByType = categoriesByType + {typeTag: categoriesByType[typeTag] + [categoryTag]} />
45 <#elseif !(categoriesByType[typeTag]??)>
46 <#assign categoriesByType = categoriesByType + {typeTag: [categoryTag]} />
47 </#if>
48 <#if (category.description?split(",")[0]?trim?length gt 0)>
49 <#assign categoryDescription=category.description?split(",")[0]?trim?substring(132, category.description?length - 28)?trim>
50 <#else>
51 <#assign categoryDescription=categoryTag>
52 </#if>
53 <#assign categories = categories + {categoryTag: categoryDescription} />
54 </#if>
55 </#list>
56 </#list>
57 </#if>
58 </#list>
59
60 <!-- Sort Categories -->
61 <#assign sortedCategoriesByType = {}>
62 <#list categoriesByType?keys?sort as typeTag>
63 <#assign sortedCategories = categoriesByType[typeTag]?sort>
64 <#assign sortedCategoriesByType = sortedCategoriesByType + {typeTag: sortedCategories} />
65 </#list>
66 <#assign categoriesByType = sortedCategoriesByType>
67
68 <!-- Default Type and Category -->
69 <#if urlType == 'ap_' || !types?seq_contains(urlType)>
70 <#assign defaultType = types[0]>
71 <#else>
72 <#assign defaultType = urlType>
73 </#if>
74 <#assign defaultCategory=categoriesByType[defaultType]?first>
75
76 <!-- Type of Documents Filters -->
77 <div class="type-buttons">
78 <#list types as type>
79 <#assign typeTitle = type?replace(customTagID, "")?replace("_", " ")?split(" ")?map(x -> (x == 'and')?string("and", x?cap_first))?join(" ")>
80 <#if type == defaultType>
81 <button class="type-btn active" id="typeBtn_${type}" data-type="${type}">
82 ${typeTitle}
83 </button>
84 <#else>
85 <button class="type-btn" id="typeBtn_${type}" data-type="${type}">
86 ${typeTitle}
87 </button>
88 </#if>
89 </#list>
90 </div>
91 </div>
92 </div>
93</div>
94
95<!-- Type of Document Title -->
96<div class="container">
97 <div class="row">
98 <a class="col-md-12 main-title type-title" id="type-title">${defaultType?replace(customTagID, "")?replace("_", " ")?split(" ")?map(x -> (x == 'and')?string("and", x?cap_first))?join(" ")}
99 </a>
100 </div>
101</div>
102
103<!-- Category of Document || Documents -->
104<div class="container">
105 <div class="row">
106 <div class="filters-title">
107 Filters
108 </div>
109 <!-- Category of Documents Filters -->
110 <div class="col-md-3 category-filters" id="categories_${portletID}">
111 <#list categoriesByType[defaultType] as category>
112 <#assign categoryTitle = categories[category]>
113 <label class="category-label custom-checkbox">
114 <#if category == defaultCategory>
115 <input class="category-btn" type="checkbox" value="${category}" checked>
116 <#else>
117 <input class="category-btn" type="checkbox" value="${category}">
118 </#if>
119 <span class="custom-checkmark"></span>
120 <a class="category-text">${categoryTitle}</a>
121 </label>
122 </#list>
123 </div>
124 <!-- Documents -->
125 <div class="col-md-7 multiple-documents-files" style="padding-bottom: 30px">
126 <div class="tiles-container">
127 <#if entries?has_content>
128 <#list entries as curEntry>
129 <#if curEntry.getClassName()==dlFileEntryClass>
130 <#assign dlFileEntry=dlFileEntryUtil.getFileEntry(curEntry.getClassPK())>
131 <#assign entryTypes=curEntry.getTags()>
132 <#assign entryCategories=curEntry.getCategories()>
133 <#assign classTypes="">
134 <#assign classCategories="">
135 <#list entryTypes as type>
136 <#list type.name?split(",") as typeName>
137 <#assign classTypes=classTypes + " " + typeName?trim>
138 </#list>
139 </#list>
140 <#list entryCategories as category>
141 <#list category.name?split(",") as categoryName>
142 <#assign classCategories=classCategories + " " + categoryName?trim>
143 </#list>
144 </#list>
145 <div class="multiple-documents-tile small-box-shadow-containers ${portletID} ${classTypes} ${classCategories}">
146 <div class="multiple-documents-tile-text">
147 <#if (curEntry.getDescription(locale)?split(",")[0]?trim?length gt 0)>
148 ${curEntry.getDescription(locale)}
149 <#else>
150 ${curEntry.getTitle(locale)}
151 </#if>
152 </div>
153 <div class="multiple-documents-tile-download">
154 <a href="${curEntry.getAssetRenderer().getURLDownload(themeDisplay)}" target="_blank">
155 Download
156 </a>
157 </div>
158 </div>
159 </#if>
160 </#list>
161 </#if>
162 <a class="anchor" id="${themeDisplay.getPortletDisplay().title}" name="${themeDisplay.getPortletDisplay().title}"></a>
163 </div>
164 <!--PAGINATION-->
165 <div class="asset-pagination">
166 <div class="asset-pagination-showing">
167 Showing <span id="startResults_${portletID}">1</span> to <span id="endResults_${portletID}">10</span> of <span id="totalResults_${portletID}">25</span> results
168 </div>
169 <div class="asset-pagination-btns">
170 <button class="asset-pagination-btn" id="prevBtn_${portletID}"> <i class="fa-solid fa-arrow-left"></i> </button>
171 <button class="asset-pagination-btn active" id="firstBtn_${portletID}">1</button>
172 <button class="asset-pagination-btn" id="secondBtn_${portletID}">2</button>
173 <button class="asset-pagination-btn" id="thirdBtn_${portletID}">3</button>
174 <button class="asset-pagination-btn" id="fourthBtn_${portletID}">4</button>
175 <button class="asset-pagination-btn" id="nextBtn_${portletID}"><i class="fa-solid fa-arrow-right"></i></button>
176 </div>
177 </div>
178 </div>
179 </div>
180</div>
181
182
183<script>
184(function() {
185 var titleID = "${portletID}";
186 var currentType = "${defaultType}";
187 var currentCategory = "${defaultCategory}";
188 var itemsPerPage = isMobileDevice() ? 3 : 10;
189 var currentPage = 0;
190 var nextPage = 0;
191 var taggedRows = [];
192 var startPagination = 1;
193 var rows = [];
194 var totalTaggedRows = 0;
195 var totalPages = 0;
196 var nextStart = 0;
197 var nextEnd = 0;
198 var allCategoriesByType = {
199 <#list categoriesByType as type, typeCategories>
200 "${type}": [<#list typeCategories as category>"${category}"<#if category_has_next>,</#if></#list>]
201 <#if type_has_next>,</#if>
202 </#list>
203 };
204 var allCategories = {
205 <#list categories as categoryName, categoryDescription>
206 "${categoryName}": "${categoryDescription}"
207 <#if categoryName_has_next>,</#if>
208 </#list>
209 };
210
211 function isMobileDevice() {
212 const userAgent = navigator.userAgent || navigator.vendor || window.opera;
213
214 if (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent) ||
215 ('ontouchstart' in window && (window.innerWidth <= 800 || window.innerHeight <= 600))) {
216 return true;
217 }
218
219 return false;
220 }
221
222 // Update Type
223 function updateType(event) {
224 var selectedType = event.target.getAttribute('data-type');
225 currentType = selectedType;
226
227 document.querySelectorAll('.type-btn').forEach(function(button) {
228 button.classList.remove('active');
229 });
230 event.target.classList.add('active');
231
232 var currentTypeTitle = currentType.replace("ap_", "").replace(/_/g, " ").split(" ") .map(x => x.toLowerCase() === "and" ? "and" : x.charAt(0).toUpperCase() + x.slice(1)).join(" ");
233 document.getElementById("type-title").text = currentTypeTitle;
234
235 updateCategories(currentType);
236 }
237
238 // Update Category based on type
239 function updateCategories(type) {
240 var categoryContainer = document.getElementById(`categories_${portletID}`);
241 categoryContainer.innerHTML = '';
242
243 var categories = allCategoriesByType[type] || [];
244 categories.forEach(function(category) {
245 var label = document.createElement("label");
246 label.classList.add("category-label", "custom-checkbox");
247
248 var checkbox = document.createElement("input");
249 checkbox.type = 'checkbox';
250 checkbox.name = 'category';
251 checkbox.value = category;
252 checkbox.classList.add("category-btn");
253
254 if (category === categories[0]) {
255 checkbox.checked = true;
256 currentCategory = category;
257 }
258
259 var customCheckmark = document.createElement("span");
260 customCheckmark.classList.add("custom-checkmark");
261
262 var categoryTitle = allCategories[category];
263 var categoryText = document.createElement("a");
264 categoryText.classList.add("category-text");
265 categoryText.textContent = categoryTitle;
266
267 label.appendChild(checkbox);
268 label.appendChild(customCheckmark);
269 label.appendChild(categoryText);
270
271 categoryContainer.appendChild(label);
272 });
273
274 document.querySelectorAll('.category-btn').forEach(function(checkbox) {
275 checkbox.addEventListener('click', updateCategory);
276 });
277
278 updateRows(currentCategory);
279 }
280
281 function updateCategory(event) {
282 var selectedCategory = event.target.getAttribute('value');
283 currentCategory = selectedCategory;
284
285 document.querySelectorAll('.category-btn').forEach(function(button) {
286 button.checked = false;
287 });
288 event.target.checked = true;
289 updateRows(currentCategory);
290 }
291
292
293 // Update Rows based on Type and Category
294 function updateRows(category) {
295 currentCategory = category;
296
297 rows = document.querySelectorAll(".multiple-documents-tile." + titleID);
298 taggedRows = [];
299 rows.forEach(function(row) {
300 var tags = Array.from(row.classList);
301 var categories = Array.from(row.classList);
302
303 // Check if row matches selected type and category
304 if (tags.includes(currentType) && categories.includes(currentCategory)) {
305 taggedRows.push(row);
306 }
307
308 // Hide all -> pagination will show
309 if (!row.classList.contains("assetHidden")) {
310 row.classList.add("assetHidden");
311 }
312 });
313 handlePaginationChange("0");
314 }
315
316 //EVENT LISTENERS //
317 // On load
318 function initialHandle() {
319 updateCategories("${defaultType}");
320 }
321 window.onload = initialHandle();
322 // Type
323 document.querySelectorAll('.type-btn').forEach(function(button) {
324 button.addEventListener('click', updateType);
325 });
326 // Category
327 document.addEventListener('DOMContentLoaded', function() {
328 document.querySelectorAll('.category-btn').forEach(function(checkbox) {
329 checkbox.addEventListener('click', updateCategory);
330 });
331 });
332 //
333
334 //Pagination
335 function handlePaginationChange(page) {
336 totalTaggedRows = taggedRows.length;
337 totalPages = Math.ceil((totalTaggedRows / itemsPerPage));
338 nextPage = 0;
339 // defines what is the next page
340 if (page === 'prev') {
341 nextPage = currentPage - 1 < 0 ? currentPage : currentPage - 1;
342 if (currentPage == nextPage) return;
343 } else if (page === 'next') {
344 nextPage = currentPage + 1 < totalPages ? currentPage + 1 : currentPage;
345 if (currentPage == nextPage) return;
346 } else {
347 nextPage = parseInt(page, 10) + (startPagination - 1);
348 }
349 nextStart = (nextPage) * itemsPerPage;
350 nextEnd = nextStart + itemsPerPage;
351 //hides current reports and shows reports for the next page
352 handlePaginationReports();
353 //changes numbers in Showing x to y of z results
354 handleShowingResults();
355 //updates the pagination labels
356 handlePaginationButtons();
357 //update current page
358 currentPage = nextPage;
359 }
360
361 function handlePaginationReports() {
362 //hides current page reports
363 var curStart = (currentPage) * itemsPerPage;
364 var curEnd = curStart + itemsPerPage;
365 for (let r = curStart; r < curEnd && r < totalTaggedRows; r++) {
366 if (!taggedRows[r].classList.contains("assetHidden")) {
367 taggedRows[r].classList.add("assetHidden");
368 }
369 }
370 //shows next page reports
371 var nextStart = (nextPage) * itemsPerPage;
372 var nextEnd = nextStart + itemsPerPage;
373 for (let r = nextStart; r < nextEnd && r < totalTaggedRows; r++) {
374 if (taggedRows[r].classList.contains("assetHidden")) {
375 taggedRows[r].classList.remove("assetHidden");
376 }
377 }
378 }
379
380 function handleShowingResults() {
381 const startResultsLabel = document.getElementById('startResults_${portletID}');
382 startResultsLabel.innerText = nextStart + 1;
383 const endResultsLabel = document.getElementById('endResults_${portletID}');
384 endResultsLabel.innerText = nextEnd < totalTaggedRows ? nextEnd : totalTaggedRows;
385 const totalResultsLabel = document.getElementById('totalResults_${portletID}');
386 totalResultsLabel.innerText = totalTaggedRows;
387 }
388
389 function handlePaginationButtons() {
390 var currentPageNumber = nextPage + 1;
391 // numbered buttons
392 const firstBtn = document.getElementById('firstBtn_${portletID}');
393 const secondBtn = document.getElementById('secondBtn_${portletID}');
394 const thirdBtn = document.getElementById('thirdBtn_${portletID}');
395 const fourthBtn = document.getElementById('fourthBtn_${portletID}');
396 if (totalPages <= 4 || currentPageNumber == 1 || currentPageNumber == 2 || currentPageNumber == 3) {
397 updateButton(firstBtn, currentPageNumber, 1);
398 updateButton(secondBtn, currentPageNumber, 2);
399 updateButton(thirdBtn, currentPageNumber, 3);
400 updateButton(fourthBtn, currentPageNumber, 4);
401 startPagination = 1;
402 } else if (currentPageNumber == totalPages) {
403 updateButton(firstBtn, currentPageNumber, currentPageNumber - 3);
404 updateButton(secondBtn, currentPageNumber, currentPageNumber - 2);
405 updateButton(thirdBtn, currentPageNumber, currentPageNumber - 1);
406 updateButton(fourthBtn, currentPageNumber, currentPageNumber);
407 startPagination = currentPageNumber - 3;
408 } else if (currentPageNumber == totalPages - 1) {
409 updateButton(firstBtn, currentPageNumber, currentPageNumber - 2);
410 updateButton(secondBtn, currentPageNumber, currentPageNumber - 1);
411 updateButton(thirdBtn, currentPageNumber, currentPageNumber);
412 updateButton(fourthBtn, currentPageNumber, currentPageNumber + 1);
413 startPagination = currentPageNumber - 2;
414 } else if (currentPageNumber > 3) {
415 updateButton(firstBtn, currentPageNumber, currentPageNumber - 2);
416 updateButton(secondBtn, currentPageNumber, currentPageNumber - 1);
417 updateButton(thirdBtn, currentPageNumber, currentPageNumber);
418 updateButton(fourthBtn, currentPageNumber, currentPageNumber + 1);
419 startPagination = currentPageNumber - 2;
420 }
421 // prev and next buttons
422 const prevBtn = document.getElementById('prevBtn_${portletID}');
423 const nextBtn = document.getElementById('nextBtn_${portletID}');
424 if (currentPageNumber == 1) {
425 prevBtn.classList.add('disabled');
426 } else {
427 prevBtn.classList.remove('disabled');
428 }
429 if (currentPageNumber == totalPages) {
430 nextBtn.classList.add('disabled');
431 } else {
432 nextBtn.classList.remove('disabled');
433 }
434 }
435
436 function updateButton(button, currentPageNumber, label) {
437 // active
438 if (currentPageNumber == label) {
439 button.classList.add('active');
440 } else {
441 button.classList.remove('active');
442 }
443 // label + visibility
444 if (totalPages >= label && currentPageNumber <= totalPages) {
445 button.classList.remove('assetHidden');
446 button.innerText = label;
447 } else {
448 button.classList.add('assetHidden');
449 }
450 }
451 //
452 //EVENT LISTENERS //
453 // Pagination event listener
454 var prevBtn = document.getElementById(`prevBtn_${portletID}`);
455 prevBtn.addEventListener('click', function(event) {
456 handlePaginationChange("prev");
457 });
458 var firstBtn = document.getElementById(`firstBtn_${portletID}`);
459 firstBtn.addEventListener('click', function(event) {
460 handlePaginationChange("0");
461 });
462 var secondBtn = document.getElementById(`secondBtn_${portletID}`);
463 secondBtn.addEventListener('click', function(event) {
464 handlePaginationChange("1");
465 });
466 var thirdBtn = document.getElementById(`thirdBtn_${portletID}`);
467 thirdBtn.addEventListener('click', function(event) {
468 handlePaginationChange("2");
469 });
470 var fourthBtn = document.getElementById(`fourthBtn_${portletID}`);
471 fourthBtn.addEventListener('click', function(event) {
472 handlePaginationChange("3");
473 });
474 var nextBtn = document.getElementById(`nextBtn_${portletID}`);
475 nextBtn.addEventListener('click', function(event) {
476 handlePaginationChange("next");
477 });
478 ///////////////////////
479})();
480</script>