1<#-- Variables -->
2<#assign specificationName = 'ctn' />
3<#assign paramNameSpecificationName = cpSpecificationOptionsSearchFacetDisplayContext.getParameterName() />
4<#assign isDebug = false />
5<#assign showAllSpecificationsName = false />
6<#assign staticHost = getCXConfig("ecom-static-files") />
7
8<#-- ========================= -->
9<#-- Functions freemaker -->
10<#-- ========================= -->
11<#function getCXConfig clientExtensionName field="webApiUrl">
12 <#if !clientExtensionName?has_content><#return "" /></#if>
13 <#local encoded = urlCodec.encodeURL(clientExtensionName) />
14 <#local items = restClient.get("/c/customconfigs/?fields=clientExtensionConfig&filter=clientExtensionName eq '" + encoded + "'").items![] />
15 <#return items?has_content?then(items[0].clientExtensionConfig?eval[field]!"", "") />
16</#function>
17
18<#function getTaxonomyVocabularyId vocabularyName>
19 <#local response = restClient.get("/headless-admin-taxonomy/v1.0/sites/" + groupId + "/taxonomy-vocabularies?filter=contains(name, '" + vocabularyName + "')&pageSize=1")!{} />
20 <#local items = response.items![] />
21 <#return items?has_content?then(items[0].id, "") />
22</#function>
23
24<#function getTaxonomyCategoryMap vocabularyId pageSize=200>
25 <#local response = restClient.get("/headless-admin-taxonomy/v1.0/taxonomy-vocabularies/" + vocabularyId + "/taxonomy-categories?pageSize=" + pageSize)!{} />
26 <#local categoryMap = {} />
27 <#list response.items![] as cat>
28 <#local categoryMap = categoryMap + {vocabularyId + "-" + cat.id: cat.name} />
29 </#list>
30 <#return categoryMap />
31</#function>
32
33<#function getSpecOrganismosMap specificationKey organismoMap aggResult>
34 <#local specMap = {} />
35 <#if !aggResult?has_content><#return specMap /></#if>
36 <#list aggResult.getBuckets() as specBucket>
37 <#local specKey = specBucket.getKey() />
38 <#local organismoAgg = specBucket.getChildAggregationResult("organismos") />
39 <#local orgNames = [] />
40 <#list organismoAgg.getBuckets() as orgBucket>
41 <#local orgName = organismoMap[orgBucket.getKey()]!"?" />
42 <#local orgNames = orgNames + [orgName] />
43 </#list>
44 <#local specMap = specMap + {specKey: orgNames} />
45 </#list>
46 <#return specMap />
47</#function>
48
49<#function getAggregationResult specificationKey>
50 <#local sharedSearchResponse = renderRequest.getAttribute("LIFERAY_SHARED_PortletSharedSearchResponse") />
51 <#local searchResponse = sharedSearchResponse.get() />
52 <#local esResponse = searchResponse.getSearchResponse() />
53 <#return esResponse.getAggregationResult(specificationKey + "_with_organismos") />
54</#function>
55
56<#if paramNameSpecificationName == specificationName || showAllSpecificationsName>
57
58 <#-- ========================= -->
59 <#-- Datos -->
60 <#-- ========================= -->
61 <#assign organismoVocabId = getTaxonomyVocabularyId("organismos") />
62 <#assign organismoMap = getTaxonomyCategoryMap(organismoVocabId) />
63 <#assign specOrganismosMap = getSpecOrganismosMap(paramNameSpecificationName, organismoMap, aggResult) />
64
65 <#-- ========================= -->
66 <#-- Debug -->
67 <#-- ========================= -->
68 <#if isDebug>
69 <div style="background:#f0f0f0;padding:8px;margin-bottom:8px;font-size:11px;border:1px solid #ccc;">
70 <p><strong>paramName:</strong> ${paramNameSpecificationName}</p>
71 <p><strong>organismoMap size:</strong> ${organismoMap?size}</p>
72 <p><strong>specOrganismosMap size:</strong> ${specOrganismosMap?size}</p>
73 <#list specOrganismosMap?keys as spec>
74 <p>${spec}: ${specOrganismosMap[spec]?join(", ")}</p>
75 </#list>
76 </div>
77 </#if>
78
79 <#-- Variables -->
80 <#assign facetId = "facet-" + paramNameSpecificationName + "-" + renderResponse.getNamespace() />
81 <#assign facetClass = "facet-" + paramNameSpecificationName + "-combo-search-wrapper" />
82
83 <#-- ========================= -->
84 <#-- TOM SELECT framework -->
85 <#-- ========================= -->
86 <@liferay_util["html-top"] outputKey="tom-select">
87 <link href="${staticHost}/scripts/vendor/tom-select/tom-select.css" rel="stylesheet">
88 <script src="${staticHost}/scripts/vendor/tom-select/tom-select.complete.min.js"></script>
89 </@>
90
91 <#-- ========================= -->
92 <#-- CSS -->
93 <#-- ========================= -->
94 <style>
95 #${facetId} {
96 --facet-font-family: Inter;
97 --facet-font-size: 14px;
98 --facet-font-weight: 400;
99 --facet-line-height: 18px;
100 --facet-letter-spacing: 0.5px;
101 --facet-color: #66757f;
102 }
103 #${facetId}-select {
104 appearance: none;
105 -webkit-appearance: none;
106 visibility: hidden;
107 position: absolute;
108 }
109 #${facetId} .ts-wrapper {
110 width: 100%;
111 margin-bottom: 16px;
112 opacity: 0;
113 transition: opacity 0.1s ease;
114 }
115 #${facetId} .ts-wrapper .ts-control {
116 padding: 15.5px 16px;
117 font-family: var(--facet-font-family);
118 font-size: var(--facet-font-size);
119 font-weight: var(--facet-font-weight);
120 line-height: var(--facet-line-height);
121 letter-spacing: var(--facet-letter-spacing);
122 color: var(--facet-color);
123 text-align: left;
124 height: 55px;
125 width: 100%;
126 border-radius: 4px;
127 border: none;
128 background-color: #F5F5F5;
129 background-image: url(/documents/d/global/ico-chevron-down-2);
130 background-repeat: no-repeat;
131 background-position: right 1rem center;
132 background-size: 18px 10px;
133 box-shadow: none;
134 cursor: pointer;
135 box-sizing: border-box;
136 }
137 #${facetId} .ts-wrapper .ts-control:focus,
138 #${facetId} .ts-wrapper .ts-control:focus-visible {
139 background-image: url(/documents/d/global/ico-chevron-down-2);
140 background-position: right 1rem center;
141 background-size: 18px 10px;
142 outline: none;
143 box-shadow: none;
144 }
145 #${facetId} .ts-wrapper .ts-control .item {
146 padding-right: 0.75rem;
147 overflow: hidden;
148 text-overflow: ellipsis;
149 max-width: calc(100% - 0.75rem);
150 }
151 #${facetId} .ts-wrapper.single .ts-control::after {
152 display: none;
153 }
154 #${facetId} .ts-dropdown .dropdown-input-wrap .dropdown-input {
155 font-family: var(--facet-font-family);
156 font-size: var(--facet-font-size);
157 font-weight: var(--facet-font-weight);
158 line-height: var(--facet-line-height);
159 letter-spacing: var(--facet-letter-spacing);
160 color: var(--facet-color);
161 padding: 8px 16px;
162 border: none;
163 border-bottom: 1px solid #d9d9d9;
164 background-color: #fff;
165 width: 100%;
166 box-sizing: border-box;
167 }
168 #${facetId} .ts-dropdown .dropdown-input-wrap .dropdown-input:focus {
169 outline: none;
170 box-shadow: none;
171 }
172 #${facetId} .ts-dropdown .ts-dropdown-content .option {
173 font-family: var(--facet-font-family);
174 font-size: var(--facet-font-size);
175 font-weight: var(--facet-font-weight);
176 line-height: var(--facet-line-height);
177 letter-spacing: var(--facet-letter-spacing);
178 color: var(--facet-color);
179 padding: 8px 16px;
180 }
181 #${facetId} .ts-dropdown .ts-dropdown-content .option:hover,
182 #${facetId} .ts-dropdown .ts-dropdown-content .option.active {
183 background-color: #6a9bd3;
184 color: #fff;
185 }
186 #${facetId} .ts-dropdown .ts-dropdown-content .option:hover .badge,
187 #${facetId} .ts-dropdown .ts-dropdown-content .option.active .badge {
188 background-color: #fff;
189 color: #3a6a9b;
190 }
191 </style>
192
193 <div class="checks-container ${facetClass}" id="${facetId}">
194 <div class="d-flex flex-column w-100">
195 <label class="panel-title mb-2" for="${facetId}-select">
196 ${languageUtil.get(locale, "norma." + paramNameSpecificationName)}
197 <#if isDebug>
198 <p style="font-size:11px;color:#999;font-weight:normal;">
199 (total options: ${entries?size})
200 </p>
201 <p style="font-size:11px;color:#999;font-weight:normal;">
202 (facetId: ${facetId})
203 </p>
204 </#if>
205 </label>
206
207 <#-- ========================= -->
208 <#-- SELECT UI -->
209 <#-- ========================= -->
210 <select id="${facetId}-select" data-parameter-name="${paramNameSpecificationName}">
211 <option value="">${languageUtil.get(locale, "search.cualquiera")}</option>
212 <#list entries?sort_by("displayName") as entry>
213 <#assign specName = entry.getDisplayName() />
214 <#assign orgNames = specOrganismosMap[specName]![] />
215 <option value="${htmlUtil.escape(specName)}"
216 <#if entry.isSelected()>selected</#if>
217 data-organismos="${htmlUtil.escape(orgNames?join(","))}">
218 ${htmlUtil.escape(specName)} (${entry.getFrequency()})
219 </option>
220 </#list>
221 </select>
222 </div>
223 </div>
224
225 <#-- ========================= -->
226 <#-- SCRIPT -->
227 <#-- ========================= -->
228 <script>
229 (function () {
230 var FACET_ID = '${facetId}';
231 var PARAM_NAME = '${paramNameSpecificationName}';
232
233 function applyUrlFilter(value) {
234 var url = new URL(window.location.href);
235 if (value) {
236 url.searchParams.set(PARAM_NAME, value);
237 } else {
238 url.searchParams.delete(PARAM_NAME);
239 }
240 window.location.href = url.toString();
241 }
242
243 function initFacet() {
244 var select = document.getElementById(FACET_ID + '-select');
245 if (!select) return;
246 if (select.tomselect) select.tomselect.destroy();
247 if (select.dataset.bound === 'true') return;
248 select.dataset.bound = 'true';
249
250 new TomSelect(select, {
251 allowEmptyOption: true,
252 maxItems: 1,
253 create: false,
254 render: {
255 option: function(data, escape) {
256 var organismos = data.$option ? data.$option.getAttribute('data-organismos') : '';
257 var badges = '';
258 if (organismos) {
259 organismos.split(',').forEach(function(org) {
260 badges += '<span class="badge badge-secondary mr-1">' + escape(org.trim()) + '</span>';
261 });
262 }
263 return '<div class="option py-1">' + escape(data.text) + '<div class="mt-1">' + badges + '</div></div>';
264 },
265 no_results: function(data, escape) {
266 return '<div class="no-results">${languageUtil.get(locale, "occurrence-not-found")}</div>';
267 }
268 },
269 plugins: {
270 dropdown_input: {}
271 },
272 onDelete: function(value) {
273 this.isDelete = true;
274 },
275 onChange: function(value) {
276 if (value) {
277 this.lastValidValue = value;
278 applyUrlFilter(value);
279 return;
280 }
281 this.lastValidValue = '';
282 applyUrlFilter('');
283 },
284 onInitialize: function() {
285 this.lastValidValue = this.getValue() || '';
286 this.isDelete = false;
287 var wrapper = document.querySelector('#' + FACET_ID + ' .ts-wrapper');
288 if (wrapper) wrapper.style.opacity = '1';
289 }
290 });
291 }
292
293 if (document.readyState === 'loading') {
294 document.addEventListener('DOMContentLoaded', initFacet);
295 } else {
296 initFacet();
297 }
298 })();
299 </script>
300
301</#if>