thymeleaf th:if - Cannot index into a null value - thymeleaf

thymeleaf index.xhtml -
EL1012E: Cannot index into a null value
<div class="mylist" th:each="row,rowStat : *{dataList}">
Folder: <span th:text="*{dataList[__${rowStat.index}__].folderName}" />
<div class="invalid-feedback"
th:if="${dataList[__${rowStat.index}__].folderName == appFolderName}">
Folder already exists. Please choose different folder name.
</div>
</div>
It is displaying the folderName but not validating th:if and appFolderName is a model attribute [dynamic value].

For the th:each="row,rowStat : *{dataList}" iterator, I would simplify that code to this:
th:each="row : ${dataList}"
You can think of this as being broadly equivalent to the following Java for-loop:
List<DataItem> dataList = ...; // assume this is populated with DataItems
for (DataItem dataItem : dataList) {
System.out.println(dataItem.getFolderName());
}
In the above for-loop, we do not need to access the list by index - and the same is also true for the Thymeleaf syntax.
Thymeleaf lets you access fields in an object without needing to refer to the getter method.
So, now that we have our row variable from th:each="row : ${dataList}", we can do this:
<div class="mylist" th:each="row,rowStat : *{dataList}">
Folder: <span th:text="${row.folderName}" />
<div class="invalid-feedback"
th:if="${row.folderName == appFolderName}">
Folder already exists. Please choose different folder name.
</div>
</div>
In the above code, you can see ${row.folderName} - which means Thymeleaf will invoke the getFolderName() method on the row object. This relies on your object using JavaBean naming standards for your getters.
You can enhance the Thymeleaf th:each processor by adding a second variable - which is what you do in your question: rowStat:
th:each="row,rowStat : ${dataList}"
This gives you access to extra information about the status of the Thymeleaf iterator - you can see a list of all these extra data values here.
These extra values are no needed in your case. But they can be useful in other situations - for example if you want to identify the first or last record in the list, or all even records, and so on.
Your example in the question uses the __${...}__ preprocessing syntax - which is very powerful and can be extremely useful. But again, it is not needed for your basic functionality.
Your example uses both ${...} and *{...} syntaxes to create Thymeleaf variables. It's important to understand the basic differences between them.
The difference is covered in the documentation describing the asterisk syntax:
the asterisk syntax evaluates expressions on selected objects rather than on the whole context. That is, as long as there is no selected object, the dollar and the asterisk syntaxes do exactly the same. And what is a selected object? The result of an expression using the th:object attribute.
The documentation has examples.
Finally, because you are using Spring (as per the tag in your question), then you are actually using Spring's dialect of Thymeleaf and SpEL - the Spring Expression Language.
This is broadly similar to the standard (OGNL) expression language used by the standard (non-Spring) Thymeleaf dialect - but it has several very useful enhancements.
One such enhancement is the safe navigation operator I mentioned in my comment:
${row?.folderName}
Here, the ? will immediately return null if row is null. Without this, you would get a null pointer exception when Thymeleaf attempted to invoke the getFolderName() method on a null row object.

Related

Thymeleaf - converting a set to an ordered list

We have a Set<String> that we are printing to the page in Thymeleaf:
<h5>Unique Students names</h5>
<ol>
<div th:each="uniqueName: ${course.uniqueStudentsNames}">
<li th:text="${uniqueName}"></li>
</div>
</ol>
Since it is a Set<String> there is no order of the items in the set.
We like to convert the non-ordered Set<String> to an ordred List<String> so we will be able to print it by ABC. Since it is a UI issue, we don't like the backend to change its data structure.
An option we thought about is to create such a list in the Model:
model.addAttribute("course", course);
model.addAttribute("orderedList", /* convert Set to ordred List */);
But this seems very strange that the Model needs to handle such a UI issue.
Is there a way to do it in Thymeleaf?
Instantiate it as a TreeSet (you are probably using hashset?) and you have a set with the ordering behaviour you want. Set<String> (Set<anything> really) guarantees there won't be repeated items, but it does not imply anything about the order/unorder.
On the other side, thymeleaf, as a template engine, won't do such things as adding ordering behaviour to a collection that does not implement it. The most it will do is allow you to access methods of the collection; in the case of set: set utility methods.
If you no matter what want to use a non ordered set, you'd have to use something like javascript to manipulate the items on the client to order them.
Use the Thymeleaf utility method to convert the object to a List:
${#lists.toList(object)}.
Then perform a utility operation to sort as needed from there, optionally using your own Comparator.
The current Thymeleaf source code shows that the toList() method will check if the target object is an instance of Iterable. In your case, Set implements the interface.
To reference the external link:
if (target instanceof Iterable<?>) {
final List<Object> elements = new ArrayList<Object>(10);
for (final Object element : (Iterable<?>)target) {
elements.add(element);
}
return elements;
}

How do you get the value of a data attribute within an event-based rule in Adobe Dynamic Tag Manager (DTM)?

I have an event-based rule configured to fire on click of an element with a specific class. On click, I would like to capture the value of a data attribute that exists. The DTM documentation says you can capture attribute values using this syntax:
%this.data-event%
or
%this.id%
Example HTML:
On click of links with the class "test", I would like to store the value of "event name" within an eVar. When I used the above syntax however, the syntax is converted to a string and in the Adobe server call as:
v25:%this.data-event%
What is the best way to dynamically grab the value of an attribute of an HTML element on click within DTM?
DTM documentation says you can do that, but in practice I too have found that it doesn't seem to work as advertised most of the time, and will instead populate it with a literal (un-eval'd) string like that.
So what I do instead is under Conditions > Rule Conditions I create a Custom condition. In the Custom condition, I add the following:
// example to get id
_satellite.setVar('this_id',this.id);
// example to get href
_satellite.setVar('this_href',this.href);
return true;
Basically I create on-the-fly data elements using javascript, and then return true (so the condition doesn't affect the rule from triggering).
Then I use %this_id%, %this_href%, etc. syntax from the data element I created, in the Adobe Analytics section variable fields.
The easist way to capture the values of a data attribute against an eVar or prop on the element clicked using DTM is to set the input as the following:
%this.getAttribute(data-attributename)%
For example, if there was a data attribute on an element of data-social-share-destination='facebook' simply input %this.getAttribute(data-social-share-destination)%. This will then capture the value of 'facebook'
More detail on data attributes can be found at http://www.digitalbalance.com.au/our-blog/event-based-tracking-using-html5-custom-data-attributes/
I found a solution. The best way to grab the value of an attribute on click is to use this syntax:
%this.getAttribute(data-title)%
The key is to not use quotes around the attribute name AND ensure the attribute has a value.
If the attribute is missing, the expression is not replaced by an empty string as one would normally expect from experience in other platforms, but instead will display the raw un-interpolated code.

Find pages with tag

In a Umbraco 7 solution, i have a Tags Content picker on all pages. Pages can with this, set tags on each page.
I then want to get alle pages, within the intire site, that has, lets say tag 111 (id, not name).
I have tried with:
var ids = Model.MacroParameters["tags"]; //the tags to show
CurrentPage.AncestorOrSelf(1).Descendants().Where(x => ids.Contains(x.tags.ToString()));
But that gives me the error:
Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type
Whats the correct way?
Solved it with;
Umbraco.Content(rootId).Descendants().Where("tags.Contains(#0)", ids);
You have a few options, depending on whether you prefer a dynamic or strongly typed view model.
Strongly Typed API
Umbraco.TypedContentAtRoot().Descendants().Where(x => x.tags.Contains(ids));
Dynamic API
Umbraco.ContentAtRoot().Descendants().Where("tags.Contains(#0)", ids);
Please note that the Contains statement may give you inconsistent results, as the tags property seems to be returning a comma separated list. In that case you can try splitting the string or install the Core Property Value Converters package and get the tags as IEnumerable<IPublishedContent>
Always try to avoid using Descendants, especially on the root node.
To get the tags for a property:
ApplicationContext.Current.Services.TagService.GetTagsForProperty(Model.Content.Id, "propertyname")
To find content with a specific tag:
ApplicationContext.Current.Services.TagService.GetTaggedContentByTag("tag")

Big Commerce Custom Fields

I'm planning on allowing a client to provide a couple codes for each product that I'll need to reference with Javascript on the product pages.
Basically my plan was to use the Big Commerce's 'custom fields' to do so, but I'm having trouble spitting out the custom fields onto the product pages. I've been looking all over for some type of GLOBAL variable that allows me to reference custom fields, but I'm coming up dry. I would think there would be some type of GLOBAL array with all the custom fields in it, or a way to reference them by name directly.
Am I blind, or is there just no way to do this directly in the BC template file?
Thanks.
In Bigcommerce the custom fields can generally be found within the ProductOtherDetails.html Panel which contains a Snippet named ProductCustomFieldItem.html. This snippet has the markup for each custom field that the system outputs.
Inside of the ProductCustomFieldItem.html Snippet are the two codes you are looking for: %%GLOBAL_CustomFieldName%% and %%GLOBAL_CustomFieldValue%%.
I ran into this as well - given that it's quite a long time later, I'm supposing there's no better answer - a decent amount of searching turned up nothing useful as it seems all you can do is output the full set of custom fields as a set of divs.
So, I output them into a div which was hidden:
<div id="fpd-custom-fields" style="display:none;">
%%SNIPPET_ProductCustomFields%%
</div>
and then set up a javascript function to get the value based on the name:
function getCustomFieldValue(label)
{
var value = '';
$('#fpd-custom-fields div.Label').each(function()
{
if($(this).text().toLowerCase() == (label.toLowerCase() + ':'))
{
value = $('div.Value', $(this).parent()).text().trim();
}
});
return value;
}
Doesn't feel quite right as it's not a very clean solution, but was the best I could come up with unfortunately!

How to check for that map is containg the particular key using thymleaf

I am using the thymeleaf for presentation layer, so from controller i am sending the map containing key and list of values like this Map<Long,List<Long>> to x.html. But how i check whether the key contained in map in x.html using thymeleaf so please tell me the way to check it
i tried this way but not successfull
<span th:if="${#maps.containsKey(myMap, myStringValue)}">YEAH!</span>
There is such method you described since Thymeleaf version 1.0 which works for me as expected (see documentation). Maybe your Map key isn't String value or myStringValue isn't String.
Did you tried use constant String as key?
<span th:if="${#maps.containsKey(myMap, 'valueOfMyStringValue')}">YEAH!</span>
Or call Map#containsKey method directly on Map?
<span th:if="${myMap.containsKey('valueOfMyStringValue')}">YEAH!</span>

Resources