DisplayFields

DisplayFields is a templating API that enables integration adapters to provide nested sets of dynamic, internationalised, customisable fields (label-value pairs) to Caplin’s front end applications for display.

DisplayFields are used to display information in:

  • Trade confirmations

  • Blotter summaries

  • Post-trade history

DisplayFields in context

DisplayFields are sent to the front end in the DisplayFields field of some trade model messages and blotter records.

Message builders that support DisplayFields auto-generate a default value for the DisplayFields field that you are free to override.

See below a simplified example of DisplayFields being sent to the front end as part of an RFS spot trade confirmation trade model message using the TicketSpotTradeConfirmation message builder.

Example TradeConfirmationProvider
public class TradeConfirmationProvider implements CachedMessageProvider<TradeSubjectInfo> {

  private SubjectMessagePublisher<TradeSubjectInfo> publisher;

  @Override
  public void initialise(final SubjectMessagePublisher<TradeSubjectInfo> publisher) {
    this.publisher = publisher;
  }

  @Override
  public void onRequest(TradeSubjectInfo subject) {
    TicketSpotTradeConfirmation tradeConfirmation =
      TicketSpotTradeConfirmation.newBuilder()
        .setDisplayFields(
          new DisplayFields()
            .displayFields(List.of(
              new DisplayField()
                .label("Account")
                .template("{{account}}")
                .arguments(Map.ofEntries(
                  Map.entry("account", new DisplayFieldArgument("User 1", DisplayFieldArgument.TypeEnum.TEXT))
                ))
            ))
        )
        .build();
    publisher.publishMessage(subject, tradeConfirmation);
  }
}

Anatomy of a DisplayField

Each DisplayField is made up of a label, a template, and a set of arguments. The front end combines these attributes to render a label-value pair. Common combinations can be created using the DefaultDisplayFields helpers:

Labels, templates, and arguments

Labels, templates, and arguments are the core parts of each individual DisplayField that inform the front end on how to render the text label and value of the field.

An example DisplayField with multiple arguments
int rate = 1.125
int amount = 10000 // 10,000
int total = rate * amount;

new DisplayField()
  .label("Rate x Amount") (1)
  .template("{{rate}} x {{amount}} = {{total}}") (2)
  .arguments(Map.ofEntries( (3)
    Map.entry("rate", new DisplayFieldArgument(rate, DisplayFieldArgument.TypeEnum.NUMBER))
    Map.entry("amount", new DisplayFieldArgument(amount, DisplayFieldArgument.TypeEnum.NUMBER))
    Map.entry("total", new DisplayFieldArgument(total, DisplayFieldArgument.TypeEnum.NUMBER))
  ))
1 The label to be shown in the front end.
2 A template string that becomes the value for the field. The keys surrounded by double curly brackets (e.g. {{rate}}) are replaced with the values of the corresponding arguments (C).
3 A map of arguments to be interpolated into the template (B).

When template interpolation and formatting has been applied in the front end, the example above would look something like this:

Rate x Amount

1.125 x 10,000 = 11,250

Translation tokens

To ensure that DisplayFields are translatable along with other content in the front end, DisplayField labels and arguments support the use of i18n tokens.

The front end automatically recognises i18n tokens in the label of a DisplayField, but arguments with i18n tokens must use the argument type TOKEN for an i18n token to be recognised.

For a full list of argument types, see Argument types.

An example DisplayField using i18n tokens
new DisplayField()
  .label("display-field-label.trade-status") (1)
  .template("{{status}}") (2)
  .arguments(Map.ofEntries( (3)
    Map.entry("status", new DisplayFieldArgument("display-field-value.trade-status-pending", DisplayFieldArgument.TypeEnum.TOKEN)) (3)
  ))
1 A DisplayField label using the i18n token display-field-label.trade-status.
2 The template uses the value of the status argument below (3).
3 A single argument status is used. The value of the argument is display-field-value.trade-status-pending, and it uses the argument type TOKEN.

If the application had i18n translations for both English and Spanish, the example above might display as:

Trade Status

Pending

Or alternatively:

Estado Comercial

Pendiente

Note that you must provide i18n translations for each of the languages and translation tokens you wish to support.

Argument types

Each DisplayField be of one of several data types. The types inform the front end how it should format and display the fields. The following types are available via DisplayFieldArgument.TypeEnum.

TEXT

A plaintext value that should be displayed unmodified. TOKEN is preferred for text values that need to be translatable.

TOKEN

An i18n translation token for text translations.

NUMBER

A numerical value, such as 1.0, 100, or 1,000. The front end should apply formatting appropriate to the user’s locale.

DATE

An ISO date (YYYY-MM-DD). The front end should apply formatting appropriate to the user’s locale.

TIME

An ISO date-time (YYYY-MM-DD[T]HH:mm:ss.SSS) or time (HH:mm:ss.SSS) with or without milliseconds. Input must be in UTC. Milliseconds will be displayed only if the input format contained milliseconds. The front end will convert the value to local time and display the time part only, formatted according to the user’s locale settings.

DATETIME

An ISO date-time (YYYY-MM-DD[T]HH:mm:ss.SSS) with or without milliseconds. Input must be in UTC. Milliseconds will be displayed only if the input format contained milliseconds. Frontend will convert to local time and display the date and time formatted according to the user’s locale settings.

RATE

A rate value that may be formatted with adapter-provided formatting attributes.

POINTS

An amount value that may be formatted with attributes provided by an adapter.

AMOUNT

An amount value that may be formatted with attributes provided by an adapter.

Grouping DisplayFields into sections

DisplayFields can be grouped into sections by nesting them under other DisplayFields that act as wrappers. Headers can be added to these sections using the template property to interpolate a header label.

An example of DisplayFields grouped into two sections
new DisplayFields()
  .displayFields(List.of(
    new DisplayFields() (1)
      .template("{{header}}") (2)
      .arguments(Map.ofEntries( (3)
        Map.entry("header", new DisplayFieldArgument("User Info", DisplayFieldArgument.TypeEnum.TEXT))
      ))
      .displayFields(List.of( (4)
        new DisplayField()
          .label("Account")
          .template("{{account}}")
          .arguments(Map.ofEntries(
            Map.entry("account", new DisplayFieldArgument("User 1", DisplayFieldArgument.TypeEnum.TEXT))
          ))
      )),
    new DisplayFields() (5)
      .template("{{header}}") (6)
      .arguments(Map.ofEntries( (7)
        Map.entry("header", new DisplayFieldArgument("Trade Details", DisplayFieldArgument.TypeEnum.TEXT))
      ))
      .displayFields(List.of( (8)
        new DisplayField()
          .label("Settlement Date")
          .template("{{settlement}}")
          .arguments(Map.ofEntries(
            Map.entry("settlement", new DisplayFieldArgument("2024-04-01", DisplayFieldArgument.TypeEnum.DATE))
          ))
      ))
  ));
1 The beginning of the first section.
2 The template uses the header argument. This will act as the header for the first section.
3 The header argument is defined, to be used in the template (2).
4 The fields for this section are added.
5 The beginning of the second section.
6 The template uses the header argument. This will act as the header for the second section.
7 The header argument is defined, to be used in the template (6).
8 The fields for this section are added.

When template interpolation and formatting has been applied in the front end, the example above would look something like this:

User Info
Account

User 1

Trade Details
Settlement Date

01/04/2024

DisplayFields in table format

Some messages support DisplayFields in table format.

You can use the DefaultTableFields helpers to help you create tables for these messages.

See below a simplified version of a DisplayFields table being added to a PostTradeHistory message. For a full example, see ExamplePostTradeHistoryProvider in the FX Example Adapter.

Simplified ExamplePostTradeHistoryProvider with a DisplayFields table
public class ExamplePostTradeHistoryProvider implements CachedObjectProvider<PostTradeHistory, TradeSubjectInfo> {

  @Override
  public void onRequest(final TradeSubjectInfo subjectInfo, final SubjectConsumer<PostTradeHistory> publishObject) {
    final Table postTradeHistoryTable = DefaultTableFields.createPostTradeHistoryTable();
    DefaultTableFields.addPostTradeHistoryRow(
        postTradeHistoryTable,
        LocalDate.now(),
        new FormattedAmount(new BigDecimal("1000"), "GBP", 2),
        LocalDate.now().plusDays(7),
        Tenor._1W.toString(),
        new FormattedAmount(new BigDecimal("0"), "GBP", 2),
        new PostTradeHistoryAction(TypeEnum.ROLL_FORWARD, "0987654321")
    );
    final PostTradeHistory postTradeHistory = new PostTradeHistory();
    postTradeHistory.setTable(postTradeHistoryTable);

    publishObject.onNext(postTradeHistory);
  }
}

Anatomy of a DisplayFields table

DisplayFields tables are created using Table, TableCell, and TableRow objects.

TableCell and TableRow objects use templates and arguments in the same way as regular DisplayFields. See Labels, templates, and arguments for details.

new Table() (1)
  .headers(List.of( (2)
    new TableCell()
      .template("{{header}}")
      .arguments(
        Map.ofEntries(
          Map.entry("header", new DisplayFieldArgument("Header 1", DisplayFieldArgument.TypeEnum.TEXT))
        )
      ),
    new TableCell()
      .template("{{header}}")
      .arguments(
        Map.ofEntries(
          Map.entry("header", new DisplayFieldArgument("Header 2", DisplayFieldArgument.TypeEnum.TEXT))
        )
      )
  ))
  .rows(List.of( (3)
    new TableRow()
      .cells(List.of( (4)
        new TableCell()
          .template("{data}}")
          .arguments(Map.ofEntries(
            Map.entry("data", new DisplayFieldArgument("Value 1", DisplayFieldArgument.TypeEnum.TEXT))
          )),
        new TableCell()
          .template("{data}}")
          .arguments(Map.ofEntries(
            Map.entry("data", new DisplayFieldArgument("Value 2", DisplayFieldArgument.TypeEnum.TEXT))
          ))
      ))
  ));
1 Create a Table object.
2 Using Table.headers, provide a list of TableCell objects. These become headers in the table.
3 Using Table.rows, provide a list of TableRow objects. These become rows in the table.
4 Each row has a list of TableCell objects, populating each of the columns in that row.

The example above creates a table with the following structure:

Header 1 Header 2

Value 1

Value 2