Formatting columns in GUI Table

Hi;

In a field group, I can easily set a field mask on a particular field to act as an input and formatting mask (e.g. for a north american telephone number I enter ###-###-####).

For a GUI table such as is used in browser screens or master/detail views, there is no mask property on columns. Instead there is a formatter property which is supposed to allow me (I think) to specify a class that implements com.haulmont.cuba.gui.components.Formatter.

My problem is that I can’t find a list of classes that implement com.haulmont.cuba.gui.components.Formatter, or ways to specify a mask similar to what I do for fields in a field group.

Moreover, it seems inconsistent that there are these two different approaches for formatting field data. Why not just have a mask attribute on a table’s column just like there is in a field group?

Thanks in advance,

Brian.

Create a formatter class in any package of the module where your screen is defined, for example:


package com.company.sales.gui.customer;

import com.haulmont.cuba.gui.components.Formatter;

public class PhoneFormatter implements Formatter<String> {

    @Override
    public String format(String value) {
        if (value != null && value.length() == 9)
            return value.substring(0, 3) + "-" + value.substring(3, 6) + "-" + value.substring(6);
        return value;
    }
}

Then specify the fully-qualified name of the class for the table column. In XML descriptor, it will look as follows:


<columns>
    <column id="name"/>
    <column id="email"/>
    <column id="phone">
        <formatter class="com.company.sales.gui.customer.PhoneFormatter"/>
    </column>
</columns>

You are right that specifying a mask right on the column would be more convenient, especially if the table is editable. Or better the mask should be on the entity attribute itself, to save from scattering the mask through the UI code. We’ll consider this requirement after implementing this feature: https://youtrack.cuba-platform.com/issue/PL-8123

Hi Brian,

Do you need the mask for editing values in table in-place?

Hi;

No, at this point I’m just trying to get the read-only table for the browser screen to format the same way as the read/write fields in the edit screen.

Ideally sure it would be nice to be able to enable in place editing in the browser view as well - but that’s not my problem right now and in any case, unless I’ve missed something that base behaviour of in-place editing doesn’t seem to be supported in the standard screen generation.

What about the numeric entity atributes (int, long, double, decimal)?

They are mapped to a single TextField. If I would like to apply a mask, for instance, in a currency field, what I do?
Should I create a special implementation with addCustomField() together with the custom=“true” attribute of the field element? What about the conversion from the text presentation form (front end) to numeric field value (back end)?

Formatter is suitable only for read-only UI elements: labels, table cells, etc. Two-way conversion for editable fields is done in datatypes.

For example, to display currency values, you can define a custom datatype similar to described here. The datatype class can be as follows:


package com.company.sample.entity;

import com.google.common.base.Strings;
import com.haulmont.chile.core.datatypes.Datatype;
import com.haulmont.chile.core.datatypes.impl.NumberDatatype;
import org.dom4j.Element;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.Locale;

public class CurrencyDatatype extends NumberDatatype implements Datatype<BigDecimal> {

    public static final String NAME = "currency";

    private static final String PATTERN = "$#,##0.00";

    public CurrencyDatatype(Element element) {
        super(element);
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public Class getJavaClass() {
        return BigDecimal.class;
    }

    @Nonnull
    @Override
    public String format(@Nullable Object value) {
        if (value == null)
            return "";

        DecimalFormat format = new DecimalFormat(PATTERN);
        return format.format(value);
    }

    @Nonnull
    @Override
    public String format(@Nullable Object value, Locale locale) {
        return format(value);
    }

    @Nullable
    @Override
    public BigDecimal parse(@Nullable String value) throws ParseException {
        if (Strings.isNullOrEmpty(value))
            return null;

        DecimalFormat format = new DecimalFormat(PATTERN);
        format.setParseBigDecimal(true);
        BigDecimal result;
        try {
            result = (BigDecimal) format.parse(value);
        } catch (ParseException e) {
            try {
                result = new BigDecimal(value);
            } catch (Exception e1) {
                throw new ParseException("Error parsing " + value, 0);
            }
        }
        return result;
    }

    @Nullable
    @Override
    public BigDecimal parse(@Nullable String value, Locale locale) throws ParseException {
        return parse(value);
    }

    @Nullable
    @Override
    public BigDecimal read(ResultSet resultSet, int index) throws SQLException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void write(PreparedStatement statement, int index, @Nullable Object value) throws SQLException {
        throw new UnsupportedOperationException();
    }

    @Override
    public int getSqlType() {
        return Types.NUMERIC;
    }

    @Override
    public String toString() {
        return NAME;
    }
}

An example project is available here.

Wow! That’s what I need! I will try your direction.
Thank you for the quick reply.

:ticket: See the following issue in our bug tracker:

https://youtrack.cuba-platform.com/issue/PL-8221