What is the screen telling you when the total won't follow the dropdowns?
The operator picks a status or part number from a dropdown. The table rows change, but the pallet-weight total either shows the grand total or keeps the value from the previous selection. The Perspective table has no built-in column footer or aggregate for this case. You have to build the total yourself, and it only updates if its binding subscribes to something that changes when the filter changes.
Perspective bindings are subscription-driven. A property binding re-evaluates only when the property it points at changes. A script transform runs only when its binding's input changes. If the transform reads a dropdown with self.getSibling(...), that read does not create a subscription. The tag is right; the binding is wrong. The checks below find which of three architectures you have and route you to the matching fix.
Check 1: Is the table's built-in filter doing the filtering?
Reading: In the Designer Property Editor, look at the table's filter properties while you type in the table's own filter box in preview.
-
Built-in filter in use: The table publishes the filtered rows at
filter.results.data. Bind your total to that path and stop here. -
Filtering is done with external dropdowns or text inputs:
filter.results.datais not populated by your logic. Go to Check 2.
Check 2: Does props.data change when a dropdown changes?
Reading: Open the table's props.data in the Property Editor during preview. Change a dropdown and watch whether the dataset row count changes.
| Observation | What it means | Next step |
|---|---|---|
Row count in props.data changes |
The dropdowns feed the query parameters or WHERE clause. The data binding re-runs on each selection. |
Bind a custom property or label to props.data and sum it. The subscription chain handles refresh: dropdown → data binding → custom prop → total label. |
| Row count stays the same, but visible rows change | The query returns everything. Filtering happens in a transform or elsewhere in the view. | Go to Check 3. |
| Nothing changes at all | The dropdown is not wired to anything the table reads. | Fix the dropdown binding first, then return to Check 2. |
For the first row, an expression on the total label is enough. Adjust the component path and column name to your view:
// Total weight
sum({../Table.props.data}, "PalletWeight")
// Record count
len({../Table.props.data})
Check 3: Should the filter live in SQL or in the view?
Two configurations both work. Choose based on data volume and how many filters you expect to add.
| Option | Where it runs | Effect | Use when |
|---|---|---|---|
Separate SUM query with the dropdown values in its WHERE clause |
Database | This adds a second round trip. The total can drift from the table if rows change between the two queries, or if you update one WHERE clause and not the other. |
The result set is large, or the table is paged server-side and never holds all rows. |
| Custom property holds the filtered dataset; both the table and the total bind to it | Gateway, in the view session | There is one filter definition. The table and the total cannot disagree, because both read the same dataset. | The dataset fits comfortably in memory. This covers typical pallet or work-order tables. |
Prefer the in-view option for operator screens. The common failure in that design is a custom property bound only to props.data, with a transform that filters by reading dropdown values inside the script. When a dropdown changes, props.data does not change, so the transform never re-runs.
A working pattern is to send a message on each dropdown or text-input change and call refreshBinding on the custom property. That works, but every new input needs its own message call. A cleaner fix is to put every filter input into the binding itself, so the subscription does the work.
Which pitfalls keep coming back?
| Symptom | Cause | Fix |
|---|---|---|
| Total lags one selection behind or never changes | The transform reads dropdowns through getSibling, so there is no subscription. |
Move the inputs into an expression structure binding, or call refreshBinding on change. |
| Total goes blank or errors on some selections | The weight column contains nulls. | Skip or zero nulls in the transform before summing. |
| Total matches the unfiltered sum | The label is bound to the raw query result, not the filtered dataset. | Re-point the label to the filtered custom property. |
| A new filter works on the table but not the total | The filter logic is duplicated in two places. | Filter once, in one custom property, and bind both the table and the total to it. |
| Sum concatenates or throws a type error | The weight column is typed as a string in the query. | Cast the column in SQL, or convert it with float() in the transform. |
How do you build the filtered dataset and verify the total?
- Leave the SQL binding that returns the full dataset where it is. Move it to a custom property on the table, for example
custom.rawData. - Create a second custom property,
custom.filteredData. Give it an expression structure binding with one key per input:data→{this.custom.rawData},status→ the status dropdown'sprops.value,part→ the part-number dropdown'sprops.value. Any change to any key re-runs the transform. - Add a script transform to
custom.filteredData. The column names below are examples; match them to your query.def transform(self, value, quality, timestamp): ds = value['data'] status = value['status'] part = value['part'] headers = list(ds.getColumnNames()) rows = [] for r in range(ds.getRowCount()): if status and ds.getValueAt(r, 'Status') != status: continue if part and ds.getValueAt(r, 'PartNumber') != part: continue rows.append([ds.getValueAt(r, c) for c in range(ds.getColumnCount())]) return system.dataset.toDataSet(headers, rows) - Bind the table's
props.datato{this.custom.filteredData}. The table now shows exactly what the total sums. - Bind the total label to
sum({../Table.custom.filteredData}, "PalletWeight")and the count label tolen({../Table.custom.filteredData}). If the column can contain nulls, compute the sum in a transform that skips them. - To add a future filter, add one key to the expression structure and one
iftest to the transform. No message handlers need updating. - If you keep the message-based design instead, have each input's
onChangeevent send a message. In the table's message handler, callself.refreshBinding('custom.filteredData'). Confirm the message scope reaches the table.
Verification: Clear all dropdowns and confirm the total equals a manual SELECT SUM(...) of the unfiltered query. Select one status value and confirm the count label matches the row count shown in the table. Then change the part-number dropdown without touching status, and confirm both labels update on the first change, not the second. Finally, run the same SUM in the database with both filter values in the WHERE clause. The screen total must match it to the last decimal the column carries.
FAQ
What happens if I bind the total to props.data but filter the rows in a transform?
The total goes stale. Perspective only re-evaluates the binding when props.data changes, and dropdown values read inside a script transform do not trigger it. Put the dropdown values into an expression structure binding, or call refreshBinding on each change.
What happens if the pallet weight column contains null values?
The summed result can come back null or raise an error, depending on how you total it. Skip nulls, or treat them as zero, in the script transform before summing, or wrap the column in COALESCE in the SQL query.
What happens if the table uses paging: does the sum only cover the visible page?
No. The pager limits what is displayed, not the dataset. A sum bound to props.data or a filtered custom property covers every row in that dataset, not just the visible page.
What happens if I add another filter dropdown later?
With the single filtered custom property, you add one key to the expression structure binding and one condition to the transform. The table and the total both pick it up. With duplicated logic or message-based refresh, you must update every copy, which is where totals start disagreeing with the table.
Can I use filter.results.data when filtering with my own dropdowns?
Only if the dropdowns drive the table's built-in filter. When filtering is done externally, filter.results.data does not reflect your selection, so build and sum your own filtered dataset instead.