Search code examples
javacssspringthymeleaf

Java Spring Thymeleaf `th:each` inside `<style>` tag


Active languages of my web app are stored inside the Java List and that list is available to Thymeleaf.
Outside <style> tag this is easy and working like a charm:

<button th:each="lang : ${ activeLanguages }" th:data-lang="${ lang.languageTag }" onclick="setLang( this );">
    <span th:text="${ lang.localName }"></span>
</button>

Is it possible to issue th:each inside <style> tag to avoid hardcoding active languages in CSS classes?

This is how I have it so far:

<style>
    .Flag.IE { background-image:url("/IE.svg"); }
    .Flag.HR { background-image:url("/HR.svg"); }
    .Flag.EN { background-image:url("/EN.svg"); }
</style>

This is what I want (ok, not excactly this but solution in that fashion):

<style>
---> th:each="lang : ${ activeLanguages }" <---
    .Flag.${ lang.iso2Country.toUpperCase() } {
        background-image:url("/${ lang.iso2Country.toUpperCase() }.svg");
    }
---> th:each END <---
</style>

EDIT: I have working solution but it's extremelly ugly:

<style th:each="lang : ${ webData.activeLanguages }">
    .Flag.[[ ${ lang.iso2Country.toUpperCase() } ]]::before {
        background-image:url("/[[ ${ lang.iso2Country.toUpperCase()} ]]");
    }
</style>

Solution

  • Whether you consider this to be less ugly or not is probably subjective...

    You can use CSS inlining by declaring your style tag like this:

    <style th:inline="css">
       ...
    </style>
    

    And then you can combine this with textual inline iteration, similar to what is shown in this section of the documentation:

    <style th:inline="css">
        [# th:each="lang : ${activeLanguages}"]
            [#th:block th:utext="|.Flag.${lang.languageTag} \{|" /]
            [#th:block th:utext="|background-image: url('${lang.languageTag}.svg');|" /]
            [#th:block th:utext="|}|" /]
        [/]
    </style>
    

    This produces HTML like the following:

    <style>            
        .Flag.XY {
            background-image: url('XY.svg');
        }
        .Flag.AB {
            background-image: url('AB.svg');
        }        
    </style>
    

    WARNING - this requires you to use th:utext - which carries security risks if it handles user-provided UNTRUSTED data. But if you have your own pre-defined list of languages (users cannot "provide" these values they can only choose one of them) then the text used for the Thymeleaf variables should be safe. But it is up to you to ensure this is the case.


    UPDATE - more concise and safer

    There is a more concise way to do this:

    <style th:inline="css">
        [# th:each="lang : ${activeLanguages}"]
            .Flag.[[${lang.languageTag}]] {
                background-image: url("/[[${lang.languageTag}]].svg");
            }
        [/]
    </style>
    

    Here, the inline text really is just plain text - and the Thymeleaf variables are inserted in between [[ and ]].

    The results:

    <style>
        .Flag.XY {
            background-image: url("/XY.svg");
        }
        .Flag.AB {
            background-image: url("/AB.svg");
        }
    </style>
    

    What is also good about this is that we can use [[...]] instead of [(...)] - which means any potentially dangerous content in ${...} will be safely escaped by the Thymeleaf renderer.

    So my earlier warning does not apply to this approach.