5. List windows
A list window shows a table's records in a listbox with a toolbar above it. In Foundation 6 this is the Fnd_Out module: one shared form, columns built at run time from an entity selection, and a View Options palette through which the user chooses, orders, sizes and styles the columns and saves the result as a layout. The developer decides which fields are offered, which are hidden, and what the list looks like before anyone has touched it.
This replaces the classic output form of the 5.4 manual, a per-table Output form with Header, Detail and Footer lines inheriting Fnd_Tlbr_Toolbar. That form still works where a host has one and Fnd_Out is not switched on; nothing here requires you to convert it. New hosts should not build one.
What you need
- The
Fnd_Outform, a project form named exactly that, once per host and shared by every table. Foundation cannot carry it, because a component's forms cannot be the host's. Copy the folderHooksAndHosts/Fnd_Out/into yourProject/Sources/Forms/. It holds one collection listbox namedFnd_Out_ListBoxwith no columns, bound toForm.listData, plus two invisible buttons for Cmd-W and Cmd-Option-W, and a three-line form method:
Fnd_Gen_FormMethod
Fnd_Tlbr_FormMethod
Fnd_Out_FormMethod
The listbox's object method is Fnd_Out_ListBoxMethod. The form inherits Fnd_Tlbr_Toolbar, which gives it the toolbar and status line.
Fnd_Out_Active(True)inFnd_Hook_IO_DisplayTable. The switch is per process and per opening, so the hook sets it every time. Without it Foundation looks for the classicOutputform.- An
Inputform for the table (chapter 6), so that double-click opens a record.
Then Fnd_IO_DisplayTable(->[Invoices]) opens the list: from the navigation palette, the Open Table dialog, a related-records command or your own code. Foundation supplies the rest: toolbar, status line, Find and Sort, the Window menu, the View Options palette, saved widths and order, and the two hooks below.
The standard columns
With nothing else said, Foundation picks a few of the table's own fields in field order, skipping the primary key. Say what you want instead in the same hook, per table:
Case of
: (Fnd_Gen_CurrentTable=(->[Invoices]))
Fnd_Out_Active(True)
Fnd_Out_AddField("InvoiceNumber"; 0; "Inv. No")
Fnd_Out_AddField("InvoiceDate"; 0; "Date")
Fnd_Out_AddField("contact.reversedFullName()"; 0; "Contact")
Fnd_Out_AddField("Total"; 0; ""; 0; <>Gen_MoneyFormat_t)
End case
Fnd_Out_AddField takes a field pointer, a path through the relations in ORDA form (contact.Company, invoice.contact.City), a formula on the entity (contact.reversedFullName(), an exposed function of your entity class), or an object with fo_field, fo_title, fo_type, fo_format, fo_hAlign, fo_vAlign, fo_width, fo_minWidth, fo_maxWidth, fo_style and fo_visible. Width 0 means "estimate from the type": text 100, numbers 65 right-aligned, dates and times 65, booleans 19. This set is the layout called Standard; "Reset to Standard" in the palette returns the user to it.
Columns are formula columns over This, the row's entity, so a path or a function of the entity class is a column like any field. Headers sort nothing by themselves: Sort is on the Select menu, as before.
What the user may add
The palette offers the table's own fields and, block by block, the fields of every related table up to five relations deep. Narrow that in the hook:
| Call | Effect |
|---|---|
Fnd_Out_HideField("Password"), Fnd_Out_HideField("@.Password") | never offer this path; @ wildcards |
Fnd_Out_OfferField("Subtotal") | the first call for a table switches to "offer only what is listed" |
Fnd_Out_OfferFormula("This.fullName()"; "Contact"; Is text) | offer a formula under Other Columns |
Fnd_Out_MaxRelationDepth(1) | how far to walk the relations; 0 is the table's own fields only |
Fnd_Out_AllowCustomColumns(False) | the user may not add formulas of their own to this list |
None of these touches a column you added with Fnd_Out_AddField. The same rules can be written once, declaratively, in the host's Resources/Fnd_Out/<Table>.json: offer.hide, offer.only, formulas, appearance, allowCustomColumns, and builtInLayouts. FPS has three such files to copy from.
Appearance for every list
Fnd_Out_Default(tag; value) sets a programmed default for every list in the session; one argument reads it back. Tags are the constants Fnd_Out_FontName, Fnd_Out_TextSize, Fnd_Out_RowHeight, Fnd_Out_RowPixels, Fnd_Out_VAlign, Fnd_Out_Fill, Fnd_Out_AltFill, Fnd_Out_RelationDepth, Fnd_Out_MaxColumns, Fnd_Out_AllowCustom and Fnd_Out_AllowPublish. Set them in Fnd_Hook_Shell_Setup or at the top of Fnd_Hook_IO_DisplayTable, as FPS sets Fnd_Out_Default("allowPublish"; True).
What the user sees
Select > View Options… opens one floating palette that follows whichever list window is in front.
- Appearance: a Layout menu, the list's font (Default is the application's list font), text size (with a custom size), row height in lines or pixels, vertical alignment, background and alternate colours. Save makes the font, size, height and colours the default for every list on this machine; Reset returns this list to the programmed default.
- Columns: a tick list in blocks, the table's own fields first, then one block per related table, then Custom Columns. Ticking inserts the column; dragging a header in the window reorders; dragging a border resizes, and the last column fills the window. Reset to Standard discards every change.
- Advanced widens the palette for the highlighted column: title, font (the column's cells only; its header keeps the list's font), horizontal and vertical alignment, bold, italic, format, and Use Default to drop the overrides; below it, Row Styles (next section) and the formula box for custom columns, when the developer allows them.
Everything the user does is saved when the window closes, in <4D application folder>/<DatabaseName>/Fnd_Out/<Table>.json on that machine, and comes back next time. Hold Option and Shift while a list opens to ignore the saved order and widths once.
Layouts
A layout is a named set of visible columns, order, widths, font, text size, row height and row-style rules. The Layout menu lists them and Manage Layouts… renames, deletes and sets defaults.
| Kind | Who makes it | Where it lives | Shown as |
|---|---|---|---|
| Standard | the developer's AddField calls | not stored | Standard |
| User | any user, Save As… | the user's Fnd_Out/<Table>.json | Name |
| Shared | a user with publish rights, Publish… | the data file, as a shared preference | Name (Shared) |
| Built-in | the developer, in an interpreted host | the host's Resources/Fnd_Out/<Table>.json | Name (Built-in) |
Publish rights are the Designer, the Administrator and the Administration group, or whatever Fnd_Out_Default("allowPublish"; …) says. A layout marked default opens for every new user of that table, built-in first, then shared.
Developer mode
An interpreted host is developer mode; nothing switches it on. The palette then shows an offer tick per candidate column, which writes offer.hide and offer.only into the table's Resources/Fnd_Out/<Table>.json for you, and the Layout menu gains Save as Application Layout… and Delete Application Layout, which write builtInLayouts there. Compiled, those two refuse to run and hidden candidates are not even listed. Ship the JSON files with your application.
Row styles
Two sources, the first that applies wins: a layout's row-style rules, made in the Advanced panel from a condition written in the formula editor and a style (bold, italic, underline, text and fill colour); and the hook Fnd_Hook_Out_RowStyle, evaluated for every row with This as the entity:
#DECLARE()->$style_o : Object
$style_o:=New object
Case of
: (Fnd_Gen_CurrentTable=(->[Invoices]))
If (This.Total>500)
$style_o.fontWeight:="bold"
End if
: (Fnd_Gen_CurrentTable=(->[Products]))
If (This.ListPrice>500)
$style_o.fontWeight:="bold"
$style_o.stroke:="red"
End if
End case
Return any of fontWeight, fontStyle, textDecoration, stroke, fill; an empty object leaves the row alone. It is a 4D meta-info expression, so keep it cheap: it runs for every visible row on every redraw.
The context menu
A right-click (Control-click on the Mac) in a list calls Fnd_Hook_Out_ContextMenu(->table; $selected_es; $clicked_e; $row_i) with the table, the highlighted records as an entity selection, the entity under the pointer (Null over empty space) and the clicked row number. The hook builds and shows the menu itself. The generated template, which FPS uses as it stands, offers New, Edit Record(s) up to a limit, Delete, Show Subset and Omit Subset, calling Fnd_Rec_NewRecord, Fnd_Out_EditRecord($row_i), Fnd_Out_EditSelection($selected_es), Fnd_Rec_DeleteUserSet, Fnd_Rec_ShowSubset and Fnd_Rec_OmitSubset. Add your own items after them; a commented example in the template archives the selection through ORDA.
The search box
A list can show a search box in its toolbar band. It searches the whole table, not the current selection, as the user types; Enter searches at once, and emptying the box shows every record.
Add a subform object named exactly Fnd_Out_Search, with detail form Fnd_Out_Search, to your Fnd_Out form once, drawn anywhere in the toolbar band at about 192 × 26. Foundation places it when the list opens and on every resize, and hides it when the window is too narrow.
Then declare what each table searches, in Fnd_Hook_Out_SearchFields, which Foundation generates the first time Fnd_Out runs. FPS does this for three tables:
Case of
: ($table_ptr=(->[Contacts]))
Fnd_Out_AddSearchField(->[Contacts]LastName; "contains")
Fnd_Out_AddSearchField(->[Contacts]FirstName; "contains")
Fnd_Out_AddSearchField(->[Contacts]Company; "contains")
: ($table_ptr=(->[Invoices]))
Fnd_Out_AddSearchField(->[Invoices]InvoiceNumber; ""; "Inv. #")
Fnd_Out_AddSearchField(->[Invoices]InvoiceDate)
Fnd_Out_AddSearchField("contact.LastName"; ""; "Customer")
: ($table_ptr=(->[Products]))
Fnd_Out_AddSearchField(->[Products]ProductNumber)
Fnd_Out_AddSearchField(->[Products]ProductName; "keyword")
Fnd_Out_SearchHint("Number or name"; "Product numbers match from the start; names match whole words."+Char(Carriage return)+"Searches: "+Fnd_Out_SearchHelp)
End case
The empty box shows the fields it searches in grey, as many as fit, and hovering over it lists them all.
Each field has a title, which is the name used in the hint and the tip. It is the field's virtual field name by default, and Fnd_VS_SetFieldTitle changes it everywhere. The optional third parameter changes it for the search box only. A path with no virtual field name shows its last segment. The second parameter may be "", which means begins.
The object form is for the tech-savvy. It takes the same three things as keys, using the constants fo_field, fo_operator and fo_title, and a key outranks the matching parameter:
Fnd_Out_AddSearchField(New object(fo_field; "contact.LastName"; fo_title; "Customer"))
To replace the hint or the tip altogether, call Fnd_Out_SearchHint(hint; tooltip) after the declarations. The hint is shown as written. The tip may run to several lines. An empty text keeps Foundation's own. Fnd_Out_SearchHelp returns the generated list of field names, so the tip can include it, as the Products branch does.
A table with no declared fields has no box. A field is a pointer, or a text path for one reached through relations. A text field takes an operator: begins (the default), contains, keyword or exact. A number field is searched only when the text is a number. A date field is searched only when the text is a date, which Fnd_Out_SearchDate reads: a full date, today, yesterday, a bare day of this month, or a day and month in the system's order.
For anything the declared fields cannot express, Fnd_Hook_Out_Search is called first and may return an entity selection to display; returning Null falls back to the declared fields.
Working with the list from your code
| Call | Does |
|---|---|
Fnd_Out_Update | redraw the list after you changed records, and refresh the status line |
Fnd_Out_SynchroniseSelection | make the classic current selection match what the list shows, before a classic command such as a print or export |
Fnd_Out_EditRecord($row_i), Fnd_Out_EditSelection($selected_es) | open a row, or every highlighted record in turn |
Fnd_Out_Info("highlighted"), ("inSelection"), ("state") | counts and state for your own status text |
Fnd_IO_MultiWindow(False) | show the record in the list's own window rather than a new one (FPS Products) |
Fnd_IO_AddMultipleRecords(True) | after Save, offer another new record until Cancel (FPS Products) |
The selection the list shows is an entity selection in Form.listData; Find, Sort, Show Subset and the rest replace it and the list follows. Foundation's classic current selection is kept in step at the moments it matters, which is what Fnd_Out_SynchroniseSelection is for when you need it yourself.
Known limits in the beta
- Clicking a column header does not sort; use Sort.
- View Options has no keyboard shortcut yet.
- Computed attributes of an entity class are not offered as candidates; add them with
Fnd_Out_AddFieldorFnd_Out_OfferFormula. - Layouts are per machine user, not per Foundation user.