テンプレート処理中にエラーが発生しました。
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, false, 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 "CityEdge":"Portfolio Type",
130 "Gas Edge":"Portfolio Type",
131 "Grid Edge Intelligence":"Portfolio Type",
132 "Smart Water Solutions":"Portfolio Type"
133 };
134
135 const level0CategoryName = level0Hash[productCategoryTitle];
136 document.getElementById('level0CategoryCrumb').innerHTML = level0CategoryName;
137 }
138 });
139
140 } else {
141 console.log('No level1Categories found in the response.');
142 }
143 })
144 .catch(error => {
145 console.error('Error:', error);
146 });
147
148 });
149
150 } else {
151 console.log('No productCategoryItems found in the response.');
152 }
153 })
154 .catch(error => {
155 console.error('Error:', error);
156 });
157 fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${cpProductId}/attachments?accountId=0&pageSize=50')
158 .then(response => response.json())
159 .then(data => {
160 const attachmentsMap = new Map();
161 const youTubeMap = new Map();
162 const productAttachmentItems = data.items.sort((a,b) => (a.title > b.title) ? 1 : ((b.title > a.title) ? -1 : 0));
163 if (productAttachmentItems && productAttachmentItems.length > 0) {
164 productAttachmentItems.forEach(function (productAttachmentItem) {
165 const productAttachmentURL = productAttachmentItem.src.split("?")[0];
166 const productAttachmentURLStripped = productAttachmentURL.replace("https://itronportal.lxc.liferay.com","");
167 const productAttachmentTitle = productAttachmentItem.title;
168 const productAttachmentCustomFields = productAttachmentItem.customFields
169 var productAttachmentContentType = "Article"
170 var displayAttachment = true;
171 productAttachmentCustomFields.forEach(function (productAttachmentCustomField) {
172 const productAttachmentCustomFieldName = productAttachmentCustomField.name
173 if(productAttachmentCustomFieldName == "ContentType"){
174 productAttachmentContentType = productAttachmentCustomField.customValue.data;
175 }
176 if(productAttachmentCustomFieldName == "LanguageKey"){
177 const productAttachmentLanguageKey = productAttachmentCustomField.customValue.data;
178 if((productAttachmentLanguageKey != null || productAttachmentLanguageKey != "") && productAttachmentLanguageKey != 'en_US'){
179 displayAttachment = false;
180 if(productAttachmentLanguageKey === '${locale}'){
181 displayAttachment = true;
182 }
183 }
184 }
185 });
186
187 if(productAttachmentContentType == "YouTube Video"){
188 const productAttachmentFileEntryId = productAttachmentItem.fileEntryId;
189 fetch('/o/headless-delivery/v1.0/documents/' + productAttachmentFileEntryId)
190 .then(response => response.json())
191 .then(data => {
192 const contentCustomFields = data.customFields;
193 if (contentCustomFields) {
194 let youTubeURL = "";
195 let youTubeCode = "";
196 let videoTitle = "";
197 contentCustomFields.forEach(function (contentCustomField) {
198 const contentName = contentCustomField.name
199
200 if(contentName == "VideoUrl"){
201 youTubeURL = contentCustomField.customValue.data;
202 const splitURL = youTubeURL.split("/");
203 const splitSize = splitURL.length;
204 youTubeCode = splitURL[splitSize-1];
205 }
206 if(contentName == "VideoTitle"){
207 videoTitle = contentCustomField.customValue.data;
208 }
209 });
210 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>';
211 let existingVideoList = document.getElementById('videoListSection').innerHTML;
212
213 document.getElementById('videoListSection').innerHTML = existingVideoList + videoListItem;
214 showHideVideoSection();
215 } else {
216 console.log('No contentCustomFields found in the response.');
217 }
218 })
219 .catch(error => {
220 console.error('Error:', error);
221 });
222 } else {
223 if(displayAttachment){
224 const listItem = "<li><a target='_blank' href="+ productAttachmentURLStripped+" class='list-link'>"+productAttachmentTitle+"</a></li>";
225 let mapValue = attachmentsMap.get(productAttachmentContentType);
226 let newMapValue = "";
227 if(mapValue){
228 let newMapValue = mapValue + listItem;
229 attachmentsMap.set(productAttachmentContentType, newMapValue);
230 } else {
231 attachmentsMap.set(productAttachmentContentType, listItem);
232 }
233 }
234 }
235 });
236
237 let totalList = '';
238 let sortedMap = new Map([...attachmentsMap.entries()].sort());
239 for (const [key, value] of sortedMap.entries()) {
240 let ulist = '<h2 class="list-title">' + key + '</h2><ul class="list">' + value + '</ul>'
241 totalList = totalList + ulist;
242 }
243
244 document.getElementById('resourcesDiv').innerHTML = totalList;
245 } else {
246 console.log('No attachments for selected product.');
247 }
248 })
249 .catch(error => {
250 console.error('Error:', error);
251 });
252
253
254fetch(`/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/${cpProductId}/related-products`)
255 .then(response => response.json())
256 .then(data => {
257 let relatedList = '<ul class="product-catalog-list-detail">';
258 const relatedProductItems = data.items;
259 const totalCount = data.totalCount;
260 let fetchedProducts = []; // Array to store products with names
261 let fetchCount = 0; // Counter for completed fetch requests
262
263 if (relatedProductItems && relatedProductItems.length > 0) {
264 relatedProductItems.forEach((relatedProductItem) => {
265 const relatedProductId = relatedProductItem.productId;
266
267 fetch('/o/headless-commerce-delivery-catalog/v1.0/channels/${channelId}/products/' + relatedProductId)
268 .then((response) => response.json())
269 .then((productData) => {
270 if (productData) {
271 const relatedProductName = productData.name;
272 const relatedProductImageURL = productData.urlImage;
273 const relatedProductFriendlyURL = productData.urls['en_US'] || '';
274
275 // Push product details to the fetchedProducts array
276 fetchedProducts.push({
277 name: relatedProductName,
278 imageURL: relatedProductImageURL,
279 friendlyURL: relatedProductFriendlyURL,
280 });
281 }
282 })
283 .catch((error) => {
284 console.error("Error fetching product details:", error);
285 })
286 .finally(() => {
287 fetchCount++;
288 if (fetchCount === relatedProductItems.length) {
289 // Sort products by name after all fetches are completed
290 fetchedProducts.sort((a, b) => a.name.localeCompare(b.name));
291
292 // Generate the list items
293 fetchedProducts.forEach((product) => {
294 if (product.name) {
295 relatedList += '<li class="product-catalog-list-item-detail"><a class="product-catalog-list-item-link" href="/products/' + product.friendlyURL + '"><div class="card-inner"><div class="card-image-div"><img class="product-catalog-list-item-img" width="227" height="227" loading="lazy" src="'+product.imageURL+'"alt="'+product.name+'" /></div><div class="card-body"><h2 class="card-title card-title__xl">' +product.name + '</h2></div></div></a></li>';
296 }
297 });
298
299 // Close the list and update the DOM
300 relatedList += '</ul>';
301 document.getElementById('relatedProductDiv').innerHTML = relatedList;
302 showHideRelatedProductsSection();
303 }
304 });
305 });
306 } else {
307 console.log("No relatedProductItems found in the response.");
308 }
309 })
310 .catch((error) => {
311 console.error("Error fetching related products:", error);
312 });
313
314
315 </script>
Region Selector
Select a region and country for the best experience.