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