
Onko palveluillamme vaikutusta? - blogi
Blogisarja kertoo inspiroivia esimerkkejä Pirkanmaan hyvinvointialueen palveluiden kehittämisestä ja vaikuttavuustyöstä eri palveluissa.
Vaikuttavuus tarvitsee yhteisen kielen, jotta se voi toteutua
Blogi
15.4.2026
Virhe tapahtui prosessoidessa esitysmallia.
Failed to "?eval" string with this error:
---begin-message---
Syntax error in ?eval-ed string in line 1, column 77:
Lexical error: encountered "u" (117), after "\"Henkil\u00f6 pit\u00e4\u00e4 k\u00e4siss\u00e4\u00e4n esitett\u00e4, jossa lukee \\".
---end-message---
The failing expression:
==> image?eval [in template "43104#43145#2794039" at line 42, column 38]
----
FTL stack trace ("~" means nesting-related):
- Failed at: #assign imageJson = image?eval [in template "43104#43145#2794039" at line 42, column 17]
----
1<#assign
2 portlet_preferences = portletDisplay.getPortletPreferences()
3 portlet_use_custom_title = getterUtil.getString(portlet_preferences.getValue("portletSetupUseCustomTitle", ""))
4 portlet_title = htmlUtil.escape(portletDisplay.getTitle())
5 portletId = portletDisplay.instanceId
6 headingSize = 2
7/>
8
9<#assign scopeGroupTreePath = themeDisplay.getScopeGroup().treePath?split('/') />
10<#assign pirhaGroupId = scopeGroupTreePath[1]?number />
11
12<#assign images_folder = themeDisplay.getPathThemeImages() />
13<#assign pageUrl = "">
14
15<#if entries?has_content>
16 <#if portlet_use_custom_title == 'true' >
17 <h2 class="portlet-title-custom" id="heading-${portletId}">${portlet_title}</h2>
18 <#assign headingSize = 3 />
19 </#if>
20
21 <div class="pirha-news-list list-with-images grid-list">
22 <#list entries as curEntry>
23 <#assign assetRenderer = curEntry.getAssetRenderer() />
24 <#assign pageUrl = assetPublisherHelper.getAssetViewURL(renderRequest, renderResponse, assetRenderer, entry, true)>
25
26 <#assign article = assetRenderer.getArticle() />
27
28 <#assign title = webArticleHelper.getArticleFieldValue(article, "title") />
29 <#assign lead = webArticleHelper.getArticleFieldValue(article, "lead") />
30 <#assign content = webArticleHelper.getArticleFieldValue(article, "content") />
31 <#assign image = webArticleHelper.getArticleFieldValue(article, "mainImage") />
32 <#assign publishDate = curEntry.publishDate?string('d.M.yyyy') />
33 <#assign publishDateISO = curEntry.publishDate?string("yyyy-MM-dd") />
34
35 <#assign articleDDMStructure = article.getDDMStructure() />
36 <#assign typeCategories = webArticleHelper.getAssetCategoriesForArticleInVocabulary(article, pirhaGroupId, "Tyyppi") />
37
38 <#assign imageURL = "" />
39 <#assign imageAlt = '' />
40
41 <#if image?has_content && image != ''>
42 <#assign imageJson = image?eval />
43
44 <#if imageJson.url?? && imageJson.url != "">
45 <#assign imageURL = imageJson.url />
46 </#if>
47
48 <#if imageJson.alt?? && imageJson.alt != "">
49 <#assign imageAlt = imageJson.alt />
50 </#if>
51
52 <#if imageJson.fileEntryId?? && imageJson.fileEntryId != "">
53 <#assign fileEntryId = imageJson.fileEntryId />
54 </#if>
55 </#if>
56
57 <div class="pirha-news-list-item mb-4" data-article-id="${article.id}">
58 <div class="list-item-image ratio ratio-16x9">
59 <img src="${imageURL}" alt="${imageAlt}" class="image-cover" />
60 </div>
61
62 <h${headingSize} class="pirha-news-list--heading"><a href="${pageUrl}">${title}</a></h${headingSize}>
63
64 <div class="meta">
65 <#if typeCategories?has_content>
66 <#list typeCategories as category>
67 <#if category.getTitle('fi_FI') == 'Uutinen'>
68 <#assign categoryColor = 'color--orange-dark' />
69 <#elseif category.getTitle('fi_FI') == 'Blogi'>
70 <#assign categoryColor = 'color--violet' />
71 <#else>
72 <#assign categoryColor = 'color--blue' />
73 </#if>
74
75 <span class="meta-text category ${categoryColor}">${category.getTitle(locale)}</span>
76 </#list>
77 </#if>
78
79 <span data-date="${publishDateISO}">${publishDate}</span>
80 </div>
81 </div>
82 </#list>
83 </div>
84</#if>
85
86<@liferay_util["body-bottom"] outputKey="bodybottomNewsList">
87<script>
88 document.addEventListener("DOMContentLoaded", (event) => {
89 const listOne = document.querySelector('.pirha-bg-row .pirha-news-list.list-with-images');
90 const listTwo = document.querySelector('.pirha-bg-row .pirha-news-list.list-simple');
91
92 <#-- // Find the parent portlet columns -->
93 const columnOne = listOne.closest('.portlet-column');
94 const columnTwo = listTwo.closest('.portlet-column');
95
96 if (!columnOne || !columnTwo) {
97 return;
98 }
99
100 <#-- // Check if columns are adjacent -->
101 const columns = Array.from(document.querySelectorAll('.pirha-bg-row .portlet-column'));
102 const indexOne = columns.indexOf(columnOne);
103 const indexTwo = columns.indexOf(columnTwo);
104
105 <#-- // Only run if columns are adjacent -->
106 if (Math.abs(indexOne - indexTwo) !== 1) {
107 return;
108 }
109
110 <#-- // Ignore first two -->
111 const listOneItems = Array.from(listOne.querySelectorAll('.pirha-news-list-item')).slice(2);
112
113 function getListTwoItems() {
114 return Array.from(listTwo.querySelectorAll('.item'));
115 }
116
117 <#-- // Helper to get date timestamp -->
118 function getDate(item) {
119 const el = item.querySelector('.meta span[data-date]');
120 return el ? new Date(el.dataset.date).getTime() : 0;
121 }
122
123 <#-- // Helper to get title text -->
124 function getTitle(item) {
125 const el = item.querySelector('.heading a, .pirha-news-list--heading a');
126 return el ? el.textContent.trim().toLowerCase() : '';
127 }
128
129 <#-- // Comparator: date DESC, title ASC -->
130 function compareItems(a, b) {
131 const dateDiff = getDate(b) - getDate(a);
132 if (dateDiff !== 0) return dateDiff;
133 return getTitle(a).localeCompare(getTitle(b));
134 }
135
136 <#-- // Clone and insert for mobile -->
137 listOneItems.forEach(item => {
138 const title = getTitle(item);
139
140 <#-- // Mark original item as mobile-hidden -->
141 item.classList.add('item--mobile-hidden');
142
143 <#-- // Clone and mark as desktop-hidden (only visible on mobile) -->
144 const clone = item.cloneNode(true);
145 clone.classList.add('item--desktop-hidden');
146
147 <#-- // Remove all images from clone -->
148 clone.querySelectorAll('.list-item-image').forEach(img => img.remove());
149
150 <#-- // Find correct insert position -->
151 const insertBefore = getListTwoItems().find(existing => compareItems(clone, existing) < 0);
152
153 if (insertBefore) {
154 listTwo.insertBefore(clone, insertBefore);
155 } else {
156 listTwo.appendChild(clone);
157 }
158 });
159 })
160</script>
161
162<style>
163 .item--mobile-hidden {
164 display: none;
165 }
166
167 .item--desktop-hidden {
168 display: none;
169 }
170
171 <#-- /* Mobile view */ -->
172 @media (max-width: 767px) {
173 .item--mobile-hidden {
174 display: none !important;
175 }
176
177 .item--desktop-hidden {
178 display: block !important;
179 }
180 }
181
182 <#-- /* Desktop view */ -->
183 @media (min-width: 768px) {
184 .item--mobile-hidden {
185 display: block !important;
186 }
187
188 .item--desktop-hidden {
189 display: none !important;
190 }
191 }
192</style>
193</@>
Päivitetty 21.3.2025