Class Column<T>

java.lang.Object
limn.components.table.Column<T>
Type Parameters:
T - the row type

public final class Column<T> extends Object
One column of a Table: a title, how a row becomes a cell, how rows compare on it, and how wide it is.

A column is built once by the application and handed to the table. Three factories cover the everyday cases — text(limn.i18n.I18nString, java.util.function.Function<T, java.lang.String>), numeric(limn.i18n.I18nString, java.util.function.ToDoubleFunction<T>) and of(limn.i18n.I18nString, java.util.function.Function<T, V>, java.util.function.Function<? super V, java.lang.String>) with a formatter — and widget is the seam for a cell that is a control rather than a value: a switch, a button, an icon. Everything after the factory is a fluent setter.

The language a cell is written in is the table's own: every formatter a column runs, a cell's, a footer's and the text a sort falls back to, runs with the table's locale in scope, so NumberFormats, DateFormats and anything else that reads I18n.locale() write a cell in the table's language even when the table reads its rows again outside a pass.

The UI thread. Every setter runs on the UI thread, as every widget's does, and throws IllegalStateException on any other thread and before a backend is running. The table reads its columns on that thread while it lays out, sorts and paints, and a column changes under a table that already holds it (a width, a visibility), so a column written from another thread is a race with that reading, one nothing would report. Build the columns where the table is built, on the UI thread.

Widths. A column has a preferred width, a minimum, and a weight. Layout gives every column its preferred width and then shares what is left of the viewport among the weighted ones, as Expanded shares a row's leftover; a table whose preferred widths exceed the viewport scrolls sideways rather than crushing anything below its minimum. A width the user dragged is held here, so it survives a Table.refresh(), and is held as dragged: the column takes no share of the leftover until resetWidth(), so the divider stays where the pointer left it and the other weighted columns take up the difference.

Sorting. A header click sorts through compare(T, T, java.text.Collator, java.util.Locale): a comparator the application named, else the values themselves — numbers numerically, strings through the language's collator, anything else comparable by itself — else the formatted text. Numbers compare exactly, so a long id past 2^53 or a BigDecimal amount keeps its place, and a column whose values differ in kind sorts its numbers first, then its text, then the rest, each among its own kind. A widget column has no value and is not sortable unless given a comparator.

The footer. A column may put something in the table's summary row, which is pinned under the rows the way the header is pinned over them and appears as soon as one column has something for it: a fixed text (footer(I18nString)), a value computed from all the rows and formatted as the cells are (footer(Function)), or one of the aggregates a numeric column offers (footerSum(), footerAverage(), footerMin(), footerMax()) and the count any column does (footerCount()). The table computes it on Table.setRows(java.util.List<T>) and Table.refresh(), never per frame.

  • Method Details

    • text

      public static <T> Column<T> text(I18nString title, Function<T,@Nullable String> value)
      A column of text.
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      value - the text of a row's cell; null draws an empty cell
      Returns:
      the column
    • text

      public static <T> Column<T> text(String title, Function<T,@Nullable String> value)
      text(I18nString, Function) with a literal title, for a header no translation covers.
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      value - the text of a row's cell
      Returns:
      the column
    • numeric

      public static <T> Column<T> numeric(I18nString title, ToDoubleFunction<T> value)
      A column of numbers: aligned to the reading end, formatted and localized as the charts format an axis, and sorted numerically.
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      value - a row's number
      Returns:
      the column
    • numeric

      public static <T> Column<T> numeric(I18nString title, ToDoubleFunction<T> value, DoubleFunction<String> format)
      A column of numbers written by format, which is any of NumberFormats or the application's own DoubleFunction<String>: NumberFormats.prefix("R$ ") for a fixed prefix, decimals(2), unit(" kg"), percent(1), compact(). The footer's aggregates are written by the same format, so a sum of prices is a price.
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      value - a row's number
      format - the number as text, reading the locale in effect when it formats
      Returns:
      the column
    • numeric

      public static <T> Column<T> numeric(String title, ToDoubleFunction<T> value, DoubleFunction<String> format)
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      value - a row's number
      format - the number as text
      Returns:
      the column
    • currency

      public static <T> Column<T> currency(I18nString title, ToDoubleFunction<T> value, Currency currency)
      A column of amounts in one currency, written as the language in effect writes money (NumberFormats.currency(Currency)): the symbol, its side, the decimals and the separators are the locale's, and a footer sum is money too.
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      value - a row's amount, in currency
      currency - the currency the amounts are in
      Returns:
      the column
    • currency

      public static <T> Column<T> currency(String title, ToDoubleFunction<T> value, Currency currency)
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      value - a row's amount
      currency - the currency the amounts are in
      Returns:
      the column
    • numeric

      public static <T> Column<T> numeric(String title, ToDoubleFunction<T> value)
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      value - a row's number
      Returns:
      the column
    • of

      public static <T, V> Column<T> of(I18nString title, Function<T,@Nullable V> value, Function<? super V,String> format)
      A column over any value, with a format that turns it into text and reads the language in effect as it does, as DateFormats and NumberFormats do: a date through DateFormats.date(FormatStyle.SHORT), an enum through its own words. The table's locale is in scope while it formats.
      Type Parameters:
      T - the row type
      V - the value type
      Parameters:
      title - what the header says
      value - a row's value; null draws an empty cell and sorts first
      format - the value as text; never asked for null
      Returns:
      the column
    • of

      public static <T, V> Column<T> of(String title, Function<T,@Nullable V> value, Function<? super V,String> format)
      Type Parameters:
      T - the row type
      V - the value type
      Parameters:
      title - what the header says
      value - a row's value
      format - the value as text
      Returns:
      the column
    • of

      public static <T, V> Column<T> of(I18nString title, Function<T,@Nullable V> value, BiFunction<V,Locale,String> format)
      A column over any value, with a formatter that is handed the locale to write it in: I18n.numberLocale() under the table's language, the locale the toolkit writes numbers in. It names the numbering system the toolkit writes (-u-nu-) wherever the platform would otherwise write other digits, so String.format(locale, "%.1f MB", mb) writes 1234.5 as 1234.5 MB under a bare ar and ١٢٣٤٫٥ MB under ar-EG, in the digits of the numeric columns beside it; the bare locale wrote Arabic-Indic digits under ar, where the toolkit writes Latin ones. The language is the table's: a month's name or a word looked up under this locale is in that language. A date wants of(I18nString, Function, Function) with DateFormats instead, because the platform writes a date in ASCII digits under any locale.
      Type Parameters:
      T - the row type
      V - the value type
      Parameters:
      title - what the header says
      value - a row's value; null draws an empty cell and sorts first
      format - the value as text under the locale handed to it; never asked for null
      Returns:
      the column
    • of

      public static <T, V> Column<T> of(String title, Function<T,@Nullable V> value, BiFunction<V,Locale,String> format)
      Type Parameters:
      T - the row type
      V - the value type
      Parameters:
      title - what the header says
      value - a row's value
      format - the value as text under the locale handed to it
      Returns:
      the column
    • widget

      public static <T> Column<T> widget(I18nString title, Function<T,Widget<?>> factory)
      A column whose cells are widgets: one is built per realized row and released with it, so the factory is called again for a row that scrolled out and back. Not sortable unless comparator names how.

      The factory is handed the record the table holds for the row as the cell is built, and a control made here acts on that object. A row that Table.setRows(java.util.List<T>) or Table.refresh() keeps, because a popup hangs from one of its widget cells or the keyboard is in one, keeps the widgets built then; where the list now holds a new object equal to the old, as after a server's push, they go on acting on the old one until the row is built again. A control that writes to its record, a combo box or a check box that sets a field, takes widget(I18nString, BiFunction), whose writes land in the record the table holds.

      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      factory - the widget for a row's cell
      Returns:
      the column
    • widget

      public static <T> Column<T> widget(String title, Function<T,Widget<?>> factory)
      widget(I18nString, Function) with a literal title.
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      factory - the widget for a row's cell
      Returns:
      the column
    • widget

      public static <T> Column<T> widget(I18nString title, BiFunction<T,Supplier<T>,Widget<?>> factory)
      A column whose cells are widgets that act on their row's record as the table holds it when they act: the factory is handed the row it builds the cell for and a supplier of that row's record, which a control reads in its handler. The supplier answers the object the factory was handed until a Table.setRows(java.util.List<T>) or a Table.refresh() keeps the cell's row (because a popup hangs from one of its widget cells, such as a combo box's open list, or the keyboard is in one) while the list now holds another object for that record, as a server's push of new objects equal to the old does; from then on it answers that object. So a choice made in a list that stayed open through the push lands in the record the table holds, and the row shows it when it is built again, where a control holding on to the object it was built with would have written to one the table no longer holds.
      
       Column.<Order>widget("Status", (order, current) -> {
           ComboBox<Status> box = new ComboBox<>(List.of(Status.values()));
           box.setSelectedItem(order.status());
           box.onSelect(() -> current.get().setStatus(box.selectedItem()));
           return box;
       });
       

      Ask it on the UI thread, where a control's handlers run. Once the row has left the table it answers the last record it was bound to. In every other respect this is widget(I18nString, Function).

      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      factory - the widget for a row's cell, from the row and the supplier of its record now
      Returns:
      the column
    • widget

      public static <T> Column<T> widget(String title, BiFunction<T,Supplier<T>,Widget<?>> factory)
      widget(I18nString, BiFunction) with a literal title.
      Type Parameters:
      T - the row type
      Parameters:
      title - what the header says
      factory - the widget for a row's cell, from the row and the supplier of its record now
      Returns:
      the column
    • title

      public I18nString title()
      Returns:
      what the header says
    • isWidgetColumn

      public boolean isWidgetColumn()
      Returns:
      whether this column's cells are widgets rather than values
    • comparator

      public Column<T> comparator(@Nullable Comparator<T> comparator)
      Names how rows compare on this column, replacing the comparison derived from the value. A table sorted on this column sorts again at once, as Table.setSort(limn.components.table.Column<T>, limn.components.table.SortOrder) would: the focus cell, the range anchor and a widget cell holding the keyboard go with their records, and the selection stays with its own. A table whose Table.onSortRequest orders the rows itself keeps them as they are. A comparator that leaves the column unable to compare (null on a widget column) takes the table's sort on it away. UI thread only.
      Parameters:
      comparator - the order; null restores the derived one
      Returns:
      this column
    • sortable

      public Column<T> sortable(boolean sortable)
      Sets whether a header click sorts on this column (default on). A column that cannot compare — a widget column with no comparator — is not sortable whatever this says. A table sorted on this column when it stops being sortable goes back to the model's order at once, as Table.setSort(limn.components.table.Column<T>, limn.components.table.SortOrder) with SortOrder.NONE would, the focus cell and the range anchor going with their records. UI thread only.
      Parameters:
      sortable - whether a click sorts
      Returns:
      this column
    • isSortable

      public boolean isSortable()
      Returns:
      whether a header click sorts on this column
    • align

      public Column<T> align(Column.Alignment alignment)
      Sets where a cell's text sits across the column (default Column.Alignment.START, or Column.Alignment.END for a numeric(limn.i18n.I18nString, java.util.function.ToDoubleFunction<T>) column). UI thread only.
      Parameters:
      alignment - where the text sits
      Returns:
      this column
    • alignment

      public Column.Alignment alignment()
      Returns:
      where a cell's text sits across the column
    • width

      public Column<T> width(float points)
      Sets the width layout starts from, in points (default 120). A width the user dragged replaces it until resetWidth(). UI thread only.
      Parameters:
      points - the preferred width; at least the minimum
      Returns:
      this column
    • minWidth

      public Column<T> minWidth(float points)
      Sets the width below which neither layout nor a drag takes this column (default 40). UI thread only.
      Parameters:
      points - the floor
      Returns:
      this column
    • weight

      public Column<T> weight(float weight)
      Sets this column's share of the viewport width left over once every column has its preferred width (default 0: keep the preferred width). A weight of 1 on one column makes it take all of the leftover; equal weights on two share it equally. A column whose width the user dragged takes no share until resetWidth(). UI thread only.
      Parameters:
      weight - the share; zero or more
      Returns:
      this column
    • visible

      public Column<T> visible(boolean visible)
      Shows or hides the column. A hidden column takes no space, is not published to a screen reader, builds no widget for its cells when it is a widget column, and keeps its widths for when it returns. The change is picked up by the table's next layout, which releases a hidden widget column's widgets and builds a newly shown one's and leaves every other cell's widget alone, a focused one included; the table asks for that layout itself. Table.refresh() asks for one too, and as every refresh does rebuilds the realized rows except the one whose widget cell holds the keyboard, which stays with its record while the list still holds it (and hands the keyboard to the table when its own column is the one hidden, or its record is gone). A focus cell, or the header's cursor, on the hidden column moves to the nearest shown column. UI thread only.
      Parameters:
      visible - whether the column is shown
      Returns:
      this column
    • isVisible

      public boolean isVisible()
      Returns:
      whether the column is shown
    • footer

      public Column<T> footer(@Nullable I18nString text)
      Puts a fixed text in this column's footer cell: "Total", say, beside the sums. UI thread only.
      Parameters:
      text - what the footer cell says; null removes the footer cell
      Returns:
      this column
    • footer

      public Column<T> footer(String text)
      footer(I18nString) with a literal text.
      Parameters:
      text - what the footer cell says
      Returns:
      this column
    • footer

      public Column<T> footer(@Nullable Function<List<T>,?> summary)
      Computes this column's footer cell from all the rows, and formats the result as the column's cells are formatted: a text(limn.i18n.I18nString, java.util.function.Function<T, java.lang.String>) column expects a string back, a numeric(limn.i18n.I18nString, java.util.function.ToDoubleFunction<T>) one any Number, an of(limn.i18n.I18nString, java.util.function.Function<T, V>, java.util.function.Function<? super V, java.lang.String>) column a value of its own type. UI thread only.
      Parameters:
      summary - the footer value from the rows; null removes the footer cell
      Returns:
      this column
    • footerSum

      public Column<T> footerSum()
      Sums the column's numbers in the footer. UI thread only.
      Returns:
      this column
      Throws:
      IllegalStateException - on a column without a numeric value, from the first Table.refresh() that computes it
    • footerAverage

      public Column<T> footerAverage()
      Averages the column's numbers in the footer; empty when there are no rows. UI thread only.
      Returns:
      this column
    • footerMin

      public Column<T> footerMin()
      The smallest of the column's numbers in the footer; empty when there are no rows. UI thread only.
      Returns:
      this column
    • footerMax

      public Column<T> footerMax()
      The largest of the column's numbers in the footer; empty when there are no rows. UI thread only.
      Returns:
      this column
    • footerCount

      public Column<T> footerCount()
      The row count in the footer, localized as a number; any column may carry it. UI thread only.
      Returns:
      this column
    • clearFooter

      public Column<T> clearFooter()
      Takes the footer cell away, as footer(null) would: that call does not compile, because null fits more than one of the overloads. UI thread only.
      Returns:
      this column
    • hasFooter

      public boolean hasFooter()
      Returns:
      whether this column puts something in the footer
    • resetWidth

      public void resetWidth()
      Forgets a width the user dragged, so layout starts from the preferred width again and the column's weight takes its share of the leftover again. UI thread only.
    • width

      public float width()
      Returns:
      the width layout starts from: the one the user dragged, else the preferred one
    • minWidth

      public float minWidth()
      Returns:
      the floor under this column's width
    • weight

      public float weight()
      Returns:
      this column's share of the leftover width