An error occurred while processing the template.
The following has evaluated to null or missing: ==> cpProductId [in template "44616#44647#3234203" at line 93, column 92] ---- 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: ${cpProductId} [in template "44616#44647#3234203" at line 93, column 90] ----
1<#assign
2commerceContext = renderRequest.getAttribute("COMMERCE_CONTEXT")
3channelId = commerceContext.getCommerceChannelId()
4/>
5<#if cpCatalogEntry?has_content>
6 <#assign
7 cpDefinitionId = cpCatalogEntry.getCPDefinitionId()
8 cpProductId = cpCatalogEntry.getCProductId()
9 productName = cpCatalogEntry.getName()
10 productShortDescription = cpCatalogEntry.getShortDescription()
11 productDescription = cpCatalogEntry.getDescription()
12 productImages = cpContentHelper.getImages(cpDefinitionId, themeDisplay)
13 productImages = productImages?reverse
14 cpAttachmentFileEntries = cpContentHelper.getCPMedias(cpDefinitionId, themeDisplay)
15 />
16
17 <nav class="breadcrumb-navigation" aria-label="breadcrumb">
18 <ol class="breadcrumb-list">
19 <li class="breadcrumb-item"><a id="level0CategoryCrumb" href='${languageUtil.get(locale, "what-we-offer-products-url")}' class="breadcrumb-link"></a></li>
20
21 <li class="breadcrumb-item"><a id="level1CategoryCrumb" href="#" class="breadcrumb-link"></a></li>
22 <li class="breadcrumb-item" aria-current="page">${productName}</li>
23 </ol>
24 </nav>
25
26 <div class="category-detail">
27 <h1 class="category-title">${productName}</h1>
28 <p class="category-description">${productShortDescription}</p>
29 </div>
30
31 <div class="product-catalog-detail-section">
32 <div class="accordion tabs">
33 <h2 class="toggle">Product Detail</h2>
34 <div class="content" tabindex="0">
35 <div class="product-detail-content row align-items-lg-start align-items-sm-start align-items-start align-items-md-start flex-lg-row flex-sm-row flex-row flex-md-row">
36 <div class="product-detail-slider-wrap col col-lg-5 col-sm-12 col-12 col-md-5">
37 <span class="product-detail-image-paging-info visually-hidden" role="status"></span>
38 <div class="product-detail-slider">
39 <#if productImages?has_content>
40 <#list productImages as currentImage>
41 <li class="product-detail-slider-slide">
42
43 <img class="product-detail-slider-slide-img" src="${currentImage.getURL()}" width="428" height="428" alt="${currentImage.getTitle()}" />
44 </li>
45 </#list>
46 </#if>
47 </div>
48 <div class="product-detail-slider-nav">
49 <#if productImages?has_content>
50 <#list productImages as currentImage>
51 <li role="button" class="product-detail-slider-nav-btn">
52 <img class="product-detail-slider-slide-img" src="${currentImage.getURL()}" width="65" height="65" alt="${currentImage.getTitle()}" />
53 </li>
54 </#list>
55 </#if>
56 </div>
57 </div>
58 <div class="product-detail-content-desc col col-lg-7 col-sm-12 col-12 col-md-7">
59 <p>${productDescription}</p>
60 <a class="btn btn-primary" href="/contact">Contact Sales</a>
61 </div>
62 </div>
63 </div>
64
65 <#assign contentTypeMap = {}/>
66 <#assign youtubeVideoMap = []/>
67
68 <#if cpAttachmentFileEntries?has_content>
69 <h2 class="toggle">Resources</h2>
70 <div class="content" tabindex="0">
71 <div id="resourcesDiv" >
72 </div>
73 </div>
74 </#if>
75 <h2 class="toggle">Related Products</h2>
76 <div class="content" tabindex="0">
77 <div id="relatedProductDiv">
78
79 </div>
80 </div>
81
82 <h2 id="videoTitle" class="toggle">Videos</h2>
83 <div class="content" tabindex="0">
84 <div id="videoDiv" >
85 <ul id="videoListSection" class="product-catalog-list-detail"></ul>
86 </div>
87 </div>
88 </div>
89 </div>
90</#if>
91 <script>
92
93 fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${cpProductId}/categories')
94 .then(response => response.json())
95 .then(data => {
96 const productCategoryItems = data.items;
97 if (productCategoryItems && productCategoryItems.length > 0) {
98 productCategoryItems.forEach(function (productCategoryItem) {
99 const categoryId = productCategoryItem.id
100 fetch('/o/c/levelonecategories/?fields=categoryLiferayId,categoryName,categoryURL&filter=categoryLiferayId eq '+categoryId + '&p_auth='+ Liferay.authToken)
101 .then(response => response.json())
102 .then(data => {
103 const level1Categories = data.items;
104 if (level1Categories && level1Categories.length > 0) {
105 level1Categories.forEach(function (level1Category) {
106 if(level1Category){
107 const productCategoryTitle = level1Category.categoryName;
108 const productCategoryURL = level1Category.categoryURL;
109 const productCategoryFinalURL = "/categories/" + productCategoryURL;
110 document.getElementById('level1CategoryCrumb').setAttribute('href', productCategoryFinalURL);
111 document.getElementById('level1CategoryCrumb').innerHTML = productCategoryTitle;
112
113 const level0Hash = {
114 "Electricity Meters + Modules":"Measurement + Sensing",
115 "Gas Meters + Modules":"Measurement + Sensing",
116 "Sensing + Control":"Measurement + Sensing",
117 "Thermal Energy Meters + Modules":"Measurement + Sensing",
118 "Water Meters + Modules":"Measurement + Sensing",
119 "Grid Management":"Networks + Operations",
120 "Mobile Meter Reading":"Networks + Operations",
121 "Network Infrastructure + Management ":"Networks + Operations",
122 "Operations Management":"Networks + Operations",
123 "Smart Cities":"Networks + Operations",
124 "Distributed Energy Management":"Software + Services",
125 "Energy Forecasting":"Software + Services",
126 "Meter Data Management + Analytics":"Software + Services",
127 "Prepayment":"Software + Services",
128 "Services":"Software + Services"
129 };
130
131 const level0CategoryName = level0Hash[productCategoryTitle];
132 document.getElementById('level0CategoryCrumb').innerHTML = level0CategoryName;
133 }
134 });
135
136 } else {
137 console.log('No level1Categories found in the response.');
138 }
139 })
140 .catch(error => {
141 console.error('Error:', error);
142 });
143
144 });
145
146 } else {
147 console.log('No productCategoryItems found in the response.');
148 }
149 })
150 .catch(error => {
151 console.error('Error:', error);
152 });
153 fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${cpProductId}/attachments?accountId=0&pageSize=50')
154 .then(response => response.json())
155 .then(data => {
156 const attachmentsMap = new Map();
157 const youTubeMap = new Map();
158 const productAttachmentItems = data.items.sort((a,b) => (a.title > b.title) ? 1 : ((b.title > a.title) ? -1 : 0));
159 if (productAttachmentItems && productAttachmentItems.length > 0) {
160 productAttachmentItems.forEach(function (productAttachmentItem) {
161 const productAttachmentURL = productAttachmentItem.src.split("?")[0];
162 const productAttachmentURLStripped = productAttachmentURL.replace("https://itronportal.lxc.liferay.com","");
163 const productAttachmentTitle = productAttachmentItem.title;
164 const productAttachmentCustomFields = productAttachmentItem.customFields
165 var productAttachmentContentType = "Article"
166 var displayAttachment = true;
167 productAttachmentCustomFields.forEach(function (productAttachmentCustomField) {
168 const productAttachmentCustomFieldName = productAttachmentCustomField.name
169 if(productAttachmentCustomFieldName == "ContentType"){
170 productAttachmentContentType = productAttachmentCustomField.customValue.data;
171 }
172 if(productAttachmentCustomFieldName == "LanguageKey"){
173 const productAttachmentLanguageKey = productAttachmentCustomField.customValue.data;
174 if((productAttachmentLanguageKey != null || productAttachmentLanguageKey != "") && productAttachmentLanguageKey != 'en_US'){
175 displayAttachment = false;
176 if(productAttachmentLanguageKey === '${locale}'){
177 displayAttachment = true;
178 }
179 }
180 }
181 });
182
183 if(productAttachmentContentType == "YouTube Video"){
184 const productAttachmentFileEntryId = productAttachmentItem.fileEntryId;
185 fetch('/o/headless-delivery/v1.0/documents/' + productAttachmentFileEntryId)
186 .then(response => response.json())
187 .then(data => {
188 const contentCustomFields = data.customFields;
189 if (contentCustomFields) {
190 let youTubeURL = "";
191 let youTubeCode = "";
192 let videoTitle = "";
193 contentCustomFields.forEach(function (contentCustomField) {
194 const contentName = contentCustomField.name
195
196 if(contentName == "VideoUrl"){
197 youTubeURL = contentCustomField.customValue.data;
198 const splitURL = youTubeURL.split("/");
199 const splitSize = splitURL.length;
200 youTubeCode = splitURL[splitSize-1];
201 }
202 if(contentName == "VideoTitle"){
203 videoTitle = contentCustomField.customValue.data;
204 }
205 });
206 let videoListItem = '<li class="product-catalog-list-item-detail"><button class="btn btn-link youtube-modal-link global-youtube-modal-trigger" data-bs-toggle="modal" data-bs-target="#global-youtube-modal" data-modal-url="'+youTubeCode+'" data-modal-title="Itron Optimizer Portfolio Overview"><div class="card-inner"><div class="card-image-div"><img width="279" height="157" loading="lazy" alt="" src="//img.youtube.com/vi/'+youTubeCode+'/maxresdefault.jpg" /><span class="modal-video-btn"><span class="visually-hidden">Play Video</span></span></div><div class="card-body"><h2 class="card-title">'+videoTitle+'</h2></div></div></button></li>';
207 let existingVideoList = document.getElementById('videoListSection').innerHTML;
208
209 document.getElementById('videoListSection').innerHTML = existingVideoList + videoListItem;
210 showHideVideoSection();
211 } else {
212 console.log('No contentCustomFields found in the response.');
213 }
214 })
215 .catch(error => {
216 console.error('Error:', error);
217 });
218 } else {
219 if(displayAttachment){
220 const listItem = "<li><a target='_blank' href="+ productAttachmentURLStripped+" class='list-link'>"+productAttachmentTitle+"</a></li>";
221 let mapValue = attachmentsMap.get(productAttachmentContentType);
222 let newMapValue = "";
223 if(mapValue){
224 let newMapValue = mapValue + listItem;
225 attachmentsMap.set(productAttachmentContentType, newMapValue);
226 } else {
227 attachmentsMap.set(productAttachmentContentType, listItem);
228 }
229 }
230 }
231 });
232
233 let totalList = '';
234 let sortedMap = new Map([...attachmentsMap.entries()].sort());
235 for (const [key, value] of sortedMap.entries()) {
236 let ulist = '<h2 class="list-title">' + key + '</h2><ul class="list">' + value + '</ul>'
237 totalList = totalList + ulist;
238 }
239
240 document.getElementById('resourcesDiv').innerHTML = totalList;
241 } else {
242 console.log('No attachments for selected product.');
243 }
244 })
245 .catch(error => {
246 console.error('Error:', error);
247 });
248
249
250 fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${cpProductId}/related-products')
251 .then(response => response.json())
252 .then(data => {
253 let relatedList = '<ul class="product-catalog-list-detail">';
254 const relatedProductItems = data.items;
255 const totalCount = data.totalCount;
256 let indexVal = 0;
257 if (relatedProductItems && relatedProductItems.length > 0) {
258 relatedProductItems.forEach(function (relatedProductItem) {
259 const relatedProductId = relatedProductItem.productId;
260 fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/' + relatedProductId)
261 .then(response => response.json())
262 .then(data => {
263 if (data) {
264 const relatedProductName = data.name;
265 const nullData = (relatedProductName == null)
266 indexVal = indexVal + 1;
267 if(!nullData){
268 const relatedProductImageURL = data.urlImage;
269 const relatedProductFriendlyURL = data.urls["en_US"];
270
271 if(data.urls['${locale}']){
272 const relatedProductFriendlyURL = data.urls['${locale}'];
273 }
274 let relatedListItem = '<li class="product-catalog-list-item-detail"><a class="product-catalog-list-item-link" href="/products/' + relatedProductFriendlyURL + '"><div class="card-inner"><div class="card-image-div"><img class="product-catalog-list-item-img" width="227" height="227" loading="lazy" src="'+relatedProductImageURL+'"alt="'+relatedProductName+'" /></div><div class="card-body"><h2 class="card-title card-title__xl">' +relatedProductName + '</h2></div></div></a></li>'
275 relatedList = relatedList + relatedListItem;
276 }
277 if(totalCount == indexVal){
278 relatedList = relatedList + '</ul>';
279 document.getElementById('relatedProductDiv').innerHTML = relatedList;
280 showHideRelatedProductsSection();
281 }
282 } else {
283 console.log('No related products found in the response. three');
284 }
285 })
286 .catch(error => {
287 console.error('Error:', error);
288 });
289 });
290
291
292 } else {
293 console.log('No relatedProductItems found in the response Two.');
294 }
295 })
296 .catch(error => {
297 console.error('Error:', error);
298 });
299
300 </script>