Skip to content

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 rowsDisplaying read-only data
Simple data entry formsLarge datasets requiring search/filter
Contact lists, address booksReports and analytics
Product catalogs (editable)User management interfaces
Max 20-30 rowsHundreds or thousands of rows

List Views (listrows) Implementation ​

Where to Add List Views in the IDE ​

  1. Open your page in the BetterForms IDE
  2. Navigate to Page Builder tab
  3. Search for "listrows" in the snippets
  4. 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 ​

  1. Open your page in the BetterForms IDE
  2. Navigate to Page Builder tab
  3. Search for "table" in the snippets
  4. 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 If

Triggering 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 ​

  1. Use Server-Side Pagination
    • Load data in chunks from FileMaker
    • Implement search on the server side
  2. Optimize FileMaker Queries
    • Use efficient finds
    • Limit returned fields
    • Use getColumnAsArray for better performance
  3. 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 listrows for data entry
  • Use tables2 for 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 model property matches your data structure
  • Ensure 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:

INFO

Performance Tip: For datasets with more than 100 rows, consider implementing server-side pagination and filtering in your FileMaker scripts.

Klai Studio documentation