Case Studies

Here you can see some real solutions that Mecalux has developed. Contact us so we can assess the ideal solution for your company.

Contact us
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)}"> &larr; <@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" /> &rarr; </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>