cancel
Showing results for 
Search instead for 
Did you mean: 

How to use UDF values in FSM HTML report?

DAcker
Contributor
0 Kudos

Hello experts, 
I am struggling with the syntax to embed UDF values in FSM HTML Checkout report.

I analyzed the data.js file and the UDF value are displayed in this format:

DAcker_0-1708703030511.png

However, how should the syntax in the template.js look like? I tested different versions (e.g. udf.MILEAGE_NOTES) but they are not working... 

Can someone please help?

DAcker_1-1708703109530.png

Thanks a lot!
Best regards,
Deborah

@krzysztof_szalach Do you have a tip?

Accepted Solutions (1)

Accepted Solutions (1)

0 Kudos

Okay, so it took me a while, but @DAcker I've found some sort of generic solution.

In template.js add for getMileagesTableDescriptor new column:

{
headerKey: 'Mileage_Udf_Notes_L', udfValue: 'MILEAGE_NOTES',
}

then in template.html in genericTableTemplate for <td> entry:

{{else if udfValue}}
{{#each data-row.udfValues}}
{{#compare meta.name "==" column.udfValue}}
{{value}}
{{/compare}}
{{/each}}

 Nów, how it works:

- when generating generic table it will check if attribute "udfValue" in column is set. If yes, it iterates through all udfValues for this row and checks if meta.name matches the attribute value. This way you can use it in any table. 

Full code:
genericTableTemplate

<script id="genericTableTemplate" type="text/x-handlebars-template">
{{#if dataGroups}}
{{#gt dataGroups.length 0}}
<section class="generic-table">
<h2>{{ localize titleKey }}</h2>
<table>
<thead>
<tr>
{{#each columns}}
<th>{{ localize headerKey }}</th>
{{/each}}
</tr>
</thead>

<tbody>
{{#each dataGroups as |data-group|}}
{{#each . as |data-row|}}
<tr>
{{#each ../../columns as |column|}}
<td>
{{#if propertyName}}
{{formatValue (get propertyName data-row) column }}

{{else if aggregate}}
{{formatValue (aggregateValues data-row column) column }}

{{else if udfValue}}
{{#each data-row.udfValues}}
{{#compare meta.name "==" column.udfValue}}
{{value}}
{{/compare}}
{{/each}}

{{/if}}
</td>
{{/each}}
</tr>
{{/each}}
<tr>
{{#each ../columns as |column|}}
<td>
{{#if column.footer}}
<div class="column-footer">
{{#if column.footer.formatFooter}}
{{formatValue (callFunction column.footer.valueProvider ../this/this) column}}
{{else}}
{{callFunction column.footer.valueProvider ../this/this}}
{{/if}}
</div>
{{/if}}
</td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
</section>
{{/gt}}
{{/if}}
</script>

getMileagesTableDescriptor

const getMileagesTableDescriptor = (mileages, localization) => ({
titleKey: 'EntityPluralName_Mileage',
columns: [
{
headerKey: 'Mileage_StartDate', propertyName: 'travelStartDateTime', templateId: 'dateTimeTemplate', templateOptions: {formatString: 'L'},
footer: {
valueProvider: _ => localization.total,
formatFooter: false
}
},
{headerKey: 'ExpenseMaterialMileage_Detail_CreatePerson_L', aggregate: ['createPerson.firstName', 'createPerson.lastName'], operator: 'concat'},
{headerKey: 'Mileage_Detail_From_L', propertyName: 'source'},
{headerKey: 'Mileage_Detail_To_L', propertyName: 'destination'},
{
headerKey: 'Mileage_Detail_Distance_L', propertyName: 'distance', templateId: 'numberTemplate',
footer: {
valueProvider: mileages => mileages.map(mileage => mileage.distance).reduce((acc, current) => acc + current, 0),
formatFooter: true
}
},
{
headerKey: 'Mileage_Detail_TravelDuration_L', aggregate: ['travelEndDateTime', 'travelStartDateTime'], operator: 'difference', templateId: 'durationTemplate',
footer: {
valueProvider: mileages => mileages.filter(mileage => mileage.travelStartDateTime && mileage.travelEndDateTime).map(mileage => mileage.travelEndDateTime - mileage.travelStartDateTime).reduce((acc, current) => acc + current, 0),
formatFooter: true
}
},
{headerKey: 'Mileage_Udf_Notes_L', udfValue: 'MILEAGE_NOTES'}
],
dataGroups: mileages && mileages.length ? [mileages] : []
});

 

Although for me column header is not displayed even I've added key to localization file, but I will leave it up to you.

DAcker
Contributor
0 Kudos
Thanks so much!!! Yes, this is working.
DAcker
Contributor
0 Kudos
Thanks so much!!! Yes, this is working. The column header I already figured out myself. If you add the description of the column header in each translation file, it works. So in my case I add an entry for the /translations/de.js and /translations.en.js. ("Mileage_Udf_Notes_L":"Notizen" this makes the column header description)
DAcker
Contributor
0 Kudos
What language is this inline coding? If I want to build up knowledge about... To understand the code rudimentary is not problem. But to code this by myself - no I would never have thought of that myself
It's Celigo - https://docs.celigo.com/hc/en-us/articles/360039227731-Handlebars-overview here is very nice explanation about the handlebars 🙂
DAcker
Contributor
0 Kudos
I would like to add a certain UDF value into the aggregate expression. Is this possible? In Jasper it was very easy to put multiple value in one data cell.. But it seems that it is not or very difficult to integrate a udf value into the aggregate. Do you have a code example or tipp? (code line in getMileagesTableDescriptor that is of course not working: {headerKey: 'ExpenseMaterialMileage_Detail_CreatePerson_L', aggregate: ['createPerson.firstName', 'createPerson.lastName', udfValue: 'Subdienstleister_Mitarbeiter'], operator: 'concat'},)
0 Kudos
This will be more tricky. What I can think of is that you should modify aggregateValues custom function (in handlebarsSapHelpers.js) in a way that in "prop => Handlebars.helpers.get(prop, context)" it checks first if prop is udfValue (for example if it starts with) and then in such case, it should do something similar to what I did in previous response - so it gets the udfValues and iterates to find that specific one. But this time, this will be written in pure JS, not in Celigo syntax. Or even more generic approach would be to create overridden method of "get", that would check for udfValue. This way it would work across all the report without any additional effort.
0 Kudos
Okay, so this is example way of solving this for aggregateValues function:
Handlebars.registerHelper('aggregateValues', (context, columnDefinition) => { if (columnDefinition.operator === undefined) { return; } if (columnDefinition.operator == 'concat') { return columnDefinition.aggregate.map(prop => { if (prop.startsWith('udfValue.')) { let udfValue = prop.split('.')[1]; return context.udfValues.find(udf => udf.meta.name === udfValue).value; } else { return Handlebars.helpers.get(prop, context); } }).reduce((acc, current) => acc + ' ' + current, ''); } if (columnDefinition.operator == 'difference') { return columnDefinition.aggregate.map(prop => Handlebars.helpers.get(prop, context)).filter(val => val).reduce((acc, current) => acc - current); } });
How it works - if the prop value starts with udfValue.* then it search for udfValues with the name of it and returns its value. But keep in mind that it's just a POC - you need to add for example null checks or cover other edge cases (like missing udfValues etc). In template.js you can then use it as simple as "{headerKey: 'Mileage_Udf_Notes_L', aggregate: ['createPerson.firstName', 'udfValue.MILEAGE_NOTES'], operator: 'concat'}". Hope you can now improve it - for example with creating some generic function that takes care of it.
DAcker
Contributor
0 Kudos
Thanks so much! You made my day 🙂 I added the exception handling for udfValue null and tested it, it works. 🙂
0 Kudos
Nice, I'm glad that I was able to help you 🙂
DAcker
Contributor
0 Kudos
Hello Krzysztof, I add the following as expection handling for UDF null values.

Answers (4)

Answers (4)

DAcker
Contributor
0 Kudos

As addition for the community - here the code if there are multiple UDF value and you have to loop over it:

if (columnDefinition.operator == 'concat') {
return columnDefinition.aggregate.map(prop => {
//Custom, UDF handling (line 25-33 custom)
if (prop.startsWith('udfValue.')) {
let udfValue = prop.split('.')[1];
for (const element of context.udfValues){
if (element.meta.name === udfValue){
return element.value;
}
} return "";

} else {
return Handlebars.helpers.get(prop, context);
}
}).reduce((acc, current) => acc + ' ' + current, '');
}

 

DAcker
Contributor
0 Kudos

Hello @Krzysztof_szalac,

this is unfortunately not working. I added a custom method to the template.js (see screenshot).

DAcker_3-1709039891817.png

In the webbrowser development mode I can see the correct UdfValue in the console (see screenshot). However, the column is not displaying the correct value - only [object]. If I just to test maintain udfValues[0].value it is not showing anything (empty). 

DAcker_0-1709039792873.png

However, mileage details with the other SAP standard fields are implemented in an existing loop in html (each dataGroups...) and with the getMileagesTableDescriptor constant in template.js, see screenshot. I tried to add another parameter for the const getMileagesTableDescriptor (line 166), not working. Inside the 'propertyName' to call the method doesnt work.

template.js

DAcker_1-1709039822283.png

loop, template.html

DAcker_2-1709039875273.png

So the simple request to read from a JSON a certain value is possible and works (see browser console log), but within in the context of the SAP HTML report for checkout in FSM, is is complicated... 

Do you have another tip?

Best regards, Deborah

0 Kudos
Just to make sure - it doesn't work on all mobile clients, like Android, Windows and iOS?
DAcker
Contributor
0 Kudos

no it is not working on any.. not on the Webbrowser for testing with the laptop ... it is not working with the iOS app...

DAcker
Contributor
0 Kudos

deleted

0 Kudos

@DAcker although I'm not an expert in html reports, what I can suggest - as you already noticed udf values are stored as json object with some well understandable template. It's not possible to access it directly as you want, but rather you should create custom js function to retrieve the needed value from that json. SOmething like this:

krzysztof_szalach_0-1708768737237.png

 

DAcker
Contributor
0 Kudos
Hello @Krzysztof_szalac,