4.3 Displaying Data in Tables (Page Builder & Element Config)
BetterForms provides powerful options for displaying tabular data, from simple list views to advanced data tables. This section covers when to use each approach and how to implement them effectively.
Two Main Approaches to Displaying Data
1. List Views (listrows
)
listrows
)Simple multi-row data entry forms
Add/remove rows functionality
Good for data input scenarios
Limited sorting and filtering
2. Data Tables (tables2
)
tables2
)Advanced data display with sorting, filtering, pagination
Read-only data presentation
Complex interactions and custom formatting
Based on Vue-Tables-2 library
When to Use Each Approach
Use List Views When:
Use Data Tables When:
Users need to add/edit rows
Displaying read-only data
Simple data entry forms
Large datasets requiring search/filter
Contact lists, address books
Reports and analytics
Product catalogs (editable)
User management interfaces
Max 20-30 rows
Hundreds or thousands of rows
List Views (listrows
) Implementation
listrows
) ImplementationWhere to Add List Views in the IDE
Open your page in the BetterForms IDE
Navigate to Page Builder tab
Search for "listrows" in the snippets
Copy and paste into your page schema
Basic List View Structure
{
"type": "listrows",
"label": "Contact List",
"model": "contacts",
"min": 1,
"max": 10,
"styleClasses": "col-md-12",
"schema": {
"fields": [
{
"type": "input",
"inputType": "text",
"label": "First Name",
"model": "firstName",
"styleClasses": "col-md-4"
},
{
"type": "input",
"inputType": "text",
"label": "Last Name",
"model": "lastName",
"styleClasses": "col-md-4"
},
{
"type": "input",
"inputType": "email",
"label": "Email",
"model": "email",
"styleClasses": "col-md-4"
}
]
}
}
Data Model for List Views
Your page's data model should include an array for the list data:
{
"contacts": [
{
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]"
},
{
"firstName": "Jane",
"lastName": "Smith",
"email": "[email protected]"
}
]
}
Advanced List View Example
{
"type": "listrows",
"label": "Product Inventory",
"model": "products",
"min": 0,
"max": 50,
"styleClasses": "col-md-12",
"schema": {
"fields": [
{
"type": "input",
"inputType": "text",
"label": "Product Name",
"model": "name",
"styleClasses": "col-md-3",
"validator": "string"
},
{
"type": "input",
"inputType": "number",
"label": "Price",
"model": "price",
"styleClasses": "col-md-2",
"validator": "number"
},
{
"type": "input",
"inputType": "number",
"label": "Quantity",
"model": "quantity",
"styleClasses": "col-md-2",
"validator": "number"
},
{
"type": "select",
"label": "Category",
"model": "category",
"styleClasses": "col-md-3",
"values": [
{ "value": "electronics", "label": "Electronics" },
{ "value": "clothing", "label": "Clothing" },
{ "value": "books", "label": "Books" }
]
},
{
"type": "checkbox",
"label": "Active",
"model": "isActive",
"styleClasses": "col-md-2"
}
]
}
}
Data Tables (tables2
) Implementation
tables2
) ImplementationWhere to Add Data Tables in the IDE
Open your page in the BetterForms IDE
Navigate to Page Builder tab
Search for "table" in the snippets
Copy and paste into your page schema
Basic Data Table Structure
{
"type": "tables2",
"label": "Customer List",
"model": "customers",
"styleClasses": "col-md-12",
"columns": ["firstName", "lastName", "email", "status"],
"options": {
"filterable": true,
"sortable": true,
"pagination": {
"enabled": true,
"perPage": 10
}
}
}
Data Model for Data Tables
Your page's data model should include an array of objects:
{
"customers": [
{
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]",
"status": "Active"
},
{
"firstName": "Jane",
"lastName": "Smith",
"email": "[email protected]",
"status": "Inactive"
}
]
}
Advanced Data Table with Custom Formatting
{
"type": "tables2",
"label": "Order Management",
"model": "orders",
"styleClasses": "col-md-12",
"columns": ["orderNumber", "customer", "total", "status", "actions"],
"options": {
"filterable": ["customer", "status"],
"sortable": ["orderNumber", "customer", "total"],
"pagination": {
"enabled": true,
"perPage": 25
},
"texts": {
"count": "Showing {from} to {to} of {count} orders",
"filter": "Search orders:",
"filterPlaceholder": "Type to search..."
}
},
"slots": [
{
"slot": "total",
"html": "<strong>${{props.row.total}}</strong>"
},
{
"slot": "status",
"html": "<span class=\"badge badge-{{props.row.status === 'Complete' ? 'success' : 'warning'}}\">{{props.row.status}}</span>"
},
{
"slot": "actions",
"html": "<button class=\"btn btn-sm btn-primary\" v-on:click=\"namedAction('editOrder', {orderId: props.row.id})\">Edit</button>"
}
]
}
Row Click Actions
Adding Row Click Functionality
{
"type": "tables2",
"label": "Customer List",
"model": "customers",
"styleClasses": "col-md-12",
"columns": ["firstName", "lastName", "email"],
"actions_onRowClick": [
{
"action": "path",
"options": {
"path_calc": "'/customer-detail?id=' + this.params.row.id"
}
}
]
}
Using Named Actions with Row Data
{
"type": "tables2",
"label": "Customer List",
"model": "customers",
"styleClasses": "col-md-12",
"columns": ["firstName", "lastName", "email"],
"actions_onRowClick": [
{
"action": "namedAction",
"name": "viewCustomerDetail"
}
]
}
In your Named Action:
// Access the clicked row data
let customer = action.options.params.row;
let customerId = customer.id;
let customerName = customer.firstName + ' ' + customer.lastName;
Custom Column Formatting with Slots
Adding Custom HTML to Columns
{
"type": "tables2",
"label": "Product Catalog",
"model": "products",
"columns": ["name", "price", "inventory", "status", "actions"],
"slots": [
{
"slot": "price",
"html": "<span class=\"text-success\">${{props.row.price}}</span>"
},
{
"slot": "inventory",
"html": "<span class=\"{{props.row.inventory < 10 ? 'text-danger' : 'text-success'}}\">{{props.row.inventory}} units</span>"
},
{
"slot": "status",
"html": "<span class=\"badge badge-{{props.row.status === 'active' ? 'success' : 'secondary'}}\">{{props.row.status}}</span>"
},
{
"slot": "actions",
"html": "<div class=\"btn-group\"><button class=\"btn btn-sm btn-primary\" v-on:click=\"namedAction('editProduct', {productId: props.row.id})\">Edit</button><button class=\"btn btn-sm btn-danger\" v-on:click=\"namedAction('deleteProduct', {productId: props.row.id})\">Delete</button></div>"
}
]
}
Child Row Expansion
Display additional details when a row is expanded:
{
"type": "tables2",
"label": "Order List",
"model": "orders",
"columns": ["orderNumber", "customer", "total", "status"],
"slots": [
{
"slot": "child_row",
"html": "<div class=\"p-3\"><h5>Order Details</h5><p><strong>Shipping Address:</strong> {{props.row.shippingAddress}}</p><p><strong>Notes:</strong> {{props.row.notes}}</p></div>"
}
]
}
Integration with FileMaker
Loading Data from FileMaker
Use the onFormRequest
hook to populate table data:
FileMaker Script:
// In your onFormRequest hook
If[Not IsEmpty($$BF_Query)]
Set Variable [$action; Value:JSONGetElement($$BF_Query, "action")]
If[$action = "loadCustomers"]
// Perform find and return customer data
Set Variable [$customers; Value:GetColumnAsArray("customers", "id", "1")]
Set Variable [$result; Value:JSONSetElement("{}", "customers", $customers, JSONArray)]
Exit Script [Result: $result]
End If
End If
Triggering FileMaker Actions from Tables
{
"type": "tables2",
"label": "Customer List",
"model": "customers",
"columns": ["firstName", "lastName", "email", "actions"],
"slots": [
{
"slot": "actions",
"html": "<button class=\"btn btn-sm btn-success\" v-on:click=\"runUtilityHook({type: 'activateCustomer', customerId: props.row.id})\">Activate</button>"
}
]
}
Table Filtering and Sorting
Basic Filtering
{
"type": "tables2",
"model": "products",
"columns": ["name", "category", "price"],
"options": {
"filterable": true,
"filterByColumn": true,
"pagination": {
"enabled": true,
"perPage": 20
}
}
}
Custom Sort Functions
For numeric sorting of text fields:
{
"type": "tables2",
"model": "products",
"columns": ["name", "price", "date"],
"options": {
"sortable": true,
"customSorting": {
"price_function": "var ascending = arguments[0]; return function(a,b){if (ascending) return parseFloat(a.price) >= parseFloat(b.price) ? 1 : -1; return parseFloat(a.price) <= parseFloat(b.price) ? 1 : -1;}"
}
}
}
Performance Considerations
For Large Datasets
Use Server-Side Pagination
Load data in chunks from FileMaker
Implement search on the server side
Optimize FileMaker Queries
Use efficient finds
Limit returned fields
Use
getColumnAsArray
for better performance
Consider Data Caching
Cache frequently accessed data
Use development data model for testing
Table Performance Tips
{
"type": "tables2",
"model": "largeDataset",
"columns": ["id", "name", "status"],
"options": {
"pagination": {
"enabled": true,
"perPage": 50
},
"filterable": ["name", "status"],
"sortable": ["name", "id"],
"texts": {
"count": "Showing {from} to {to} of {count} records"
}
}
}
Best Practices
1. Choose the Right Component
Use
listrows
for data entryUse
tables2
for data displayConsider user workflow needs
2. Optimize User Experience
Provide clear column headers
Use consistent formatting
Add loading states for large datasets
3. Handle Mobile Responsiveness
Test table behavior on mobile devices
Consider hiding non-essential columns
Use responsive design principles
4. Security Considerations
Validate user permissions for row actions
Sanitize data before display
Use proper authentication for sensitive data
Troubleshooting Common Issues
List Views Not Showing
Check that the data model contains the correct array
Verify the
model
property matches your data structureEnsure
schema.fields
are properly configured
Data Table Not Loading
Verify the data model array structure
Check that column names match data properties
Ensure
model
property is correctly set
Row Actions Not Working
Check action syntax in
actions_onRowClick
Verify named actions are properly defined
Test with simple actions first
Next Steps
Now that you understand data display options, you can:
Style your tables - See 4.4 Basic App Styling
Integrate with authentication - See Users & Authentication
Explore advanced features - See Data Table Reference
Last updated