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

  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

{
  "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

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

{
  "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

  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

{
  "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:

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

Last updated