Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to control significant digits, ONLY when necessary, in a Thymeleaf template?

When using the th:text attribute to evaluate and render a numeric field, Thymeleaf displays the full number of digits available. For example, this:

<span th:text="${user.averageScore}"/>

... might render on the browser screen:

107.54896

I would like to display this amount rounded to no more than two decimal places. From the Thymeleaf documentation, this:

<span th:text="${#numbers.formatDecimal(user.averageScore, 0, 2)}"/>

... changes the output to this:

107.55

However, is there a way to make this more flexible... in cases where the value has FEWER than than two decimal places? I only want to remove decimal places, to get down to two. I never want to ADD decimal places, to get up to two. If the field above has a value of 107, then it would render as:

107.00

How can I make Thymeleaf format numbers for two decimal places, or less... rather than just two decimal places, no matter what?

like image 256
Steve Perkins Avatar asked Feb 03 '14 18:02

Steve Perkins


2 Answers

Hi you could try something like this.

<span th:text="${user.averageScore} % 1 == 0? ${user.averageScore} :${#numbers.formatDecimal(user.averageScore, 0, 2)}"/>
like image 182
Eduardo Quintana Avatar answered Oct 13 '22 00:10

Eduardo Quintana


There's no easy way to do this in Thymeleaf 2.1, but there are two hard ways.

Hard way #1: Fork Thymeleaf and add a format method to class org.thymeleaf.expression.Numbers that does what you want (adding a method that takes a DecimalFormat pattern would seem like a logical extension)

Hard way #2: Add a dialect to Thymeleaf that provides a new expression class that does the formatting you want. My example below is based on using Spring with Thymeleaf to register a dialect to format numbers representing hours.

Step 1: Register the dialect:

@Component
public class ThymeLeafSetup implements InitializingBean {

@Autowired
private SpringTemplateEngine templateEngine;

@Override
public void afterPropertiesSet() throws Exception {
    templateEngine.addDialect(new HoursDialect());
}
}

Step #2: Create the dialect class (formatting logic delegated to TimeUtils static method) - based on Java8TimeDialect:

public class HoursDialect extends AbstractDialect implements IExpressionEnhancingDialect {
public static class Hours {
    public String format(BigDecimal hours) {
        return TimeUtils.formatHours(hours);
    }
}

@Override
public String getPrefix() {
    // No attribute or tag processors, so we don't need a prefix at all and
    // we can return whichever value.
    return "hours";
}

@Override
public boolean isLenient() {
    return false;
}

@Override
public Map<String, Object> getAdditionalExpressionObjects(IProcessingContext processingContext) {
    return Collections.singletonMap("hours", new Hours());
}
}

Step #3: Create formatting logic based on DecimalFormat

public class TimeUtils {

public static String formatHours(BigDecimal hours) {
    DecimalFormat format = new DecimalFormat("#0.##");
    format.setGroupingUsed(true);
    format.setGroupingSize(3);
    return format.format(hours);
}
}

Step #4: Tests of formatting logic

@Test
public void formatDecimalWilLFormatAsExpected() {
    verifyHourNumberFormatsAsExpected("1.5", "1.5");
    verifyHourNumberFormatsAsExpected("1.25", "1.25");
    verifyHourNumberFormatsAsExpected("123.0", "123");
    verifyHourNumberFormatsAsExpected("1230", "1,230");
}

void verifyHourNumberFormatsAsExpected(String number, String expected) {
    assertThat(TimeUtils.formatHours(new BigDecimal(number))).isEqualTo(expected);
}
like image 37
Basil Vandegriend Avatar answered Oct 12 '22 23:10

Basil Vandegriend