Case Studies
An error occurred while processing the template.
The following has evaluated to null or missing: ==> mlxUtilService.getReferences [in template "20101#20128#CASE-STUDIES-FILTER" at line 30, column 23] ---- Tip: It's the step after the last dot 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 references = mlxUtilService.g... [in template "20101#20128#CASE-STUDIES-FILTER" at line 30, column 1] ----
1<#assign cdn=(mlxUrlUtilService.getCdn(groupId))!"" />
2<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
3<script src="https://unpkg.com/vue-router@3.5.1/dist/vue-router.min.js"></script>
4
5<#-- variables servicios -->
6<#assign numElem = 20/>
7
8<#if (request.getParameter("page")?has_content) && (request.getParameter("page")?is_number)>
9 <#assign currentPage = request.getParameter("page")?number - 1/>
10 <#if currentPage < 0>
11 <#assign currentPage = 0/>
12 </#if>
13<#else>
14 <#assign currentPage = 0/>
15</#if>
16
17<#if (request.getParameter("prodId")?has_content)>
18 <#assign prodId = request.getParameter("prodId")/>
19<#else>
20 <#assign prodId = ''/>
21</#if>
22
23<#if (request.getParameter("catId")?has_content)>
24 <#assign catId = request.getParameter("catId")/>
25<#else>
26 <#assign catId = ''/>
27</#if>
28
29<#-- OBTENER LISTA REFERENCIAS -->
30<#assign references = mlxUtilService.getReferences(groupId,themeDisplay.getLanguageId(),catId,prodId,currentPage,numElem,true,true)/>
31
32<#-- OBTENER TOTAL PAGINAS -->
33<#assign totalPages = references.pages />
34
35<#-- OBTENER TOTAL ITEMS -->
36<#assign totalItems = references.count />
37
38<#--Se hace un ?replace para limpiar el json de los caracteres especiales (encoding) para que el ?eval no genere errores de Syntax error, se podria intentar probar con la version de freemarker (2.3.31) el ?eval_json https://freemarker.apache.org/docs/dgui_template_exp.html#dgui_template_exp_direct_string -->
39<#assign referencesList = references.references?replace('\\u','\\x')?replace('<[^>]+>','','r')?replace('<\\/i>','')?replace('<i>','') />
40
41<script>
42Vue.prototype.$vue = false;
43Vue.prototype.$ftl = true;
44</script>
45
46 <style>
47
48// Hide app until loaded
49[v-cloak] {
50 display: none;
51}
52
53[v-cloak] .corporate--cases--filters--form--select {
54 pointer-events:none;
55 opacity: .5;
56}
57
58
59
60.loading {
61 display: grid;
62 place-content: center;
63 background: rgba(0, 0, 0, 0.3);
64 z-index: 999;
65 position: fixed;
66 top: 0;
67 left: 0;
68 bottom: 0;
69 right: 0;
70}
71
72#app:not([v-cloak])~.loading {
73 display: none;
74}
75
76#app:[v-cloak] .corporate--cases--filters--form--select {
77 pointer-events:none;
78 opacity: .5;
79}
80.corporate--cases--filters--title {
81 white-space: nowrap;
82}
83.corporate--cases--filters--form {
84 display: flex;
85}
86@media (max-width: 1080px) {
87 .corporate--cases--filters--form {
88 flex-direction: column;
89 }
90}
91.corporate--cases--filters--form--select {
92 min-width: 24rem;
93 font-size: .9rem;
94}
95@media (max-width: 420px) {
96 .corporate--cases--filters--form--select {
97 min-width: 21rem;
98 font-size: .9rem;
99}
100}
101.corporate--cases--filters--form--column--small {
102 display:inherit;
103}
104.corporate--cases--filters--reset {
105 background: #005198;
106 border-radius: .4rem;
107 border: none;
108 color: #fff;
109 cursor: pointer;
110 display: inline-block;
111 padding: .5rem 1rem;
112 text-decoration: none;
113 font-weight: bold;
114 text-align: center;
115 margin-top: 1rem;
116}
117
118.page-item a {
119 color: #006ece;
120 margin-bottom: 0;
121 margin-top: 0;
122 text-decoration: none;
123}
124
125.page-item a:hover {
126 color: #2a9cff;
127}
128
129a.disabled {
130 pointer-events: none;
131 display: none !important;
132}
133
134.pagination-bar {
135 flex-direction: column !important;
136 padding: 0 !important;
137}
138
139#corporate--filter-section--nav-anchor{
140position: relative;
141 top: -70px;
142}
143
144.lfr-pagination .lfr-pagination-buttons li a {
145 margin: 0.25rem !important;
146}
147
148.lfr-pagination .lfr-pagination-buttons {
149 padding-left: 3rem !important;
150}
151 </style>
152
153 <div id="app" v-cloak>
154 <span id="corporate--filter-section--nav-anchor"></span>
155 <div class="corporate--cases--filters">
156 <div class="corporate--cases--filters--container">
157 <h4 class="corporate--cases--filters--title">
158 <@corporate.mlxlanguage key="mlx.practical-case.see-practical-cases" />
159 </h4>
160 <div class="corporate--cases--filters--form--row">
161 <div class="corporate--cases--filters--form">
162 <div class="corporate--cases--filters--form--column--small">
163 <div class="corporate--cases--filters--form--select--wrapper">
164 <select class="corporate--cases--filters--form--select" id="sectorSelector" :disabled="sectors.length == 0" v-model="sectorSelected" v-on:change="onChange($event)">
165 <option value="">
166 <@corporate.mlxlanguage key="mlx.practical-case.sector" />
167 </option>
168 <option :key="index" :value="Object.values(sector)[0]" v-for="(sector, index) in sectors">{{Object.keys(sector)[0]}}
169 </option>
170 </select>
171 </div>
172 </div>
173 <div class="corporate--cases--filters--form--column--small">
174 <div class="corporate--cases--filters--form--select--wrapper">
175 <select class="corporate--cases--filters--form--select" id="productSelector" :disabled="products.length == 0" v-model="productSelected" v-on:change="onChange($event)">
176 <option value="">
177 <@corporate.mlxlanguage key="mlx.practical-case.product" />
178 </option>
179 <option :key="index" :value="Object.values(product)[0]" v-for="(product, index) in filterProducts">{{Object.keys(product)[0]}}
180 </option>
181 </select>
182 </div>
183 </div>
184 </div>
185 </div>
186
187 <button v-on:click="resetFilters()" class="corporate--cases--filters--reset">
188 <svg class="lexicon-icon lexicon-icon-reset" role="presentation" viewBox="0 0 512 512" style="fill:#fff; width:.5rem; height:.5rem">
189 <path class="lexicon-icon-outline" d="M256,0C179.5,0,110.9,33.5,64,86.7V64c0-44.5-64-43-64,0v96l0,0c0,18.5,15,32,32,32l0,0h96c41.5,0,43.5-64,0-64h-15.1c35.2-39.3,86.2-64,143.1-64c251,0,253,384,0,384c-95.1,0-174.1-69.2-189.3-160H2c15.7,126.3,123.5,224,254,224C593,512,598,0,256,0z"></path>
190 </svg>
191 </button>
192
193 </div>
194 </div>
195
196 <div class="corporate--cases--list" v-if="Vue.prototype.$ftl" id="freemarker">
197 <#attempt>
198 <#if references.references?has_content>
199 <#list referencesList?eval as reference>
200 <article class="corporate--cases--list--item">
201 <a class="corporate--cases--list--item--link" href="${reference.pcase.url}">
202 <figure class="corporate--cases--list--item--figure">
203
204 <#--<img class="corporate--cases--list--item--image" src="${reference.pcase.list_image}" alt="${reference.pcase.list_image}">-->
205 ${corporate.img(reference.pcase.list_image, false, 'alt="' + reference.pcase.list_image + '"', 'title="' + reference.pcase.list_image + '"', 'class="corporate--cases--list--item--image"')}
206 </figure>
207 <h5 class="corporate--cases--list--item--upper-heading">${reference.name}</h5>
208 <h3 class="corporate--cases--list--item--link-wrapper">
209 ${reference.pcase.name}
210 </h3>
211 </a>
212 <#list reference.sectorCategories as sector>
213 <#list sector as sec , cod>
214 <span class="corporate--cases--list--item--heading">${sec}</span>
215 </#list>
216 </#list>
217 </article>
218 </#list>
219 </#if>
220 <#recover>
221 </#attempt>
222 </div>
223 <div class="corporate--cases--list" v-if="Vue.prototype.$vue && references.length > 0" id="vue">
224 <template :key="index" v-for="(reference, index) in references">
225 <article class="corporate--cases--list--item" v-if="Object.keys(reference.pcase).length">
226 <!--<article class="corporate--cases--list--item"-->
227 <a class="corporate--cases--list--item--link" v-bind:href="reference.pcase.url">
228 <figure class="corporate--cases--list--item--figure"><img class="corporate--cases--list--item--image" v-if="reference.pcase.list_image.length" :src="reference.pcase.list_image" :alt="reference.pcase.list_image"></figure>
229 <h5 class="corporate--cases--list--item--upper-heading">{{reference.name}}</h5>
230 <h3 class="corporate--cases--list--item--link-wrapper">
231 {{reference.pcase.name}}
232 </h3>
233 </a>
234 <template v-for="(key, value) in reference.sectorCategories">
235 <span class="corporate--cases--list--item--heading" v-for="(cod , sec) in key">{{ sec }}</span>
236 </template>
237 </article>
238 </template>
239 </div>
240
241 <#--<h2>CurrentPage : {{page}}
242 </h2>-->
243 <#-- valorar si usar variable this.$router.history.current.path o poner por freemarker -->
244 <div class="pagination-bar">
245 <!--section class="pagination--container"-->
246 <template>
247 <div class="clearfix lfr-pagination">
248 <div class="lfr-pagination-config">
249 <div class="lfr-pagination-page-selector">
250 <div class="btn-group lfr-icon-menu current-page-menu">
251 <a class="dropdown-toggle direction-down max-display-items-15 btn btn-default" v-if="page <= totalPages && page >= 1">
252 <span v-if="page <= totalPages && page >= 1" class="lfr-icon-menu-text">
253 <#assign pagText><@corporate.mlxlanguage key="mlx.search.paginate" /></#assign>
254 ${pagText?replace('0','{ page }')?replace('1','{ totalPages }')}
255 </span>
256 </a>
257 </div>
258 </div>
259 </div>
260 <ul class="lfr-pagination-buttons pager" v-if="page <= totalPages && page >= 1">
261 <li v-if="!isInFirstPage">
262 <router-link :class="{ disabled: isInFirstPage }"
263 :to="{ path: Liferay.ThemeDisplay.getLayoutRelativeURL(), query: newQueryCopyValue( 'page', 1)}"> ← <@corporate.mlxlanguage key="first" />
264 </router-link>
265 </li>
266 <li v-if="!isInFirstPage">
267 <router-link :class="{ disabled: isInFirstPage }"
268 :to="{ path: Liferay.ThemeDisplay.getLayoutRelativeURL(), query: newQueryCopyValue( 'page', parseInt(page)-1)}">
269 <@corporate.mlxlanguage key="previous" />
270 </router-link>
271 </li>
272 <li v-if="!isInLastPage">
273 <router-link :class="{ disabled: isInLastPage }"
274 :to="{ path: Liferay.ThemeDisplay.getLayoutRelativeURL(), query: newQueryCopyValue( 'page', parseInt(page)+1)}">
275 <@corporate.mlxlanguage key="next" />
276 </router-link>
277 </li>
278 <li v-if="!isInLastPage">
279 <router-link :class="{ disabled: isInLastPage }"
280 :to="{ path: Liferay.ThemeDisplay.getLayoutRelativeURL(), query: newQueryCopyValue( 'page', totalPages)}">
281 <@corporate.mlxlanguage key="last" /> → </router-link>
282 </li>
283 </ul>
284 </div>
285 </template>
286 <!--ul class="pagination">
287 <li class="page-item" v-if="totalPages > 0" v-for="pageNum in totalPages" :key="pageNum">
288 <router-link href="#app" :to="{ path: Liferay.ThemeDisplay.getLayoutRelativeURL(), query: newQueryCopyValue( 'page', pageNum)}"> {{pageNum}}
289 </router-link>
290 </li>
291 </ul-->
292 <!--/section-->
293 </div>
294 <#--<router-link :to="{ path: this.$router.history.current.path, query: newQueryCopyValue(this.$route.query, 'page', 2)}"> 2 </router-link>
295 <router-link :to="{ path: this.$router.history.current.path, query: newQueryCopyValue(this.$route.query, 'page', 3)}"> 3 </router-link>-->
296 </div>
297 <!--div class="loading" v-cloak>
298 <img alt="" class="sending" src="${cdn}/o/corporate-theme/images/common/loading2.gif">
299 </div-->
300 <script>
301 window.addEventListener('load', function() {
302 //num elementos a mostrar
303 var numElem = ${numElem};
304 // Define routes
305 var routes = [{
306 path: '/'
307 } // tambien sirve Liferay.ThemeDisplay.getLayoutRelativeURL()
308 ]
309 // Create the router instance and pass the `routes` option
310 var router = new VueRouter({
311 mode: 'history', //quitar esta propiedad en caso de que de problemas
312 linkActiveClass: "page-link", // active class for non-exact links.
313 linkExactActiveClass: "page-link-active", // active class for *exact* links.
314 scrollBehavior: function(to, _from, savedPosition) {
315 if (to.query.page) {
316 document.getElementById('corporate--filter-section--nav-anchor').scrollIntoView();
317 } else if (savedPosition) {
318 return savedPosition;
319 }
320 },
321 routes: routes,
322 })
323 // Create and mount the root instance.
324 // Make sure to inject the router with the router option to make the
325 // whole app router-aware.
326 var app = new Vue({
327 data: {
328 sector: [],
329 productSelected: '${prodId}',
330 sectorSelected: '${catId}',
331 products: ${references.productSelector},
332 sectors: ${references.sectorSelector},
333 references: ${references.references},
334 catId: '',
335 prodId: '',
336 page: ${currentPage},
337 totalItems: ${totalItems},
338 totalPages: ${totalPages},
339 respondToRouteChanges: true,
340 },
341 created: function created() {
342 // Initially load store dat
343 // this.loadData();
344 },
345 mounted: function mounted() {
346 if (isNaN(this.$route.query.page)) {
347 this.page = 1;
348 } else {
349 this.page = this.$route.query.page;
350 }
351 },
352 watch: {
353 $route: function(to, from) {
354 Vue.prototype.$vue = true;
355 Vue.prototype.$ftl = false;
356 this.loadData();
357 }
358 },
359 computed: {
360 isInFirstPage() {
361 return this.page == 1;
362 },
363 isInLastPage() {
364 return this.page == this.totalPages;
365 },
366 filterProducts() {
367 return this.products.filter(product => !Object.keys(product)[0].includes("*"));
368 },
369 },
370 methods: {
371 loadData: function loadData() {
372 //sector
373 this.sectorSelected = this.queryCatId();
374 this.catId = this.queryCatId();
375 //product
376 this.productSelected = this.queryProdId();
377 this.prodId = this.queryProdId();
378 this.page = this.$route.query.page;
379 // Update canonical href
380 document.querySelector('link[rel="canonical"]').setAttribute('href', Liferay.ThemeDisplay.getPortalURL() + this.$route.path + '?page=' + this.page);
381 // Call API to get shop data
382 mecalux.remote.ws.getReferences(this.queryCatId(), this.queryProdId(), this.queryPage(), numElem, true, true, function(obj) {
383 app.sector = obj.sectorCategories;
384 app.totalPages = obj.pages;
385 app.totalItems = obj.count;
386 app.products = obj.productSelector;
387 app.sectors = obj.sectorSelector;
388 app.references = obj.references;
389
390 });
391 },
392 setCatId: function setCatId() {
393 if (!this.respondToRouteChanges) {
394 // console.log('ignoring since route changes ignored')
395 return;
396 }
397 if (this.catId !== this.queryCatId) this.catId = this.queryCatId;
398 },
399 queryCatId: function queryCatId() {
400 return this.$route.query.catId || '';
401 },
402 queryProdId: function queryProdId() {
403 return this.$route.query.prodId || '';
404 },
405 queryPage: function queryPage() {
406 var copyQuery = Object.assign({}, this.$route.query);
407 var copyQueryPage = copyQuery.page - 1;
408 return (copyQueryPage > 0) ? copyQueryPage : 0;
409 },
410 // clona la query actual y agrega un parametro (para sustituir a '...' en ie)
411 newQueryCopyValue: function newQueryCopyValue(newParam, newValue) {
412 var copyQuery = {};
413 if(this.queryCatId()){
414 copyQuery['catId'] = this.queryCatId();
415 }
416 if(this.queryProdId()){
417 copyQuery['prodId'] = this.queryProdId();
418 }
419 copyQuery[newParam] = newValue;
420 return copyQuery;
421 },
422 updateQueryParams: function updateQueryParams() {
423 //cambiar parametros de la query de la url (?) sin tener que hacer reload
424 this.respondToRouteChanges = false;
425 //params
426 var params = {};
427 if (this.catId !== '') {
428 params['catId'] = this.catId;
429 }
430 if (this.prodId !== '') {
431 params['prodId'] = this.prodId;
432 }
433 if (this.page >= 1) {
434 params['page'] = this.page;
435 }
436 //if(Object.keys(params).length){
437 this.$router.push({
438 query: params
439 }).catch(function(e) {
440 //para evitar el error Avoided redundant navigation
441 //console.log(e);
442 }).finally(function() {
443 //para que se pueda hacer reload de nuevo en la pagina
444 this.respondToRouteChanges = true;
445 });
446 //}
447 },
448 onChange: function onChange(event) {
449 if (event.target.id === "sectorSelector") this.catId = event.target.value;
450 if (event.target.id === "productSelector") this.prodId = event.target.value;
451 this.page = 1;
452 this.updateQueryParams();
453 },
454 resetFilters: function resetFilters() {
455 this.catId = '';
456 this.prodId = '';
457 this.page = 1;
458 this.updateQueryParams();
459 }
460 },
461 router: router
462 }).$mount('#app')
463 // Now the app has started!
464
465 }, false)
466</script>