Appearance
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)
- Simple multi-row data entry forms
- Add/remove rows functionality
- Good for data input scenarios
- Limited sorting and filtering
2. Data Tables (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
Where 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
json
{
"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:
json
{
"contacts": [
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com"
},
{
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@example.com"
}
]
}Advanced List View Example
json
{
"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
Where 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
json
{
"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:
json
{
"customers": [
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"status": "Active"
},
{
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@example.com",
"status": "Inactive"
}
]
}Advanced Data Table with Custom Formatting
json
{
"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
json
{
"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
json
{
"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:
javascript
// 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
json
{
"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:
json
{
"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:
javascript
// 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 IfTriggering FileMaker Actions from Tables
json
{
"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
json
{
"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:
json
{
"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
getColumnAsArrayfor better performance
- Consider Data Caching
- Cache frequently accessed data
- Use development data model for testing
Table Performance Tips
json
{
"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
listrowsfor data entry - Use
tables2for data display - Consider 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
modelproperty matches your data structure - Ensure
schema.fieldsare properly configured
Data Table Not Loading
- Verify the data model array structure
- Check that column names match data properties
- Ensure
modelproperty 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 Authentication
- Explore advanced features - See Data Table Reference
INFO
Performance Tip: For datasets with more than 100 rows, consider implementing server-side pagination and filtering in your FileMaker scripts.